@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.
Files changed (510) hide show
  1. package/LICENSE +45 -0
  2. package/PRIVACY.md +181 -0
  3. package/README.md +34 -0
  4. package/SECURITY.md +43 -0
  5. package/dist/bench/evidence-pack/anti-dummy.d.ts +11 -0
  6. package/dist/bench/evidence-pack/anti-dummy.js +20 -0
  7. package/dist/bench/evidence-pack/anti-dummy.js.map +1 -0
  8. package/dist/bench/evidence-pack/builder.d.ts +14 -0
  9. package/dist/bench/evidence-pack/builder.js +77 -0
  10. package/dist/bench/evidence-pack/builder.js.map +1 -0
  11. package/dist/bench/evidence-pack/histograms.d.ts +2 -0
  12. package/dist/bench/evidence-pack/histograms.js +131 -0
  13. package/dist/bench/evidence-pack/histograms.js.map +1 -0
  14. package/dist/bench/evidence-pack/manifest-detector.d.ts +2 -0
  15. package/dist/bench/evidence-pack/manifest-detector.js +65 -0
  16. package/dist/bench/evidence-pack/manifest-detector.js.map +1 -0
  17. package/dist/bench/evidence-pack/package-json-digest.d.ts +2 -0
  18. package/dist/bench/evidence-pack/package-json-digest.js +51 -0
  19. package/dist/bench/evidence-pack/package-json-digest.js.map +1 -0
  20. package/dist/bench/evidence-pack/sampler.d.ts +2 -0
  21. package/dist/bench/evidence-pack/sampler.js +140 -0
  22. package/dist/bench/evidence-pack/sampler.js.map +1 -0
  23. package/dist/bench/evidence-pack/types.d.ts +127 -0
  24. package/dist/bench/evidence-pack/types.js +2 -0
  25. package/dist/bench/evidence-pack/types.js.map +1 -0
  26. package/dist/bench/evidence-pack/verifier-corpus.d.ts +5 -0
  27. package/dist/bench/evidence-pack/verifier-corpus.js +13 -0
  28. package/dist/bench/evidence-pack/verifier-corpus.js.map +1 -0
  29. package/dist/bench/taxonomy-loader.d.ts +20 -0
  30. package/dist/bench/taxonomy-loader.js +28 -0
  31. package/dist/bench/taxonomy-loader.js.map +1 -0
  32. package/dist/bench/taxonomy.v3.json +20 -0
  33. package/dist/cli/__tests__/version-migration.test.d.ts +1 -0
  34. package/dist/cli/__tests__/version-migration.test.js +69 -0
  35. package/dist/cli/__tests__/version-migration.test.js.map +1 -0
  36. package/dist/cli/output/__tests__/eslint-style.test.d.ts +1 -0
  37. package/dist/cli/output/__tests__/eslint-style.test.js +205 -0
  38. package/dist/cli/output/__tests__/eslint-style.test.js.map +1 -0
  39. package/dist/cli/output/__tests__/limit.test.d.ts +1 -0
  40. package/dist/cli/output/__tests__/limit.test.js +65 -0
  41. package/dist/cli/output/__tests__/limit.test.js.map +1 -0
  42. package/dist/cli/output/eslint-style.d.ts +18 -0
  43. package/dist/cli/output/eslint-style.js +42 -0
  44. package/dist/cli/output/eslint-style.js.map +1 -0
  45. package/dist/cli/output/limit.d.ts +5 -0
  46. package/dist/cli/output/limit.js +32 -0
  47. package/dist/cli/output/limit.js.map +1 -0
  48. package/dist/cli/output/score-gauge.d.ts +4 -0
  49. package/dist/cli/output/score-gauge.js +15 -0
  50. package/dist/cli/output/score-gauge.js.map +1 -0
  51. package/dist/cli/version-migration.d.ts +18 -0
  52. package/dist/cli/version-migration.js +49 -0
  53. package/dist/cli/version-migration.js.map +1 -0
  54. package/dist/cli.d.ts +2 -0
  55. package/dist/cli.js +856 -0
  56. package/dist/cli.js.map +1 -0
  57. package/dist/codemods/diff.d.ts +17 -0
  58. package/dist/codemods/diff.js +77 -0
  59. package/dist/codemods/diff.js.map +1 -0
  60. package/dist/codemods/git-helpers.d.ts +10 -0
  61. package/dist/codemods/git-helpers.js +75 -0
  62. package/dist/codemods/git-helpers.js.map +1 -0
  63. package/dist/codemods/index.d.ts +27 -0
  64. package/dist/codemods/index.js +40 -0
  65. package/dist/codemods/index.js.map +1 -0
  66. package/dist/codemods/naming-component-pascalcase.d.ts +13 -0
  67. package/dist/codemods/naming-component-pascalcase.js +106 -0
  68. package/dist/codemods/naming-component-pascalcase.js.map +1 -0
  69. package/dist/codemods/naming-hook-prefix.d.ts +13 -0
  70. package/dist/codemods/naming-hook-prefix.js +86 -0
  71. package/dist/codemods/naming-hook-prefix.js.map +1 -0
  72. package/dist/codemods/safety.d.ts +50 -0
  73. package/dist/codemods/safety.js +109 -0
  74. package/dist/codemods/safety.js.map +1 -0
  75. package/dist/codemods/safety.test.d.ts +1 -0
  76. package/dist/codemods/safety.test.js +154 -0
  77. package/dist/codemods/safety.test.js.map +1 -0
  78. package/dist/codemods/shadow-native.d.ts +2 -0
  79. package/dist/codemods/shadow-native.js +103 -0
  80. package/dist/codemods/shadow-native.js.map +1 -0
  81. package/dist/codemods/tokens-color.d.ts +2 -0
  82. package/dist/codemods/tokens-color.js +69 -0
  83. package/dist/codemods/tokens-color.js.map +1 -0
  84. package/dist/codemods/tokens-spacing.d.ts +2 -0
  85. package/dist/codemods/tokens-spacing.js +66 -0
  86. package/dist/codemods/tokens-spacing.js.map +1 -0
  87. package/dist/commands/__tests__/email-prompt.test.d.ts +1 -0
  88. package/dist/commands/__tests__/email-prompt.test.js +126 -0
  89. package/dist/commands/__tests__/email-prompt.test.js.map +1 -0
  90. package/dist/commands/__tests__/explain-score.test.d.ts +1 -0
  91. package/dist/commands/__tests__/explain-score.test.js +88 -0
  92. package/dist/commands/__tests__/explain-score.test.js.map +1 -0
  93. package/dist/commands/__tests__/feedback.test.d.ts +1 -0
  94. package/dist/commands/__tests__/feedback.test.js +186 -0
  95. package/dist/commands/__tests__/feedback.test.js.map +1 -0
  96. package/dist/commands/audit-flags.d.ts +49 -0
  97. package/dist/commands/audit-flags.js +17 -0
  98. package/dist/commands/audit-flags.js.map +1 -0
  99. package/dist/commands/audit-pipeline.d.ts +31 -0
  100. package/dist/commands/audit-pipeline.js +342 -0
  101. package/dist/commands/audit-pipeline.js.map +1 -0
  102. package/dist/commands/bench-pack.d.ts +5 -0
  103. package/dist/commands/bench-pack.js +61 -0
  104. package/dist/commands/bench-pack.js.map +1 -0
  105. package/dist/commands/ci-setup.d.ts +9 -0
  106. package/dist/commands/ci-setup.js +42 -0
  107. package/dist/commands/ci-setup.js.map +1 -0
  108. package/dist/commands/email-prompt.d.ts +36 -0
  109. package/dist/commands/email-prompt.js +160 -0
  110. package/dist/commands/email-prompt.js.map +1 -0
  111. package/dist/commands/explain-score.d.ts +35 -0
  112. package/dist/commands/explain-score.js +137 -0
  113. package/dist/commands/explain-score.js.map +1 -0
  114. package/dist/commands/explain.d.ts +6 -0
  115. package/dist/commands/explain.js +93 -0
  116. package/dist/commands/explain.js.map +1 -0
  117. package/dist/commands/feedback.d.ts +31 -0
  118. package/dist/commands/feedback.js +155 -0
  119. package/dist/commands/feedback.js.map +1 -0
  120. package/dist/commands/fix.d.ts +48 -0
  121. package/dist/commands/fix.js +191 -0
  122. package/dist/commands/fix.js.map +1 -0
  123. package/dist/commands/init-detect.d.ts +20 -0
  124. package/dist/commands/init-detect.js +124 -0
  125. package/dist/commands/init-detect.js.map +1 -0
  126. package/dist/commands/init-write-agents-md.d.ts +14 -0
  127. package/dist/commands/init-write-agents-md.js +71 -0
  128. package/dist/commands/init-write-agents-md.js.map +1 -0
  129. package/dist/commands/init-write-lyse-md.d.ts +14 -0
  130. package/dist/commands/init-write-lyse-md.js +253 -0
  131. package/dist/commands/init-write-lyse-md.js.map +1 -0
  132. package/dist/commands/init.d.ts +8 -0
  133. package/dist/commands/init.js +147 -0
  134. package/dist/commands/init.js.map +1 -0
  135. package/dist/commands/mcp-entry.d.ts +33 -0
  136. package/dist/commands/mcp-entry.js +47 -0
  137. package/dist/commands/mcp-entry.js.map +1 -0
  138. package/dist/commands/mcp-setup.d.ts +10 -0
  139. package/dist/commands/mcp-setup.js +74 -0
  140. package/dist/commands/mcp-setup.js.map +1 -0
  141. package/dist/commands/share.d.ts +4 -0
  142. package/dist/commands/share.js +60 -0
  143. package/dist/commands/share.js.map +1 -0
  144. package/dist/commands/telemetry.d.ts +4 -0
  145. package/dist/commands/telemetry.js +27 -0
  146. package/dist/commands/telemetry.js.map +1 -0
  147. package/dist/commands/templates/lyse-workflow.yml.template +30 -0
  148. package/dist/config/schema.d.ts +158 -0
  149. package/dist/config/schema.js +136 -0
  150. package/dist/config/schema.js.map +1 -0
  151. package/dist/credentials/keychain.d.ts +20 -0
  152. package/dist/credentials/keychain.js +35 -0
  153. package/dist/credentials/keychain.js.map +1 -0
  154. package/dist/credentials/keychain.test.d.ts +1 -0
  155. package/dist/credentials/keychain.test.js +32 -0
  156. package/dist/credentials/keychain.test.js.map +1 -0
  157. package/dist/credentials/paths.d.ts +1 -0
  158. package/dist/credentials/paths.js +6 -0
  159. package/dist/credentials/paths.js.map +1 -0
  160. package/dist/credentials/store.d.ts +17 -0
  161. package/dist/credentials/store.js +60 -0
  162. package/dist/credentials/store.js.map +1 -0
  163. package/dist/credentials/store.test.d.ts +1 -0
  164. package/dist/credentials/store.test.js +48 -0
  165. package/dist/credentials/store.test.js.map +1 -0
  166. package/dist/detection/from-filesystem.d.ts +2 -0
  167. package/dist/detection/from-filesystem.js +26 -0
  168. package/dist/detection/from-filesystem.js.map +1 -0
  169. package/dist/detection/from-git.d.ts +2 -0
  170. package/dist/detection/from-git.js +53 -0
  171. package/dist/detection/from-git.js.map +1 -0
  172. package/dist/detection/from-package-json.d.ts +2 -0
  173. package/dist/detection/from-package-json.js +194 -0
  174. package/dist/detection/from-package-json.js.map +1 -0
  175. package/dist/detection/pre-flight.d.ts +5 -0
  176. package/dist/detection/pre-flight.js +47 -0
  177. package/dist/detection/pre-flight.js.map +1 -0
  178. package/dist/detection/types.d.ts +27 -0
  179. package/dist/detection/types.js +2 -0
  180. package/dist/detection/types.js.map +1 -0
  181. package/dist/entitlement/check.d.ts +7 -0
  182. package/dist/entitlement/check.js +37 -0
  183. package/dist/entitlement/check.js.map +1 -0
  184. package/dist/entitlement/index.d.ts +1 -0
  185. package/dist/entitlement/index.js +2 -0
  186. package/dist/entitlement/index.js.map +1 -0
  187. package/dist/entitlement/keys.d.ts +10 -0
  188. package/dist/entitlement/keys.js +14 -0
  189. package/dist/entitlement/keys.js.map +1 -0
  190. package/dist/history/ndjson-store.d.ts +70 -0
  191. package/dist/history/ndjson-store.js +102 -0
  192. package/dist/history/ndjson-store.js.map +1 -0
  193. package/dist/identity/index.d.ts +1 -0
  194. package/dist/identity/index.js +2 -0
  195. package/dist/identity/index.js.map +1 -0
  196. package/dist/identity/repo-bucket.d.ts +22 -0
  197. package/dist/identity/repo-bucket.js +78 -0
  198. package/dist/identity/repo-bucket.js.map +1 -0
  199. package/dist/index.d.ts +1 -0
  200. package/dist/index.js +15 -0
  201. package/dist/index.js.map +1 -0
  202. package/dist/llm/layer4-stage.d.ts +18 -0
  203. package/dist/llm/layer4-stage.js +12 -0
  204. package/dist/llm/layer4-stage.js.map +1 -0
  205. package/dist/loaders/components.d.ts +26 -0
  206. package/dist/loaders/components.js +285 -0
  207. package/dist/loaders/components.js.map +1 -0
  208. package/dist/loaders/stories.d.ts +2 -0
  209. package/dist/loaders/stories.js +231 -0
  210. package/dist/loaders/stories.js.map +1 -0
  211. package/dist/loaders/token-graph.d.ts +39 -0
  212. package/dist/loaders/token-graph.js +146 -0
  213. package/dist/loaders/token-graph.js.map +1 -0
  214. package/dist/loaders/tokens.d.ts +2 -0
  215. package/dist/loaders/tokens.js +521 -0
  216. package/dist/loaders/tokens.js.map +1 -0
  217. package/dist/mcp/_find-root.d.ts +12 -0
  218. package/dist/mcp/_find-root.js +28 -0
  219. package/dist/mcp/_find-root.js.map +1 -0
  220. package/dist/mcp/server.d.ts +1 -0
  221. package/dist/mcp/server.js +42 -0
  222. package/dist/mcp/server.js.map +1 -0
  223. package/dist/mcp/tools/audit-file.d.ts +25 -0
  224. package/dist/mcp/tools/audit-file.js +147 -0
  225. package/dist/mcp/tools/audit-file.js.map +1 -0
  226. package/dist/mcp/tools/suggest-fix.d.ts +13 -0
  227. package/dist/mcp/tools/suggest-fix.js +100 -0
  228. package/dist/mcp/tools/suggest-fix.js.map +1 -0
  229. package/dist/menu/action-menu.d.ts +6 -0
  230. package/dist/menu/action-menu.js +15 -0
  231. package/dist/menu/action-menu.js.map +1 -0
  232. package/dist/menu/prompts.d.ts +7 -0
  233. package/dist/menu/prompts.js +30 -0
  234. package/dist/menu/prompts.js.map +1 -0
  235. package/dist/menu/repl.d.ts +17 -0
  236. package/dist/menu/repl.js +77 -0
  237. package/dist/menu/repl.js.map +1 -0
  238. package/dist/parsers/css-in-js.d.ts +2 -0
  239. package/dist/parsers/css-in-js.js +69 -0
  240. package/dist/parsers/css-in-js.js.map +1 -0
  241. package/dist/parsers/css.d.ts +2 -0
  242. package/dist/parsers/css.js +32 -0
  243. package/dist/parsers/css.js.map +1 -0
  244. package/dist/parsers/scss-transform.d.ts +25 -0
  245. package/dist/parsers/scss-transform.js +55 -0
  246. package/dist/parsers/scss-transform.js.map +1 -0
  247. package/dist/parsers/tailwind-v4.d.ts +8 -0
  248. package/dist/parsers/tailwind-v4.js +90 -0
  249. package/dist/parsers/tailwind-v4.js.map +1 -0
  250. package/dist/parsers/ts-morph-project.d.ts +16 -0
  251. package/dist/parsers/ts-morph-project.js +77 -0
  252. package/dist/parsers/ts-morph-project.js.map +1 -0
  253. package/dist/parsers/ts.d.ts +2 -0
  254. package/dist/parsers/ts.js +61 -0
  255. package/dist/parsers/ts.js.map +1 -0
  256. package/dist/reliability/__tests__/types.test.d.ts +1 -0
  257. package/dist/reliability/__tests__/types.test.js +14 -0
  258. package/dist/reliability/__tests__/types.test.js.map +1 -0
  259. package/dist/reliability/catalogue/__tests__/promotion.test.d.ts +1 -0
  260. package/dist/reliability/catalogue/__tests__/promotion.test.js +23 -0
  261. package/dist/reliability/catalogue/__tests__/promotion.test.js.map +1 -0
  262. package/dist/reliability/catalogue/__tests__/sub-axes.test.d.ts +1 -0
  263. package/dist/reliability/catalogue/__tests__/sub-axes.test.js +27 -0
  264. package/dist/reliability/catalogue/__tests__/sub-axes.test.js.map +1 -0
  265. package/dist/reliability/catalogue/promotion.d.ts +8 -0
  266. package/dist/reliability/catalogue/promotion.js +25 -0
  267. package/dist/reliability/catalogue/promotion.js.map +1 -0
  268. package/dist/reliability/catalogue/sub-axes.d.ts +3 -0
  269. package/dist/reliability/catalogue/sub-axes.js +18 -0
  270. package/dist/reliability/catalogue/sub-axes.js.map +1 -0
  271. package/dist/reliability/confidence/__tests__/manifest-loader.test.d.ts +1 -0
  272. package/dist/reliability/confidence/__tests__/manifest-loader.test.js +103 -0
  273. package/dist/reliability/confidence/__tests__/manifest-loader.test.js.map +1 -0
  274. package/dist/reliability/confidence/bundled-manifest.d.ts +2 -0
  275. package/dist/reliability/confidence/bundled-manifest.js +8 -0
  276. package/dist/reliability/confidence/bundled-manifest.js.map +1 -0
  277. package/dist/reliability/confidence/index.d.ts +3 -0
  278. package/dist/reliability/confidence/index.js +3 -0
  279. package/dist/reliability/confidence/index.js.map +1 -0
  280. package/dist/reliability/confidence/manifest-loader.d.ts +8 -0
  281. package/dist/reliability/confidence/manifest-loader.js +53 -0
  282. package/dist/reliability/confidence/manifest-loader.js.map +1 -0
  283. package/dist/reliability/feedback/__tests__/interactive.test.d.ts +1 -0
  284. package/dist/reliability/feedback/__tests__/interactive.test.js +85 -0
  285. package/dist/reliability/feedback/__tests__/interactive.test.js.map +1 -0
  286. package/dist/reliability/feedback/__tests__/repo-bucket.test.d.ts +1 -0
  287. package/dist/reliability/feedback/__tests__/repo-bucket.test.js +18 -0
  288. package/dist/reliability/feedback/__tests__/repo-bucket.test.js.map +1 -0
  289. package/dist/reliability/feedback/__tests__/sender.test.d.ts +1 -0
  290. package/dist/reliability/feedback/__tests__/sender.test.js +101 -0
  291. package/dist/reliability/feedback/__tests__/sender.test.js.map +1 -0
  292. package/dist/reliability/feedback/interactive.d.ts +20 -0
  293. package/dist/reliability/feedback/interactive.js +150 -0
  294. package/dist/reliability/feedback/interactive.js.map +1 -0
  295. package/dist/reliability/feedback/repo-bucket.d.ts +2 -0
  296. package/dist/reliability/feedback/repo-bucket.js +9 -0
  297. package/dist/reliability/feedback/repo-bucket.js.map +1 -0
  298. package/dist/reliability/feedback/sender.d.ts +16 -0
  299. package/dist/reliability/feedback/sender.js +28 -0
  300. package/dist/reliability/feedback/sender.js.map +1 -0
  301. package/dist/reliability/index.d.ts +1 -0
  302. package/dist/reliability/index.js +2 -0
  303. package/dist/reliability/index.js.map +1 -0
  304. package/dist/reliability/llm-eval/__tests__/budget.test.d.ts +1 -0
  305. package/dist/reliability/llm-eval/__tests__/budget.test.js +39 -0
  306. package/dist/reliability/llm-eval/__tests__/budget.test.js.map +1 -0
  307. package/dist/reliability/llm-eval/budget.d.ts +14 -0
  308. package/dist/reliability/llm-eval/budget.js +43 -0
  309. package/dist/reliability/llm-eval/budget.js.map +1 -0
  310. package/dist/reliability/score/__tests__/formula-v1.test.d.ts +1 -0
  311. package/dist/reliability/score/__tests__/formula-v1.test.js +29 -0
  312. package/dist/reliability/score/__tests__/formula-v1.test.js.map +1 -0
  313. package/dist/reliability/score/formula-v1.d.ts +13 -0
  314. package/dist/reliability/score/formula-v1.js +19 -0
  315. package/dist/reliability/score/formula-v1.js.map +1 -0
  316. package/dist/reliability/score/version-pin.d.ts +1 -0
  317. package/dist/reliability/score/version-pin.js +2 -0
  318. package/dist/reliability/score/version-pin.js.map +1 -0
  319. package/dist/reliability/score/weight.d.ts +1 -0
  320. package/dist/reliability/score/weight.js +5 -0
  321. package/dist/reliability/score/weight.js.map +1 -0
  322. package/dist/reliability/types.d.ts +39 -0
  323. package/dist/reliability/types.js +2 -0
  324. package/dist/reliability/types.js.map +1 -0
  325. package/dist/reporters/coverage-footer.d.ts +2 -0
  326. package/dist/reporters/coverage-footer.js +7 -0
  327. package/dist/reporters/coverage-footer.js.map +1 -0
  328. package/dist/reporters/json.d.ts +5 -0
  329. package/dist/reporters/json.js +51 -0
  330. package/dist/reporters/json.js.map +1 -0
  331. package/dist/reporters/markdown.d.ts +7 -0
  332. package/dist/reporters/markdown.js +35 -0
  333. package/dist/reporters/markdown.js.map +1 -0
  334. package/dist/reporters/sarif.d.ts +5 -0
  335. package/dist/reporters/sarif.js +109 -0
  336. package/dist/reporters/sarif.js.map +1 -0
  337. package/dist/reporters/terminal-format.d.ts +34 -0
  338. package/dist/reporters/terminal-format.js +97 -0
  339. package/dist/reporters/terminal-format.js.map +1 -0
  340. package/dist/reporters/terminal.d.ts +4 -0
  341. package/dist/reporters/terminal.js +201 -0
  342. package/dist/reporters/terminal.js.map +1 -0
  343. package/dist/rule-runner.d.ts +8 -0
  344. package/dist/rule-runner.js +22 -0
  345. package/dist/rule-runner.js.map +1 -0
  346. package/dist/rules/_codemod-adapter.d.ts +16 -0
  347. package/dist/rules/_codemod-adapter.js +49 -0
  348. package/dist/rules/_codemod-adapter.js.map +1 -0
  349. package/dist/rules/_exclude.d.ts +11 -0
  350. package/dist/rules/_exclude.js +17 -0
  351. package/dist/rules/_exclude.js.map +1 -0
  352. package/dist/rules/_function-body-analysis.d.ts +82 -0
  353. package/dist/rules/_function-body-analysis.js +379 -0
  354. package/dist/rules/_function-body-analysis.js.map +1 -0
  355. package/dist/rules/_rule-module.d.ts +31 -0
  356. package/dist/rules/_rule-module.js +32 -0
  357. package/dist/rules/_rule-module.js.map +1 -0
  358. package/dist/rules/_skip-context.d.ts +36 -0
  359. package/dist/rules/_skip-context.js +128 -0
  360. package/dist/rules/_skip-context.js.map +1 -0
  361. package/dist/rules/a11y-essentials.d.ts +1 -0
  362. package/dist/rules/a11y-essentials.js +140 -0
  363. package/dist/rules/a11y-essentials.js.map +1 -0
  364. package/dist/rules/ai-surface-agents-md-quality.d.ts +22 -0
  365. package/dist/rules/ai-surface-agents-md-quality.js +233 -0
  366. package/dist/rules/ai-surface-agents-md-quality.js.map +1 -0
  367. package/dist/rules/ai-surface-component-manifest-json.d.ts +19 -0
  368. package/dist/rules/ai-surface-component-manifest-json.js +232 -0
  369. package/dist/rules/ai-surface-component-manifest-json.js.map +1 -0
  370. package/dist/rules/ai-surface-ds-index-exported.d.ts +19 -0
  371. package/dist/rules/ai-surface-ds-index-exported.js +239 -0
  372. package/dist/rules/ai-surface-ds-index-exported.js.map +1 -0
  373. package/dist/rules/components-shadow-native.d.ts +2 -0
  374. package/dist/rules/components-shadow-native.js +118 -0
  375. package/dist/rules/components-shadow-native.js.map +1 -0
  376. package/dist/rules/manifest.d.ts +5 -0
  377. package/dist/rules/manifest.js +20 -0
  378. package/dist/rules/manifest.js.map +1 -0
  379. package/dist/rules/naming-component-pascalcase.d.ts +7 -0
  380. package/dist/rules/naming-component-pascalcase.js +197 -0
  381. package/dist/rules/naming-component-pascalcase.js.map +1 -0
  382. package/dist/rules/naming-hook-prefix.d.ts +6 -0
  383. package/dist/rules/naming-hook-prefix.js +185 -0
  384. package/dist/rules/naming-hook-prefix.js.map +1 -0
  385. package/dist/rules/pack-loader.d.ts +30 -0
  386. package/dist/rules/pack-loader.js +60 -0
  387. package/dist/rules/pack-loader.js.map +1 -0
  388. package/dist/rules/pack-validator.d.ts +8 -0
  389. package/dist/rules/pack-validator.js +97 -0
  390. package/dist/rules/pack-validator.js.map +1 -0
  391. package/dist/rules/registry.d.ts +3 -0
  392. package/dist/rules/registry.js +28 -0
  393. package/dist/rules/registry.js.map +1 -0
  394. package/dist/rules/storybook-coverage.d.ts +1 -0
  395. package/dist/rules/storybook-coverage.js +49 -0
  396. package/dist/rules/storybook-coverage.js.map +1 -0
  397. package/dist/rules/templates/_regex-utils.d.ts +5 -0
  398. package/dist/rules/templates/_regex-utils.js +8 -0
  399. package/dist/rules/templates/_regex-utils.js.map +1 -0
  400. package/dist/rules/templates/a11y-jsx-template.d.ts +15 -0
  401. package/dist/rules/templates/a11y-jsx-template.js +69 -0
  402. package/dist/rules/templates/a11y-jsx-template.js.map +1 -0
  403. package/dist/rules/templates/css-property-token-compliance.d.ts +18 -0
  404. package/dist/rules/templates/css-property-token-compliance.js +56 -0
  405. package/dist/rules/templates/css-property-token-compliance.js.map +1 -0
  406. package/dist/rules/templates/import-source-restriction.d.ts +18 -0
  407. package/dist/rules/templates/import-source-restriction.js +60 -0
  408. package/dist/rules/templates/import-source-restriction.js.map +1 -0
  409. package/dist/rules/templates/js-call-token-compliance.d.ts +18 -0
  410. package/dist/rules/templates/js-call-token-compliance.js +61 -0
  411. package/dist/rules/templates/js-call-token-compliance.js.map +1 -0
  412. package/dist/rules/templates/js-prop-token-compliance.d.ts +21 -0
  413. package/dist/rules/templates/js-prop-token-compliance.js +56 -0
  414. package/dist/rules/templates/js-prop-token-compliance.js.map +1 -0
  415. package/dist/rules/templates/naming-convention.d.ts +18 -0
  416. package/dist/rules/templates/naming-convention.js +76 -0
  417. package/dist/rules/templates/naming-convention.js.map +1 -0
  418. package/dist/rules/templates/registry.d.ts +5 -0
  419. package/dist/rules/templates/registry.js +30 -0
  420. package/dist/rules/templates/registry.js.map +1 -0
  421. package/dist/rules/templates/storybook-coverage-template.d.ts +15 -0
  422. package/dist/rules/templates/storybook-coverage-template.js +58 -0
  423. package/dist/rules/templates/storybook-coverage-template.js.map +1 -0
  424. package/dist/rules/templates/tailwind-utility-class-compliance.d.ts +15 -0
  425. package/dist/rules/templates/tailwind-utility-class-compliance.js +70 -0
  426. package/dist/rules/templates/tailwind-utility-class-compliance.js.map +1 -0
  427. package/dist/rules/templates/types.d.ts +16 -0
  428. package/dist/rules/templates/types.js +2 -0
  429. package/dist/rules/templates/types.js.map +1 -0
  430. package/dist/rules/tokens-description-coverage.d.ts +15 -0
  431. package/dist/rules/tokens-description-coverage.js +193 -0
  432. package/dist/rules/tokens-description-coverage.js.map +1 -0
  433. package/dist/rules/tokens-dtcg-conformance.d.ts +47 -0
  434. package/dist/rules/tokens-dtcg-conformance.js +363 -0
  435. package/dist/rules/tokens-dtcg-conformance.js.map +1 -0
  436. package/dist/rules/tokens-no-hardcoded-color.d.ts +18 -0
  437. package/dist/rules/tokens-no-hardcoded-color.js +436 -0
  438. package/dist/rules/tokens-no-hardcoded-color.js.map +1 -0
  439. package/dist/rules/tokens-no-hardcoded-spacing.d.ts +8 -0
  440. package/dist/rules/tokens-no-hardcoded-spacing.js +193 -0
  441. package/dist/rules/tokens-no-hardcoded-spacing.js.map +1 -0
  442. package/dist/scorer.d.ts +38 -0
  443. package/dist/scorer.js +126 -0
  444. package/dist/scorer.js.map +1 -0
  445. package/dist/share/clipboard.d.ts +26 -0
  446. package/dist/share/clipboard.js +76 -0
  447. package/dist/share/clipboard.js.map +1 -0
  448. package/dist/suppression/__tests__/inline.test.d.ts +1 -0
  449. package/dist/suppression/__tests__/inline.test.js +25 -0
  450. package/dist/suppression/__tests__/inline.test.js.map +1 -0
  451. package/dist/suppression/__tests__/lyseignore.test.d.ts +1 -0
  452. package/dist/suppression/__tests__/lyseignore.test.js +32 -0
  453. package/dist/suppression/__tests__/lyseignore.test.js.map +1 -0
  454. package/dist/suppression/defaults.d.ts +1 -0
  455. package/dist/suppression/defaults.js +14 -0
  456. package/dist/suppression/defaults.js.map +1 -0
  457. package/dist/suppression/inline.d.ts +7 -0
  458. package/dist/suppression/inline.js +32 -0
  459. package/dist/suppression/inline.js.map +1 -0
  460. package/dist/suppression/lyseignore.d.ts +2 -0
  461. package/dist/suppression/lyseignore.js +22 -0
  462. package/dist/suppression/lyseignore.js.map +1 -0
  463. package/dist/telemetry/consent.d.ts +48 -0
  464. package/dist/telemetry/consent.js +139 -0
  465. package/dist/telemetry/consent.js.map +1 -0
  466. package/dist/telemetry/index.d.ts +2 -0
  467. package/dist/telemetry/index.js +3 -0
  468. package/dist/telemetry/index.js.map +1 -0
  469. package/dist/telemetry/local-log.d.ts +64 -0
  470. package/dist/telemetry/local-log.js +123 -0
  471. package/dist/telemetry/local-log.js.map +1 -0
  472. package/dist/tokens/dtcg-model.d.ts +53 -0
  473. package/dist/tokens/dtcg-model.js +18 -0
  474. package/dist/tokens/dtcg-model.js.map +1 -0
  475. package/dist/tokens/normalizer.d.ts +17 -0
  476. package/dist/tokens/normalizer.js +360 -0
  477. package/dist/tokens/normalizer.js.map +1 -0
  478. package/dist/types.d.ts +300 -0
  479. package/dist/types.js +4 -0
  480. package/dist/types.js.map +1 -0
  481. package/dist/util/git.d.ts +6 -0
  482. package/dist/util/git.js +40 -0
  483. package/dist/util/git.js.map +1 -0
  484. package/dist/util/git.test.d.ts +5 -0
  485. package/dist/util/git.test.js +42 -0
  486. package/dist/util/git.test.js.map +1 -0
  487. package/dist/util/gitignore.d.ts +9 -0
  488. package/dist/util/gitignore.js +39 -0
  489. package/dist/util/gitignore.js.map +1 -0
  490. package/dist/util/hash-deps.d.ts +6 -0
  491. package/dist/util/hash-deps.js +24 -0
  492. package/dist/util/hash-deps.js.map +1 -0
  493. package/dist/util/spinner.d.ts +41 -0
  494. package/dist/util/spinner.js +126 -0
  495. package/dist/util/spinner.js.map +1 -0
  496. package/dist/util/with-spinner.d.ts +11 -0
  497. package/dist/util/with-spinner.js +19 -0
  498. package/dist/util/with-spinner.js.map +1 -0
  499. package/dist/walker.d.ts +14 -0
  500. package/dist/walker.js +91 -0
  501. package/dist/walker.js.map +1 -0
  502. package/package.json +83 -0
  503. package/rules-manifest.json +289 -0
  504. package/schemas/v1/lyse-config.json +82 -0
  505. package/schemas/v1/lyse-event.json +68 -0
  506. package/schemas/v1/lyse-license.json +19 -0
  507. package/schemas/v1/lyse-llm-payload.json +53 -0
  508. package/schemas/v1/lyse-llm-response.json +45 -0
  509. package/schemas/v1/lyse-result.json +98 -0
  510. 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