@vertaaux/cli 0.3.3 → 0.5.0

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 (227) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/MIGRATION.md +239 -0
  3. package/README.md +34 -16
  4. package/dist/app/interactive-app.d.ts +101 -0
  5. package/dist/app/interactive-app.d.ts.map +1 -0
  6. package/dist/app/interactive-app.js +309 -0
  7. package/dist/app/layout/canvas.d.ts +23 -0
  8. package/dist/app/layout/canvas.d.ts.map +1 -0
  9. package/dist/app/layout/canvas.js +36 -0
  10. package/dist/app/layout/footer.d.ts +31 -0
  11. package/dist/app/layout/footer.d.ts.map +1 -0
  12. package/dist/app/layout/footer.js +41 -0
  13. package/dist/app/layout/header.d.ts +20 -0
  14. package/dist/app/layout/header.d.ts.map +1 -0
  15. package/dist/app/layout/header.js +27 -0
  16. package/dist/app/menu/categories.d.ts +20 -0
  17. package/dist/app/menu/categories.d.ts.map +1 -0
  18. package/dist/app/menu/categories.js +181 -0
  19. package/dist/app/menu/filter.d.ts +17 -0
  20. package/dist/app/menu/filter.d.ts.map +1 -0
  21. package/dist/app/menu/filter.js +33 -0
  22. package/dist/app/menu/menu-view.d.ts +35 -0
  23. package/dist/app/menu/menu-view.d.ts.map +1 -0
  24. package/dist/app/menu/menu-view.js +230 -0
  25. package/dist/app/menu/recent.d.ts +24 -0
  26. package/dist/app/menu/recent.d.ts.map +1 -0
  27. package/dist/app/menu/recent.js +49 -0
  28. package/dist/app/types.d.ts +43 -0
  29. package/dist/app/types.d.ts.map +1 -0
  30. package/dist/app/types.js +7 -0
  31. package/dist/app/views/command-runner.d.ts +36 -0
  32. package/dist/app/views/command-runner.d.ts.map +1 -0
  33. package/dist/app/views/command-runner.js +372 -0
  34. package/dist/app/views/help-overlay.d.ts +21 -0
  35. package/dist/app/views/help-overlay.d.ts.map +1 -0
  36. package/dist/app/views/help-overlay.js +45 -0
  37. package/dist/auth/ci-token.d.ts +14 -2
  38. package/dist/auth/ci-token.d.ts.map +1 -1
  39. package/dist/auth/ci-token.js +15 -30
  40. package/dist/auth/device-flow.d.ts +2 -1
  41. package/dist/auth/device-flow.d.ts.map +1 -1
  42. package/dist/auth/device-flow.js +13 -10
  43. package/dist/auth/token-store.d.ts.map +1 -1
  44. package/dist/auth/token-store.js +12 -2
  45. package/dist/baseline/diff.d.ts +2 -2
  46. package/dist/baseline/diff.d.ts.map +1 -1
  47. package/dist/baseline/diff.js +15 -34
  48. package/dist/commands/a11y.d.ts +9 -0
  49. package/dist/commands/a11y.d.ts.map +1 -0
  50. package/dist/commands/a11y.js +76 -0
  51. package/dist/commands/audit/artifacts.d.ts +27 -0
  52. package/dist/commands/audit/artifacts.d.ts.map +1 -0
  53. package/dist/commands/audit/artifacts.js +158 -0
  54. package/dist/commands/audit/ci-detection.d.ts +18 -0
  55. package/dist/commands/audit/ci-detection.d.ts.map +1 -0
  56. package/dist/commands/audit/ci-detection.js +71 -0
  57. package/dist/commands/audit/explain.d.ts +11 -0
  58. package/dist/commands/audit/explain.d.ts.map +1 -0
  59. package/dist/commands/audit/explain.js +45 -0
  60. package/dist/commands/audit/filters.d.ts +17 -0
  61. package/dist/commands/audit/filters.d.ts.map +1 -0
  62. package/dist/commands/audit/filters.js +40 -0
  63. package/dist/commands/audit/index.d.ts +18 -0
  64. package/dist/commands/audit/index.d.ts.map +1 -0
  65. package/dist/commands/audit/index.js +564 -0
  66. package/dist/commands/audit/output.d.ts +32 -0
  67. package/dist/commands/audit/output.d.ts.map +1 -0
  68. package/dist/commands/audit/output.js +130 -0
  69. package/dist/commands/audit/policy.d.ts +19 -0
  70. package/dist/commands/audit/policy.d.ts.map +1 -0
  71. package/dist/commands/audit/policy.js +102 -0
  72. package/dist/commands/audit/scoring.d.ts +23 -0
  73. package/dist/commands/audit/scoring.d.ts.map +1 -0
  74. package/dist/commands/audit/scoring.js +70 -0
  75. package/dist/commands/audit/types.d.ts +88 -0
  76. package/dist/commands/audit/types.d.ts.map +1 -0
  77. package/dist/commands/audit/types.js +8 -0
  78. package/dist/commands/audit.d.ts +2 -60
  79. package/dist/commands/audit.d.ts.map +1 -1
  80. package/dist/commands/audit.js +2 -1038
  81. package/dist/commands/baseline.d.ts +1 -0
  82. package/dist/commands/baseline.d.ts.map +1 -1
  83. package/dist/commands/baseline.js +205 -121
  84. package/dist/commands/comment.d.ts +22 -0
  85. package/dist/commands/comment.d.ts.map +1 -1
  86. package/dist/commands/comment.js +122 -58
  87. package/dist/commands/compare.d.ts +17 -0
  88. package/dist/commands/compare.d.ts.map +1 -1
  89. package/dist/commands/compare.js +287 -180
  90. package/dist/commands/diff.d.ts +5 -0
  91. package/dist/commands/diff.d.ts.map +1 -1
  92. package/dist/commands/diff.js +168 -141
  93. package/dist/commands/doc.d.ts +10 -0
  94. package/dist/commands/doc.d.ts.map +1 -1
  95. package/dist/commands/doc.js +134 -76
  96. package/dist/commands/doctor.d.ts +2 -0
  97. package/dist/commands/doctor.d.ts.map +1 -1
  98. package/dist/commands/doctor.js +164 -17
  99. package/dist/commands/download.d.ts +10 -0
  100. package/dist/commands/download.d.ts.map +1 -1
  101. package/dist/commands/download.js +169 -112
  102. package/dist/commands/explain.d.ts +5 -0
  103. package/dist/commands/explain.d.ts.map +1 -1
  104. package/dist/commands/explain.js +241 -155
  105. package/dist/commands/fix-all.d.ts +25 -0
  106. package/dist/commands/fix-all.d.ts.map +1 -0
  107. package/dist/commands/fix-all.js +206 -0
  108. package/dist/commands/fix-plan.d.ts +9 -0
  109. package/dist/commands/fix-plan.d.ts.map +1 -1
  110. package/dist/commands/fix-plan.js +152 -89
  111. package/dist/commands/fix.d.ts +17 -0
  112. package/dist/commands/fix.d.ts.map +1 -0
  113. package/dist/commands/fix.js +111 -0
  114. package/dist/commands/init.d.ts +11 -0
  115. package/dist/commands/init.d.ts.map +1 -1
  116. package/dist/commands/init.js +94 -42
  117. package/dist/commands/login.d.ts +18 -0
  118. package/dist/commands/login.d.ts.map +1 -1
  119. package/dist/commands/login.js +268 -95
  120. package/dist/commands/patch-review.d.ts +11 -0
  121. package/dist/commands/patch-review.d.ts.map +1 -1
  122. package/dist/commands/patch-review.js +159 -97
  123. package/dist/commands/policy.d.ts +31 -0
  124. package/dist/commands/policy.d.ts.map +1 -1
  125. package/dist/commands/policy.js +269 -124
  126. package/dist/commands/release-notes.d.ts +10 -0
  127. package/dist/commands/release-notes.d.ts.map +1 -1
  128. package/dist/commands/release-notes.js +127 -73
  129. package/dist/commands/scan.d.ts +13 -0
  130. package/dist/commands/scan.d.ts.map +1 -0
  131. package/dist/commands/scan.js +133 -0
  132. package/dist/commands/status.d.ts +9 -0
  133. package/dist/commands/status.d.ts.map +1 -0
  134. package/dist/commands/status.js +81 -0
  135. package/dist/commands/suggest.d.ts +10 -0
  136. package/dist/commands/suggest.d.ts.map +1 -1
  137. package/dist/commands/suggest.js +153 -82
  138. package/dist/commands/triage.d.ts +35 -0
  139. package/dist/commands/triage.d.ts.map +1 -1
  140. package/dist/commands/triage.js +206 -81
  141. package/dist/commands/upload.d.ts +9 -0
  142. package/dist/commands/upload.d.ts.map +1 -1
  143. package/dist/commands/upload.js +140 -101
  144. package/dist/commands/verify.d.ts +13 -0
  145. package/dist/commands/verify.d.ts.map +1 -0
  146. package/dist/commands/verify.js +118 -0
  147. package/dist/index.d.ts +3 -2
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +125 -990
  150. package/dist/interactive/fix-wizard.d.ts +3 -0
  151. package/dist/interactive/fix-wizard.d.ts.map +1 -1
  152. package/dist/interactive/fix-wizard.js +130 -112
  153. package/dist/interactive/init-wizard.d.ts +3 -1
  154. package/dist/interactive/init-wizard.d.ts.map +1 -1
  155. package/dist/interactive/init-wizard.js +207 -138
  156. package/dist/interactive/prompts.d.ts +7 -3
  157. package/dist/interactive/prompts.d.ts.map +1 -1
  158. package/dist/interactive/prompts.js +44 -23
  159. package/dist/output/envelope.d.ts +2 -0
  160. package/dist/output/envelope.d.ts.map +1 -1
  161. package/dist/output/envelope.js +18 -2
  162. package/dist/output/factory.d.ts +9 -1
  163. package/dist/output/factory.d.ts.map +1 -1
  164. package/dist/output/html.d.ts +2 -1
  165. package/dist/output/html.d.ts.map +1 -1
  166. package/dist/output/html.js +3 -2
  167. package/dist/output/human.d.ts +9 -1
  168. package/dist/output/human.d.ts.map +1 -1
  169. package/dist/output/human.js +17 -2
  170. package/dist/output/json.d.ts +2 -1
  171. package/dist/output/json.d.ts.map +1 -1
  172. package/dist/output/junit.d.ts +2 -1
  173. package/dist/output/junit.d.ts.map +1 -1
  174. package/dist/output/sarif.d.ts +2 -1
  175. package/dist/output/sarif.d.ts.map +1 -1
  176. package/dist/types.d.ts +74 -0
  177. package/dist/types.d.ts.map +1 -0
  178. package/dist/types.js +5 -0
  179. package/dist/ui/banner.d.ts +34 -0
  180. package/dist/ui/banner.d.ts.map +1 -1
  181. package/dist/ui/banner.js +97 -5
  182. package/dist/ui/diagnostics.d.ts +9 -4
  183. package/dist/ui/diagnostics.d.ts.map +1 -1
  184. package/dist/ui/diagnostics.js +32 -82
  185. package/dist/ui/strings.d.ts +373 -0
  186. package/dist/ui/strings.d.ts.map +1 -0
  187. package/dist/ui/strings.js +499 -0
  188. package/dist/ui/table.d.ts +0 -2
  189. package/dist/ui/table.d.ts.map +1 -1
  190. package/dist/ui/table.js +3 -4
  191. package/dist/utils/api-client.d.ts +46 -0
  192. package/dist/utils/api-client.d.ts.map +1 -0
  193. package/dist/utils/api-client.js +170 -0
  194. package/dist/utils/client.d.ts +29 -18
  195. package/dist/utils/client.d.ts.map +1 -1
  196. package/dist/utils/client.js +102 -12
  197. package/dist/utils/formatters.d.ts +38 -0
  198. package/dist/utils/formatters.d.ts.map +1 -0
  199. package/dist/utils/formatters.js +277 -0
  200. package/dist/utils/local-capture.d.ts +25 -0
  201. package/dist/utils/local-capture.d.ts.map +1 -0
  202. package/dist/utils/local-capture.js +57 -0
  203. package/dist/utils/url-classify.d.ts +18 -0
  204. package/dist/utils/url-classify.d.ts.map +1 -0
  205. package/dist/utils/url-classify.js +106 -0
  206. package/node_modules/@vertaaux/tui/dist/index.cjs +713 -20
  207. package/node_modules/@vertaaux/tui/dist/index.cjs.map +1 -1
  208. package/node_modules/@vertaaux/tui/dist/index.d.cts +361 -4
  209. package/node_modules/@vertaaux/tui/dist/index.d.ts +361 -4
  210. package/node_modules/@vertaaux/tui/dist/index.js +689 -21
  211. package/node_modules/@vertaaux/tui/dist/index.js.map +1 -1
  212. package/package.json +13 -5
  213. package/dist/commands/client.d.ts +0 -14
  214. package/dist/commands/client.d.ts.map +0 -1
  215. package/dist/commands/client.js +0 -362
  216. package/dist/commands/drift.d.ts +0 -15
  217. package/dist/commands/drift.d.ts.map +0 -1
  218. package/dist/commands/drift.js +0 -309
  219. package/dist/commands/protect.d.ts +0 -16
  220. package/dist/commands/protect.d.ts.map +0 -1
  221. package/dist/commands/protect.js +0 -323
  222. package/dist/commands/report.d.ts +0 -15
  223. package/dist/commands/report.d.ts.map +0 -1
  224. package/dist/commands/report.js +0 -214
  225. package/dist/policy/sync.d.ts +0 -67
  226. package/dist/policy/sync.d.ts.map +0 -1
  227. package/dist/policy/sync.js +0 -147
@@ -4,8 +4,8 @@
4
4
  * Tokens are stored in ~/.vertaaux/credentials.json with restrictive permissions.
5
5
  * This is separate from project config - tokens are user-scoped, not project-scoped.
6
6
  */
7
- import { readFile, writeFile, mkdir, unlink } from "fs/promises";
8
- import { existsSync } from "fs";
7
+ import { readFile, writeFile, mkdir, unlink, chmod } from "fs/promises";
8
+ import { existsSync, lstatSync } from "fs";
9
9
  import { homedir } from "os";
10
10
  import { join, dirname } from "path";
11
11
  /**
@@ -26,6 +26,10 @@ export function getCredentialsPath() {
26
26
  export async function saveToken(token) {
27
27
  const credPath = getCredentialsPath();
28
28
  const dir = dirname(credPath);
29
+ // SECVAL-1: Refuse to write if credentials path is a symlink
30
+ if (existsSync(credPath) && lstatSync(credPath).isSymbolicLink()) {
31
+ throw new Error(`Security error: ${credPath} is a symlink. Refusing to write credentials.`);
32
+ }
29
33
  // Create directory if needed
30
34
  if (!existsSync(dir)) {
31
35
  await mkdir(dir, { recursive: true, mode: 0o700 });
@@ -33,6 +37,8 @@ export async function saveToken(token) {
33
37
  // Write token with restrictive permissions
34
38
  const content = JSON.stringify(token, null, 2);
35
39
  await writeFile(credPath, content + "\n", { encoding: "utf-8", mode: 0o600 });
40
+ // SECVAL-2: Ensure restrictive permissions after write
41
+ await chmod(credPath, 0o600);
36
42
  }
37
43
  /**
38
44
  * Load token data from credentials file.
@@ -44,6 +50,10 @@ export async function loadToken() {
44
50
  if (!existsSync(credPath)) {
45
51
  return null;
46
52
  }
53
+ // SECVAL-1: Refuse to read if credentials path is a symlink
54
+ if (lstatSync(credPath).isSymbolicLink()) {
55
+ return null;
56
+ }
47
57
  try {
48
58
  const content = await readFile(credPath, "utf-8");
49
59
  return JSON.parse(content);
@@ -38,8 +38,8 @@ export declare function computeDiff(currentIssues: Issue[], baseline: BaselineFi
38
38
  * Format diff result for human-readable output.
39
39
  *
40
40
  * Uses colors:
41
- * - New issues: red
42
- * - Fixed issues: green
41
+ * - New issues: red (tokens.error)
42
+ * - Fixed issues: green (tokens.success)
43
43
  * - Still present: dim
44
44
  *
45
45
  * @param diff - Diff result to format
@@ -1 +1 @@
1
- {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/baseline/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAuB,KAAK,KAAK,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,iCAAiC;IACjC,GAAG,EAAE,KAAK,EAAE,CAAC;IACb,8CAA8C;IAC9C,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,qCAAqC;IACrC,OAAO,EAAE,KAAK,EAAE,CAAC;IACjB,qBAAqB;IACrB,OAAO,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,aAAa,EAAE,KAAK,EAAE,EACtB,QAAQ,EAAE,YAAY,GACrB,UAAU,CA2CZ;AA2CD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,UAAQ,GAAG,MAAM,CAsCzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEvD"}
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/baseline/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAuB,KAAK,KAAK,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,iCAAiC;IACjC,GAAG,EAAE,KAAK,EAAE,CAAC;IACb,8CAA8C;IAC9C,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,qCAAqC;IACrC,OAAO,EAAE,KAAK,EAAE,CAAC;IACjB,qBAAqB;IACrB,OAAO,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,aAAa,EAAE,KAAK,EAAE,EACtB,QAAQ,EAAE,YAAY,GACrB,UAAU,CA2CZ;AAuBD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,UAAQ,GAAG,MAAM,CAsCzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEvD"}
@@ -7,7 +7,7 @@
7
7
  * - Fixed: Issues in baseline but not in current
8
8
  * - Present: Issues in both current and baseline
9
9
  */
10
- import chalk from "chalk";
10
+ import { boldColor, dim, tokens, text } from "@vertaaux/tui";
11
11
  import { generateFingerprint } from "./hash.js";
12
12
  /**
13
13
  * Compute diff between current issues and baseline.
@@ -60,25 +60,6 @@ export function computeDiff(currentIssues, baseline) {
60
60
  function getRuleId(issue) {
61
61
  return issue.ruleId || issue.rule_id || issue.id || "unknown";
62
62
  }
63
- /**
64
- * Format severity badge for display.
65
- */
66
- function formatSeverityBadge(severity) {
67
- const upper = (severity || "info").toUpperCase();
68
- switch (upper) {
69
- case "ERROR":
70
- case "CRITICAL":
71
- return chalk.red(`[${upper}]`);
72
- case "WARNING":
73
- case "SERIOUS":
74
- return chalk.yellow(`[${upper}]`);
75
- case "INFO":
76
- case "MINOR":
77
- return chalk.blue(`[${upper}]`);
78
- default:
79
- return chalk.gray(`[${upper}]`);
80
- }
81
- }
82
63
  /**
83
64
  * Format issue line for display.
84
65
  */
@@ -88,14 +69,14 @@ function formatIssueLine(issue) {
88
69
  ? issue.description
89
70
  : issue.description || issue.title || "";
90
71
  const severity = issue.severity || "info";
91
- return `${formatSeverityBadge(severity)} ${ruleId}: ${description}`;
72
+ return `[${text.severityBadge(severity)}] ${ruleId}: ${description}`;
92
73
  }
93
74
  /**
94
75
  * Format diff result for human-readable output.
95
76
  *
96
77
  * Uses colors:
97
- * - New issues: red
98
- * - Fixed issues: green
78
+ * - New issues: red (tokens.error)
79
+ * - Fixed issues: green (tokens.success)
99
80
  * - Still present: dim
100
81
  *
101
82
  * @param diff - Diff result to format
@@ -104,10 +85,10 @@ function formatIssueLine(issue) {
104
85
  */
105
86
  export function formatDiffHuman(diff, verbose = false) {
106
87
  const lines = [];
107
- // New issues section (red)
108
- lines.push(chalk.red.bold(`NEW ISSUES (${diff.summary.newCount})`));
88
+ // New issues section (red bold)
89
+ lines.push(boldColor(`NEW ISSUES (${diff.summary.newCount})`, tokens.error));
109
90
  if (diff.new.length === 0) {
110
- lines.push(chalk.dim(" No new issues"));
91
+ lines.push(dim(" No new issues"));
111
92
  }
112
93
  else {
113
94
  for (const issue of diff.new) {
@@ -115,10 +96,10 @@ export function formatDiffHuman(diff, verbose = false) {
115
96
  }
116
97
  }
117
98
  lines.push("");
118
- // Fixed issues section (green)
119
- lines.push(chalk.green.bold(`FIXED (${diff.summary.fixedCount})`));
99
+ // Fixed issues section (green bold)
100
+ lines.push(boldColor(`FIXED (${diff.summary.fixedCount})`, tokens.success));
120
101
  if (diff.fixed.length === 0) {
121
- lines.push(chalk.dim(" No issues fixed"));
102
+ lines.push(dim(" No issues fixed"));
122
103
  }
123
104
  else {
124
105
  for (const issue of diff.fixed) {
@@ -126,18 +107,18 @@ export function formatDiffHuman(diff, verbose = false) {
126
107
  }
127
108
  }
128
109
  lines.push("");
129
- // Still present section (dim)
130
- lines.push(chalk.dim.bold(`STILL PRESENT (${diff.summary.presentCount})`));
110
+ // Still present section (dim only — no bold+dim combo per locked decisions)
111
+ lines.push(dim(`STILL PRESENT (${diff.summary.presentCount})`));
131
112
  if (diff.present.length === 0) {
132
- lines.push(chalk.dim(" No baselined issues remain"));
113
+ lines.push(dim(" No baselined issues remain"));
133
114
  }
134
115
  else if (verbose) {
135
116
  for (const issue of diff.present) {
136
- lines.push(chalk.dim(` ${formatIssueLine(issue)}`));
117
+ lines.push(dim(` ${formatIssueLine(issue)}`));
137
118
  }
138
119
  }
139
120
  else {
140
- lines.push(chalk.dim(" (use --verbose to show all)"));
121
+ lines.push(dim(" (use --verbose to show all)"));
141
122
  }
142
123
  return lines.join("\n");
143
124
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Legacy a11y command handler.
3
+ * Runs accessibility-focused audit (alias for audit with a11y filter).
4
+ */
5
+ import type { Command } from "commander";
6
+ import type { Flags } from "../types.js";
7
+ export declare function handleA11y(rawUrl: string, cmdOptions: Flags): Promise<void>;
8
+ export declare function registerA11yCommand(program: Command): void;
9
+ //# sourceMappingURL=a11y.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a11y.d.ts","sourceRoot":"","sources":["../../src/commands/a11y.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKzC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGzC,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAgDjF;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuB1D"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Legacy a11y command handler.
3
+ * Runs accessibility-focused audit (alias for audit with a11y filter).
4
+ */
5
+ import { renderError, runSteps, createRenderer } from "@vertaaux/tui";
6
+ import { ExitCode } from "../utils/exit-codes.js";
7
+ import { parseMode, parseTimeout, parseInterval, parseScore } from "../utils/validators.js";
8
+ import { resolveApiBase } from "../utils/api-client.js";
9
+ import { runAuditCommand } from "./scan.js";
10
+ export async function handleA11y(rawUrl, cmdOptions) {
11
+ const url = /^https?:\/\//i.test(rawUrl) ? rawUrl : `https://${rawUrl}`;
12
+ const renderer = createRenderer("auto");
13
+ const baseState = {
14
+ phase: "a11y",
15
+ phaseIndex: 1,
16
+ phaseTotal: 1,
17
+ url,
18
+ mode: "a11y",
19
+ progress: {},
20
+ totals: {},
21
+ issueCount: 0,
22
+ scorePreview: null,
23
+ verbose: false,
24
+ elapsed: 0,
25
+ };
26
+ const startTime = Date.now();
27
+ const steps = [
28
+ {
29
+ id: "a11y",
30
+ actionText: "Running accessibility audit...",
31
+ summaryText: "Accessibility audit complete",
32
+ run: async () => {
33
+ const base = resolveApiBase(cmdOptions);
34
+ await runAuditCommand(base, url, { ...cmdOptions, wait: cmdOptions.wait ?? true }, "a11y");
35
+ },
36
+ },
37
+ ];
38
+ const { success, states } = await runSteps(steps, {
39
+ failFast: true,
40
+ onStateChange: (stepStates) => {
41
+ renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
42
+ },
43
+ });
44
+ renderer.finish({ url, mode: "a11y", overallScore: 0, scores: {}, issueCount: 0, passed: success, elapsed: Date.now() - startTime });
45
+ if (!success) {
46
+ const failed = states.find(s => s.status === "failed");
47
+ process.stderr.write(renderError({
48
+ message: failed?.failReason || "Command failed",
49
+ suggestion: "vertaa doctor",
50
+ }) + "\n");
51
+ process.exitCode = ExitCode.ERROR;
52
+ }
53
+ }
54
+ export function registerA11yCommand(program) {
55
+ program
56
+ .command("a11y <url>")
57
+ .description("Run accessibility-focused audit (alias for audit with a11y filter)")
58
+ .option("--mode <mode>", "Audit depth: basic|standard|deep", parseMode, "basic")
59
+ .option("--wait", "Wait for audit completion")
60
+ .option("--timeout <ms>", "Wait timeout in milliseconds (1-300000)", parseTimeout, 60000)
61
+ .option("--interval <ms>", "Poll interval in milliseconds (1-300000)", parseInterval, 5000)
62
+ .option("--fail-on-score <n>", "Exit non-zero if score below n (0-100)", parseScore)
63
+ .action(async (url, cmdOptions) => {
64
+ try {
65
+ await handleA11y(url, cmdOptions);
66
+ }
67
+ catch (error) {
68
+ process.stderr.write(renderError({
69
+ message: error instanceof Error ? error.message : String(error),
70
+ suggestion: "vertaa audit <url>",
71
+ exitCode: ExitCode.ERROR,
72
+ }) + "\n");
73
+ process.exit(ExitCode.ERROR);
74
+ }
75
+ });
76
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Artifact saving for the audit command.
3
+ *
4
+ * Handles repro artifacts (trace, HAR, screenshots, DOM snapshots)
5
+ * and CI artifact bundling.
6
+ */
7
+ import type { AuditResponse } from "../../utils/client.js";
8
+ import type { IssueLike, AuditCommandOptions, ArtifactManifest } from "./types.js";
9
+ /**
10
+ * Count issues by severity level.
11
+ */
12
+ export declare function countIssuesBySeverity(issues: IssueLike[]): ArtifactManifest["issue_count"];
13
+ /**
14
+ * Save repro artifacts from API response.
15
+ */
16
+ export declare function saveReproArtifacts(jobId: string, response: AuditResponse, options: AuditCommandOptions, quiet: boolean): void;
17
+ /**
18
+ * Save CI artifact bundle for GitHub Actions upload.
19
+ *
20
+ * Creates a complete evidence bundle:
21
+ * - results.json: Full audit results
22
+ * - results.sarif: SARIF for Code Scanning
23
+ * - report.html: HTML report for viewing
24
+ * - manifest.json: Metadata about the bundle
25
+ */
26
+ export declare function saveArtifactBundle(jobId: string, result: AuditResponse, issues: IssueLike[], exitCode: number, quiet: boolean): string;
27
+ //# sourceMappingURL=artifacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../../../src/commands/audit/artifacts.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAI3D,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGnF;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,gBAAgB,CAAC,aAAa,CAAC,CAe1F;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,aAAa,EACvB,OAAO,EAAE,mBAAmB,EAC5B,KAAK,EAAE,OAAO,GACb,IAAI,CAmFN;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,SAAS,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,GACb,MAAM,CAkDR"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Artifact saving for the audit command.
3
+ *
4
+ * Handles repro artifacts (trace, HAR, screenshots, DOM snapshots)
5
+ * and CI artifact bundling.
6
+ */
7
+ import fs from "fs";
8
+ import path from "path";
9
+ import { writeOutput } from "../../output/envelope.js";
10
+ import { formatSarif, formatAuditHtml } from "../../output/factory.js";
11
+ import { assertPathContainment } from "../../utils/sanitize.js";
12
+ import { ARTIFACTS_DIR } from "./types.js";
13
+ /**
14
+ * Count issues by severity level.
15
+ */
16
+ export function countIssuesBySeverity(issues) {
17
+ const counts = { error: 0, warning: 0, info: 0 };
18
+ for (const issue of issues) {
19
+ const sev = (issue.severity || "info").toLowerCase();
20
+ if (sev === "error" || sev === "critical") {
21
+ counts.error++;
22
+ }
23
+ else if (sev === "warning" || sev === "serious") {
24
+ counts.warning++;
25
+ }
26
+ else {
27
+ counts.info++;
28
+ }
29
+ }
30
+ return counts;
31
+ }
32
+ /**
33
+ * Save repro artifacts from API response.
34
+ */
35
+ export function saveReproArtifacts(jobId, response, options, quiet) {
36
+ const hasArtifactOptions = options.saveTrace || options.saveHar || options.screenshots || options.domSnapshots;
37
+ if (!hasArtifactOptions)
38
+ return;
39
+ // Create artifacts directory
40
+ const artifactsPath = path.resolve(process.cwd(), ARTIFACTS_DIR, jobId);
41
+ // Check if API response includes artifact data
42
+ const responseAny = response;
43
+ const artifacts = responseAny.artifacts;
44
+ if (!artifacts) {
45
+ if (!quiet) {
46
+ writeOutput("Note: Repro artifacts were requested but not available in API response.\n");
47
+ writeOutput("Artifact capture may require a 'deep' mode audit or premium plan.\n");
48
+ }
49
+ return;
50
+ }
51
+ // Ensure directory exists
52
+ if (!fs.existsSync(artifactsPath)) {
53
+ fs.mkdirSync(artifactsPath, { recursive: true });
54
+ }
55
+ const saved = [];
56
+ // Save trace file if available and requested
57
+ if (options.saveTrace && artifacts.trace) {
58
+ const tracePath = path.join(artifactsPath, "trace.zip");
59
+ fs.writeFileSync(tracePath, Buffer.from(artifacts.trace, "base64"));
60
+ saved.push("trace.zip");
61
+ }
62
+ // Save HAR file if available and requested
63
+ if (options.saveHar && artifacts.har) {
64
+ const harPath = path.join(artifactsPath, "network.har");
65
+ fs.writeFileSync(harPath, typeof artifacts.har === "string" ? artifacts.har : JSON.stringify(artifacts.har, null, 2));
66
+ saved.push("network.har");
67
+ }
68
+ // Save screenshots if available and requested
69
+ if (options.screenshots && artifacts.screenshots) {
70
+ const screenshots = artifacts.screenshots;
71
+ for (const screenshot of screenshots) {
72
+ const screenshotName = screenshot.name || "screenshot.png";
73
+ let screenshotPath;
74
+ try {
75
+ screenshotPath = assertPathContainment(screenshotName, artifactsPath);
76
+ }
77
+ catch {
78
+ writeOutput(`Security: Rejected screenshot "${screenshotName}" -- path traversal outside artifacts directory.\n`);
79
+ continue;
80
+ }
81
+ fs.writeFileSync(screenshotPath, Buffer.from(screenshot.data, "base64"));
82
+ saved.push(screenshotName);
83
+ }
84
+ }
85
+ // Save DOM snapshots if available and requested
86
+ if (options.domSnapshots && artifacts.domSnapshots) {
87
+ const snapshots = artifacts.domSnapshots;
88
+ for (const snapshot of snapshots) {
89
+ const snapshotName = snapshot.name || "snapshot.html";
90
+ let snapshotPath;
91
+ try {
92
+ snapshotPath = assertPathContainment(snapshotName, artifactsPath);
93
+ }
94
+ catch {
95
+ writeOutput(`Security: Rejected snapshot "${snapshotName}" -- path traversal outside artifacts directory.\n`);
96
+ continue;
97
+ }
98
+ fs.writeFileSync(snapshotPath, snapshot.html);
99
+ saved.push(snapshotName);
100
+ }
101
+ }
102
+ if (saved.length > 0 && !quiet) {
103
+ writeOutput(`Artifacts saved to: ${artifactsPath}\n`);
104
+ writeOutput(` - ${saved.join("\n - ")}\n`);
105
+ }
106
+ }
107
+ /**
108
+ * Save CI artifact bundle for GitHub Actions upload.
109
+ *
110
+ * Creates a complete evidence bundle:
111
+ * - results.json: Full audit results
112
+ * - results.sarif: SARIF for Code Scanning
113
+ * - report.html: HTML report for viewing
114
+ * - manifest.json: Metadata about the bundle
115
+ */
116
+ export function saveArtifactBundle(jobId, result, issues, exitCode, quiet) {
117
+ const artifactsPath = path.resolve(process.cwd(), ARTIFACTS_DIR, jobId);
118
+ // Ensure directory exists
119
+ if (!fs.existsSync(artifactsPath)) {
120
+ fs.mkdirSync(artifactsPath, { recursive: true });
121
+ }
122
+ const files = [];
123
+ // 1. Save JSON results
124
+ const jsonPath = path.join(artifactsPath, "results.json");
125
+ fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2), "utf-8");
126
+ files.push("results.json");
127
+ // 2. Save SARIF for Code Scanning
128
+ const sarifContent = formatSarif(result, {
129
+ workingDirectory: process.cwd(),
130
+ });
131
+ const sarifPath = path.join(artifactsPath, "results.sarif");
132
+ fs.writeFileSync(sarifPath, sarifContent, "utf-8");
133
+ files.push("results.sarif");
134
+ // 3. Save HTML report
135
+ const htmlContent = formatAuditHtml(result, {
136
+ interactive: true,
137
+ });
138
+ const htmlPath = path.join(artifactsPath, "report.html");
139
+ fs.writeFileSync(htmlPath, htmlContent, "utf-8");
140
+ files.push("report.html");
141
+ // 4. Save manifest
142
+ const manifest = {
143
+ job_id: jobId,
144
+ timestamp: new Date().toISOString(),
145
+ files,
146
+ audit_url: result.url,
147
+ issue_count: countIssuesBySeverity(issues),
148
+ exit_code: exitCode,
149
+ };
150
+ const manifestPath = path.join(artifactsPath, "manifest.json");
151
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
152
+ files.push("manifest.json");
153
+ if (!quiet) {
154
+ writeOutput(`Artifacts saved to: ${artifactsPath}\n`);
155
+ writeOutput(` - ${files.join("\n - ")}\n`);
156
+ }
157
+ return artifactsPath;
158
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * CI environment detection for the audit command.
3
+ *
4
+ * Detects current branch and PR labels from various CI providers.
5
+ */
6
+ /**
7
+ * Detect current branch from CI environment or git.
8
+ */
9
+ export declare function detectCurrentBranch(): string;
10
+ /**
11
+ * Detect PR labels from CI environment variables.
12
+ *
13
+ * Supports:
14
+ * - GitHub Actions: GITHUB_EVENT_PATH contains event JSON with labels
15
+ * - GitLab CI: CI_MERGE_REQUEST_LABELS is comma-separated
16
+ */
17
+ export declare function detectPRLabels(): string[];
18
+ //# sourceMappingURL=ci-detection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci-detection.d.ts","sourceRoot":"","sources":["../../../src/commands/audit/ci-detection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAqC5C;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,IAAI,MAAM,EAAE,CA0BzC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * CI environment detection for the audit command.
3
+ *
4
+ * Detects current branch and PR labels from various CI providers.
5
+ */
6
+ import fs from "fs";
7
+ /**
8
+ * Detect current branch from CI environment or git.
9
+ */
10
+ export function detectCurrentBranch() {
11
+ // GitHub Actions
12
+ if (process.env.GITHUB_HEAD_REF) {
13
+ return process.env.GITHUB_HEAD_REF;
14
+ }
15
+ if (process.env.GITHUB_REF_NAME) {
16
+ return process.env.GITHUB_REF_NAME;
17
+ }
18
+ // GitLab CI
19
+ if (process.env.CI_COMMIT_REF_NAME) {
20
+ return process.env.CI_COMMIT_REF_NAME;
21
+ }
22
+ // Azure DevOps
23
+ if (process.env.BUILD_SOURCEBRANCHNAME) {
24
+ return process.env.BUILD_SOURCEBRANCHNAME;
25
+ }
26
+ // CircleCI
27
+ if (process.env.CIRCLE_BRANCH) {
28
+ return process.env.CIRCLE_BRANCH;
29
+ }
30
+ // Jenkins
31
+ if (process.env.GIT_BRANCH) {
32
+ // Jenkins often includes origin/, remove it
33
+ return process.env.GIT_BRANCH.replace(/^origin\//, "");
34
+ }
35
+ // Generic CI
36
+ if (process.env.BRANCH_NAME) {
37
+ return process.env.BRANCH_NAME;
38
+ }
39
+ // Default
40
+ return "";
41
+ }
42
+ /**
43
+ * Detect PR labels from CI environment variables.
44
+ *
45
+ * Supports:
46
+ * - GitHub Actions: GITHUB_EVENT_PATH contains event JSON with labels
47
+ * - GitLab CI: CI_MERGE_REQUEST_LABELS is comma-separated
48
+ */
49
+ export function detectPRLabels() {
50
+ // GitHub Actions: GITHUB_EVENT_PATH contains event JSON with labels
51
+ if (process.env.GITHUB_EVENT_PATH) {
52
+ try {
53
+ const eventContent = fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf-8");
54
+ const event = JSON.parse(eventContent);
55
+ const labels = event.pull_request?.labels;
56
+ if (Array.isArray(labels)) {
57
+ return labels.map((l) => l.name || "").filter(Boolean);
58
+ }
59
+ }
60
+ catch {
61
+ // Ignore errors reading event file
62
+ }
63
+ }
64
+ // GitLab CI: CI_MERGE_REQUEST_LABELS is comma-separated
65
+ if (process.env.CI_MERGE_REQUEST_LABELS) {
66
+ return process.env.CI_MERGE_REQUEST_LABELS.split(",")
67
+ .map((l) => l.trim())
68
+ .filter(Boolean);
69
+ }
70
+ return [];
71
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * AI inline explanation for the audit command.
3
+ */
4
+ import type { VertaauxConfig } from "../../config/schema.js";
5
+ import { type AuditResponse } from "../../utils/client.js";
6
+ import type { IssueLike, AuditCommandOptions } from "./types.js";
7
+ /**
8
+ * Run the inline AI explanation for audit issues.
9
+ */
10
+ export declare function runInlineExplanation(issues: IssueLike[], result: AuditResponse, targetUrl: string, options: AuditCommandOptions, config: VertaauxConfig): Promise<void>;
11
+ //# sourceMappingURL=explain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../../src/commands/audit/explain.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,uBAAuB,CAAC;AAK/B,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAGjE;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,SAAS,EAAE,EACnB,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,mBAAmB,EAC5B,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,IAAI,CAAC,CAyCf"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * AI inline explanation for the audit command.
3
+ */
4
+ import { bold, dim, colorize, brand } from "@vertaaux/tui";
5
+ import { writeOutput } from "../../output/envelope.js";
6
+ import { resolveApiBase, getApiKey, apiRequest, } from "../../utils/client.js";
7
+ import { createSpinner, succeedSpinner, } from "../../ui/spinner.js";
8
+ import { strings } from "../../ui/strings.js";
9
+ /**
10
+ * Run the inline AI explanation for audit issues.
11
+ */
12
+ export async function runInlineExplanation(issues, result, targetUrl, options, config) {
13
+ try {
14
+ const explainBase = resolveApiBase(options.base);
15
+ const explainKey = getApiKey(config.apiKey);
16
+ const explainIssues = issues.map((i) => ({
17
+ id: i.id || null,
18
+ title: i.title || i.description || null,
19
+ description: i.description || null,
20
+ severity: i.severity || null,
21
+ category: i.category || null,
22
+ selector: i.selector || null,
23
+ wcag_reference: i.wcag_reference || null,
24
+ recommendation: i.recommendation || i.recommended_fix || null,
25
+ }));
26
+ const explainPayload = {
27
+ job_id: result.job_id || null,
28
+ url: targetUrl || null,
29
+ scores: result.scores || null,
30
+ issues: explainIssues,
31
+ };
32
+ const explainSpinner = createSpinner(strings.explain.run.action);
33
+ const explainResponse = await apiRequest(explainBase, "/cli/ai/explain", { method: "POST", body: { audit: explainPayload } }, explainKey);
34
+ succeedSpinner(explainSpinner, strings.explain.run.done(explainResponse.data.issues.length));
35
+ writeOutput("\n");
36
+ writeOutput(bold("AI Explanation") + "\n");
37
+ writeOutput(dim("\u2500".repeat(40)) + "\n");
38
+ for (const bullet of explainResponse.data.summary) {
39
+ writeOutput(` ${colorize("*", brand.cyan)} ${bullet}\n`);
40
+ }
41
+ }
42
+ catch (explainErr) {
43
+ writeOutput(dim(`\n(${strings.explain.aiUnavailable(explainErr instanceof Error ? explainErr.message : String(explainErr))})\n`));
44
+ }
45
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Issue filtering helpers for the audit command.
3
+ */
4
+ import type { IssueLike } from "./types.js";
5
+ /**
6
+ * Normalize issues from various API response formats.
7
+ */
8
+ export declare function normalizeIssues(issues: unknown): IssueLike[];
9
+ /**
10
+ * Filter issues by severity.
11
+ */
12
+ export declare function filterBySeverity(issues: IssueLike[], severityFilter: string): IssueLike[];
13
+ /**
14
+ * Filter issues by category.
15
+ */
16
+ export declare function filterByCategory(issues: IssueLike[], categoryFilter: string): IssueLike[];
17
+ //# sourceMappingURL=filters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../../../src/commands/audit/filters.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,EAAE,CAS5D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EAAE,EACnB,cAAc,EAAE,MAAM,GACrB,SAAS,EAAE,CAab;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EAAE,EACnB,cAAc,EAAE,MAAM,GACrB,SAAS,EAAE,CASb"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Issue filtering helpers for the audit command.
3
+ */
4
+ /**
5
+ * Normalize issues from various API response formats.
6
+ */
7
+ export function normalizeIssues(issues) {
8
+ if (Array.isArray(issues))
9
+ return issues;
10
+ if (issues && typeof issues === "object") {
11
+ const values = Object.values(issues);
12
+ return values.flatMap((value) => Array.isArray(value) ? value : []);
13
+ }
14
+ return [];
15
+ }
16
+ /**
17
+ * Filter issues by severity.
18
+ */
19
+ export function filterBySeverity(issues, severityFilter) {
20
+ const allowed = new Set(severityFilter.split(",").map((s) => s.trim().toLowerCase()));
21
+ // Map common aliases
22
+ if (allowed.has("error"))
23
+ allowed.add("critical");
24
+ if (allowed.has("warning"))
25
+ allowed.add("serious");
26
+ return issues.filter((issue) => {
27
+ const sev = issue.severity?.toLowerCase() || "info";
28
+ return allowed.has(sev);
29
+ });
30
+ }
31
+ /**
32
+ * Filter issues by category.
33
+ */
34
+ export function filterByCategory(issues, categoryFilter) {
35
+ const allowed = new Set(categoryFilter.split(",").map((c) => c.trim().toLowerCase()));
36
+ return issues.filter((issue) => {
37
+ const cat = issue.category?.toLowerCase() || "";
38
+ return allowed.has(cat) || Array.from(allowed).some((a) => cat.includes(a));
39
+ });
40
+ }