@a11yst/cli 1.0.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 (165) hide show
  1. package/LICENSE +373 -0
  2. package/NOTICE.md +11 -0
  3. package/README.md +60 -0
  4. package/TRADEMARKS.md +52 -0
  5. package/dist/bin.d.ts +3 -0
  6. package/dist/bin.d.ts.map +1 -0
  7. package/dist/bin.js +9 -0
  8. package/dist/bin.js.map +1 -0
  9. package/dist/ci-policy-options.d.ts +15 -0
  10. package/dist/ci-policy-options.d.ts.map +1 -0
  11. package/dist/ci-policy-options.js +54 -0
  12. package/dist/ci-policy-options.js.map +1 -0
  13. package/dist/cli-progress.d.ts +16 -0
  14. package/dist/cli-progress.d.ts.map +1 -0
  15. package/dist/cli-progress.js +35 -0
  16. package/dist/cli-progress.js.map +1 -0
  17. package/dist/command-program.d.ts +7 -0
  18. package/dist/command-program.d.ts.map +1 -0
  19. package/dist/command-program.js +950 -0
  20. package/dist/command-program.js.map +1 -0
  21. package/dist/commands/audit-flow-output.d.ts +4 -0
  22. package/dist/commands/audit-flow-output.d.ts.map +1 -0
  23. package/dist/commands/audit-flow-output.js +119 -0
  24. package/dist/commands/audit-flow-output.js.map +1 -0
  25. package/dist/commands/audit-policy-output.d.ts +9 -0
  26. package/dist/commands/audit-policy-output.d.ts.map +1 -0
  27. package/dist/commands/audit-policy-output.js +95 -0
  28. package/dist/commands/audit-policy-output.js.map +1 -0
  29. package/dist/commands/audit.d.ts +15 -0
  30. package/dist/commands/audit.d.ts.map +1 -0
  31. package/dist/commands/audit.js +178 -0
  32. package/dist/commands/audit.js.map +1 -0
  33. package/dist/commands/baseline-config.d.ts +14 -0
  34. package/dist/commands/baseline-config.d.ts.map +1 -0
  35. package/dist/commands/baseline-config.js +33 -0
  36. package/dist/commands/baseline-config.js.map +1 -0
  37. package/dist/commands/baseline.d.ts +86 -0
  38. package/dist/commands/baseline.d.ts.map +1 -0
  39. package/dist/commands/baseline.js +392 -0
  40. package/dist/commands/baseline.js.map +1 -0
  41. package/dist/commands/classify.d.ts +30 -0
  42. package/dist/commands/classify.d.ts.map +1 -0
  43. package/dist/commands/classify.js +105 -0
  44. package/dist/commands/classify.js.map +1 -0
  45. package/dist/commands/detect.d.ts +12 -0
  46. package/dist/commands/detect.d.ts.map +1 -0
  47. package/dist/commands/detect.js +213 -0
  48. package/dist/commands/detect.js.map +1 -0
  49. package/dist/commands/doctor.d.ts +16 -0
  50. package/dist/commands/doctor.d.ts.map +1 -0
  51. package/dist/commands/doctor.js +404 -0
  52. package/dist/commands/doctor.js.map +1 -0
  53. package/dist/commands/findings.d.ts +45 -0
  54. package/dist/commands/findings.d.ts.map +1 -0
  55. package/dist/commands/findings.js +220 -0
  56. package/dist/commands/findings.js.map +1 -0
  57. package/dist/commands/flows.d.ts +30 -0
  58. package/dist/commands/flows.d.ts.map +1 -0
  59. package/dist/commands/flows.js +108 -0
  60. package/dist/commands/flows.js.map +1 -0
  61. package/dist/commands/init.d.ts +64 -0
  62. package/dist/commands/init.d.ts.map +1 -0
  63. package/dist/commands/init.js +293 -0
  64. package/dist/commands/init.js.map +1 -0
  65. package/dist/commands/profiles.d.ts +5 -0
  66. package/dist/commands/profiles.d.ts.map +1 -0
  67. package/dist/commands/profiles.js +39 -0
  68. package/dist/commands/profiles.js.map +1 -0
  69. package/dist/commands/report.d.ts +72 -0
  70. package/dist/commands/report.d.ts.map +1 -0
  71. package/dist/commands/report.js +284 -0
  72. package/dist/commands/report.js.map +1 -0
  73. package/dist/commands/results-loader.d.ts +15 -0
  74. package/dist/commands/results-loader.d.ts.map +1 -0
  75. package/dist/commands/results-loader.js +100 -0
  76. package/dist/commands/results-loader.js.map +1 -0
  77. package/dist/commands/routes.d.ts +35 -0
  78. package/dist/commands/routes.d.ts.map +1 -0
  79. package/dist/commands/routes.js +201 -0
  80. package/dist/commands/routes.js.map +1 -0
  81. package/dist/commands/unclassify.d.ts +22 -0
  82. package/dist/commands/unclassify.d.ts.map +1 -0
  83. package/dist/commands/unclassify.js +79 -0
  84. package/dist/commands/unclassify.js.map +1 -0
  85. package/dist/index.d.ts +11 -0
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.js +10 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/output.d.ts +9 -0
  90. package/dist/output.d.ts.map +1 -0
  91. package/dist/output.js +29 -0
  92. package/dist/output.js.map +1 -0
  93. package/dist/presentation/ansi.d.ts +12 -0
  94. package/dist/presentation/ansi.d.ts.map +1 -0
  95. package/dist/presentation/ansi.js +51 -0
  96. package/dist/presentation/ansi.js.map +1 -0
  97. package/dist/presentation/audit-presentation.d.ts +20 -0
  98. package/dist/presentation/audit-presentation.d.ts.map +1 -0
  99. package/dist/presentation/audit-presentation.js +409 -0
  100. package/dist/presentation/audit-presentation.js.map +1 -0
  101. package/dist/presentation/boundary.d.ts +5 -0
  102. package/dist/presentation/boundary.d.ts.map +1 -0
  103. package/dist/presentation/boundary.js +4 -0
  104. package/dist/presentation/boundary.js.map +1 -0
  105. package/dist/presentation/brand-header.d.ts +6 -0
  106. package/dist/presentation/brand-header.d.ts.map +1 -0
  107. package/dist/presentation/brand-header.js +18 -0
  108. package/dist/presentation/brand-header.js.map +1 -0
  109. package/dist/presentation/capabilities.d.ts +4 -0
  110. package/dist/presentation/capabilities.d.ts.map +1 -0
  111. package/dist/presentation/capabilities.js +25 -0
  112. package/dist/presentation/capabilities.js.map +1 -0
  113. package/dist/presentation/color.d.ts +13 -0
  114. package/dist/presentation/color.d.ts.map +1 -0
  115. package/dist/presentation/color.js +27 -0
  116. package/dist/presentation/color.js.map +1 -0
  117. package/dist/presentation/disclaimers.d.ts +3 -0
  118. package/dist/presentation/disclaimers.d.ts.map +1 -0
  119. package/dist/presentation/disclaimers.js +3 -0
  120. package/dist/presentation/disclaimers.js.map +1 -0
  121. package/dist/presentation/finding-groups.d.ts +14 -0
  122. package/dist/presentation/finding-groups.d.ts.map +1 -0
  123. package/dist/presentation/finding-groups.js +77 -0
  124. package/dist/presentation/finding-groups.js.map +1 -0
  125. package/dist/presentation/finding-source-report.d.ts +21 -0
  126. package/dist/presentation/finding-source-report.d.ts.map +1 -0
  127. package/dist/presentation/finding-source-report.js +134 -0
  128. package/dist/presentation/finding-source-report.js.map +1 -0
  129. package/dist/presentation/human-output.d.ts +4 -0
  130. package/dist/presentation/human-output.d.ts.map +1 -0
  131. package/dist/presentation/human-output.js +8 -0
  132. package/dist/presentation/human-output.js.map +1 -0
  133. package/dist/presentation/index.d.ts +15 -0
  134. package/dist/presentation/index.d.ts.map +1 -0
  135. package/dist/presentation/index.js +13 -0
  136. package/dist/presentation/index.js.map +1 -0
  137. package/dist/presentation/mode.d.ts +4 -0
  138. package/dist/presentation/mode.d.ts.map +1 -0
  139. package/dist/presentation/mode.js +8 -0
  140. package/dist/presentation/mode.js.map +1 -0
  141. package/dist/presentation/progress-mode.d.ts +20 -0
  142. package/dist/presentation/progress-mode.d.ts.map +1 -0
  143. package/dist/presentation/progress-mode.js +38 -0
  144. package/dist/presentation/progress-mode.js.map +1 -0
  145. package/dist/presentation/progress-reporter.d.ts +18 -0
  146. package/dist/presentation/progress-reporter.d.ts.map +1 -0
  147. package/dist/presentation/progress-reporter.js +236 -0
  148. package/dist/presentation/progress-reporter.js.map +1 -0
  149. package/dist/presentation/progress-signals.d.ts +2 -0
  150. package/dist/presentation/progress-signals.d.ts.map +1 -0
  151. package/dist/presentation/progress-signals.js +14 -0
  152. package/dist/presentation/progress-signals.js.map +1 -0
  153. package/dist/presentation/status.d.ts +11 -0
  154. package/dist/presentation/status.d.ts.map +1 -0
  155. package/dist/presentation/status.js +14 -0
  156. package/dist/presentation/status.js.map +1 -0
  157. package/dist/presentation/types.d.ts +23 -0
  158. package/dist/presentation/types.d.ts.map +1 -0
  159. package/dist/presentation/types.js +2 -0
  160. package/dist/presentation/types.js.map +1 -0
  161. package/dist/run-cli.d.ts +6 -0
  162. package/dist/run-cli.d.ts.map +1 -0
  163. package/dist/run-cli.js +15 -0
  164. package/dist/run-cli.js.map +1 -0
  165. package/package.json +69 -0
@@ -0,0 +1,950 @@
1
+ import { resolve } from "node:path";
2
+ import { Command } from "commander";
3
+ import { ConfigError, loadConfig } from "@a11yst/config";
4
+ import { getAuditExitCode, resolveCiPolicyConfig } from "@a11yst/policy";
5
+ import { productMetadata } from "@a11yst/types";
6
+ import { parseCiPolicyCliOptions } from "./ci-policy-options.js";
7
+ import { formatAuditHuman, formatAuditJson } from "./commands/audit.js";
8
+ import { createBaselineAfterAudit, formatBaselineCreateHuman, formatBaselineCreateJson, formatBaselineMigrateHuman, formatBaselineMigrateJson, formatBaselineStatusHuman, formatBaselineStatusJson, formatBaselineUpdateHuman, formatBaselineUpdateJson, runBaselineCreate, runBaselineMigrate, runBaselineStatus, runBaselineUpdate, } from "./commands/baseline.js";
9
+ import { formatClassifyHuman, formatClassifyJson, formatClassifyPreviewFromError, runClassify, } from "./commands/classify.js";
10
+ import { formatFindingsHuman, formatFindingsJson, parseFindingsDisposition, parseFindingsStatus, runFindings, } from "./commands/findings.js";
11
+ import { formatUnclassifyHuman, formatUnclassifyJson, formatUnclassifyPreviewFromError, runUnclassify, } from "./commands/unclassify.js";
12
+ import { formatReportHuman, formatReportJson, runReport, } from "./commands/report.js";
13
+ import { formatDetectHuman, formatDetectJson, runDetect, } from "./commands/detect.js";
14
+ import { formatDoctorHuman, formatDoctorJson, runDoctor, } from "./commands/doctor.js";
15
+ import { formatInitHuman, formatInitJson, parseInitFramework, parseInitPlatform, runInit } from "./commands/init.js";
16
+ import { formatFlowsHuman, formatFlowsJson, runFlows, } from "./commands/flows.js";
17
+ import { formatProfilesHuman, runProfiles, } from "./commands/profiles.js";
18
+ import { formatRoutesHuman, formatRoutesJson, runRoutes, } from "./commands/routes.js";
19
+ import { createCliProgressReporter } from "./cli-progress.js";
20
+ import { writeJson, writeStderr, writeStdout } from "./output.js";
21
+ import { AUDIT_HELP_DISCLAIMER, createBrandHeader, parseColorMode, prependHumanBrandHeader, resolveTerminalCapabilities, resolveTerminalPresentationMode, } from "./presentation/index.js";
22
+ function exitCodeFromDoctorStatus(status) {
23
+ if (status === "fail")
24
+ return 1;
25
+ return 0;
26
+ }
27
+ function exitCodeFromError(error) {
28
+ if (error &&
29
+ typeof error === "object" &&
30
+ "exitCode" in error &&
31
+ typeof error.exitCode === "number") {
32
+ return error.exitCode;
33
+ }
34
+ return 1;
35
+ }
36
+ function collectCheckpointNames(value, previous) {
37
+ return [...previous, value];
38
+ }
39
+ function collectRuleNames(value, previous) {
40
+ return [...previous, value];
41
+ }
42
+ function collectFindingStatus(value, previous) {
43
+ return [...previous, parseFindingsStatus(value)];
44
+ }
45
+ function collectFindingDisposition(value, previous) {
46
+ return [...previous, parseFindingsDisposition(value)];
47
+ }
48
+ /** Commander "collect" helper for repeatable `--project <name>` flags. */
49
+ function collectProjectNames(value, previous) {
50
+ return [...previous, value];
51
+ }
52
+ function collectProfileNames(value, previous) {
53
+ return [...previous, value];
54
+ }
55
+ function collectFlowNames(value, previous) {
56
+ return [...previous, value];
57
+ }
58
+ function parsePositiveInteger(value, optionName) {
59
+ if (!/^\d+$/.test(value)) {
60
+ throw new Error(`${optionName} must be a positive integer; received "${value}".`);
61
+ }
62
+ const parsed = Number(value);
63
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
64
+ throw new Error(`${optionName} must be a positive integer; received "${value}".`);
65
+ }
66
+ return parsed;
67
+ }
68
+ function readGlobalPresentationOptions(command) {
69
+ const root = command.parent && typeof command.parent.opts === "function" ? command.parent : command;
70
+ const opts = root.opts();
71
+ return {
72
+ progress: opts.progress,
73
+ noProgress: opts.noProgress,
74
+ };
75
+ }
76
+ function createCommandProgress(command, opts) {
77
+ const global = readGlobalPresentationOptions(command);
78
+ return createCliProgressReporter({
79
+ json: opts.json,
80
+ progress: global.progress,
81
+ noProgress: global.noProgress,
82
+ colorMode: parseColorMode(typeof opts.color === "string" ? opts.color : undefined),
83
+ });
84
+ }
85
+ export function createCliProgram(options = {}) {
86
+ const cwd = resolve(options.cwd ?? process.cwd());
87
+ const argv = options.argv ?? process.argv;
88
+ function resolveCwd(cwdFlag) {
89
+ return cwdFlag ? resolve(cwd, cwdFlag) : cwd;
90
+ }
91
+ const program = new Command();
92
+ program
93
+ .name(productMetadata.command)
94
+ .description(`${productMetadata.name} — ${productMetadata.tagline}`)
95
+ .version(productMetadata.version, "-V, --version", "Print the version number")
96
+ .helpOption("-h, --help", "Display help information")
97
+ .showHelpAfterError()
98
+ .configureOutput({
99
+ writeOut: (str) => process.stdout.write(str),
100
+ writeErr: (str) => process.stderr.write(str),
101
+ })
102
+ .option("--progress <mode>", "Progress feedback while long operations run (auto, always, never)", "auto")
103
+ .option("--no-progress", "Disable progress feedback");
104
+ program.addHelpText("before", () => `${createBrandHeader({ tagline: true })}\n\n`);
105
+ program
106
+ .command("detect")
107
+ .description("Detect project platform, framework, and package manager")
108
+ .option("--json", "Emit machine-readable JSON on stdout", false)
109
+ .option("--workspace", "Detect every project across a monorepo workspace", false)
110
+ .option("--cwd <path>", "Directory to run detection in")
111
+ .action(async function (opts) {
112
+ const progress = createCommandProgress(this, opts);
113
+ try {
114
+ progress.start("Scanning project…");
115
+ const result = await runDetect({
116
+ cwd: resolveCwd(opts.cwd),
117
+ workspace: opts.workspace,
118
+ });
119
+ progress.succeed("Scan complete");
120
+ if (opts.json) {
121
+ writeJson(formatDetectJson(result));
122
+ }
123
+ else {
124
+ writeStdout(formatDetectHuman(result));
125
+ }
126
+ process.exitCode = 0;
127
+ }
128
+ catch (error) {
129
+ progress.fail("Detection failed");
130
+ const message = error instanceof Error ? error.message : String(error);
131
+ if (opts.json) {
132
+ writeJson({ status: "error", message }, process.stdout);
133
+ }
134
+ writeStderr(message);
135
+ process.exitCode = 1;
136
+ }
137
+ finally {
138
+ progress.stop();
139
+ }
140
+ });
141
+ program
142
+ .command("init")
143
+ .description("Create a starter configuration file")
144
+ .option("--force", "Overwrite an existing configuration file", false)
145
+ .option("--platform <platform>", 'Target platform: "web". Detected when omitted.')
146
+ .option("--framework <framework>", "Framework hint (e.g. html, react, next). Detected when omitted.")
147
+ .option("--base-url <url>", "Base URL for web projects (e.g. http://localhost:3000)")
148
+ .option("--dev-command <command>", "Command used to start the development server")
149
+ .option("--cwd <path>", "Directory to initialize (defaults to the current working directory)")
150
+ .option("--json", "Emit machine-readable JSON on stdout", false)
151
+ .action(async function (opts) {
152
+ const progress = createCommandProgress(this, opts);
153
+ try {
154
+ progress.start("Initializing project…");
155
+ const platform = opts.platform !== undefined ? parseInitPlatform(opts.platform) : undefined;
156
+ const framework = opts.framework !== undefined ? parseInitFramework(opts.framework) : undefined;
157
+ const result = await runInit({
158
+ cwd: resolveCwd(opts.cwd),
159
+ force: opts.force,
160
+ platform,
161
+ framework,
162
+ baseUrl: opts.baseUrl,
163
+ devCommand: opts.devCommand,
164
+ json: opts.json,
165
+ });
166
+ progress.succeed("Initialization complete");
167
+ if (opts.json) {
168
+ writeJson(formatInitJson(result));
169
+ }
170
+ else {
171
+ writeStdout(prependHumanBrandHeader(formatInitHuman(result)));
172
+ }
173
+ process.exitCode = 0;
174
+ }
175
+ catch (error) {
176
+ progress.fail("Initialization failed");
177
+ const message = error instanceof Error ? error.message : String(error);
178
+ if (opts.json) {
179
+ writeJson({ status: "error", message }, process.stdout);
180
+ }
181
+ writeStderr(message);
182
+ process.exitCode = 1;
183
+ }
184
+ finally {
185
+ progress.stop();
186
+ }
187
+ });
188
+ program
189
+ .command("flows")
190
+ .description("List configured user flows, steps, and checkpoints without running a browser")
191
+ .option("--json", "Emit machine-readable JSON on stdout", false)
192
+ .option("--config <path>", "Path to a configuration file")
193
+ .option("--cwd <path>", "Directory to load configuration from")
194
+ .option("--project <name>", "Show flows for this project only (repeatable)", collectProjectNames, [])
195
+ .action(async (opts) => {
196
+ try {
197
+ const result = await runFlows({
198
+ cwd: resolveCwd(opts.cwd),
199
+ configPath: opts.config,
200
+ projectName: opts.project,
201
+ json: opts.json,
202
+ });
203
+ if (opts.json) {
204
+ writeJson(formatFlowsJson(result));
205
+ }
206
+ else {
207
+ writeStdout(formatFlowsHuman(result));
208
+ }
209
+ process.exitCode = 0;
210
+ }
211
+ catch (error) {
212
+ const message = error instanceof ConfigError
213
+ ? error.format()
214
+ : error instanceof Error
215
+ ? error.message
216
+ : String(error);
217
+ if (opts.json) {
218
+ writeJson({
219
+ status: "error",
220
+ message,
221
+ code: error instanceof ConfigError ? error.code : "FLOWS_FAILED",
222
+ issues: error instanceof ConfigError ? error.issues : undefined,
223
+ }, process.stdout);
224
+ }
225
+ writeStderr(message);
226
+ process.exitCode = 1;
227
+ }
228
+ });
229
+ program
230
+ .command("routes")
231
+ .description("Resolve configured routes via framework adapters")
232
+ .option("--json", "Emit machine-readable JSON on stdout", false)
233
+ .option("--config <path>", "Path to a configuration file")
234
+ .option("--cwd <path>", "Directory to load configuration from")
235
+ .option("--project <name>", "Resolve routes for this project only (repeatable)", collectProjectNames, [])
236
+ .option("--explain", "Show route discovery provenance and unresolved patterns", false)
237
+ .action(async function (opts) {
238
+ const progress = createCommandProgress(this, opts);
239
+ try {
240
+ progress.start("Discovering routes…");
241
+ const result = await runRoutes({
242
+ cwd: resolveCwd(opts.cwd),
243
+ configPath: opts.config,
244
+ projectName: opts.project,
245
+ json: opts.json,
246
+ explain: opts.explain,
247
+ });
248
+ const routeCount = result.projects.reduce((total, project) => total + project.routes.length, 0);
249
+ progress.succeed(`${routeCount} route${routeCount === 1 ? "" : "s"} resolved`);
250
+ if (opts.json) {
251
+ writeJson(formatRoutesJson(result));
252
+ }
253
+ else {
254
+ writeStdout(formatRoutesHuman(result, { explain: opts.explain }));
255
+ }
256
+ process.exitCode = 0;
257
+ }
258
+ catch (error) {
259
+ progress.fail("Route discovery failed");
260
+ const message = error instanceof ConfigError
261
+ ? error.format()
262
+ : error instanceof Error
263
+ ? error.message
264
+ : String(error);
265
+ if (opts.json) {
266
+ writeJson({
267
+ status: "error",
268
+ message,
269
+ code: error instanceof ConfigError ? error.code : "ROUTES_FAILED",
270
+ issues: error instanceof ConfigError ? error.issues : undefined,
271
+ }, process.stdout);
272
+ }
273
+ writeStderr(message);
274
+ process.exitCode = 1;
275
+ }
276
+ finally {
277
+ progress.stop();
278
+ }
279
+ });
280
+ program
281
+ .command("audit")
282
+ .description("Run an accessibility audit against your configured projects")
283
+ .option("--json", "Emit machine-readable JSON on stdout", false)
284
+ .option("--config <path>", "Path to a configuration file")
285
+ .option("--cwd <path>", "Directory to load configuration from")
286
+ .option("--headed", "Run with a visible browser window instead of headless", false)
287
+ .option("--timeout <ms>", "Navigation timeout in milliseconds", "30000")
288
+ .option("--no-start-server", "Never start a dev server; fail fast if nothing is already listening")
289
+ .option("--project <name>", "Audit only this project (repeatable)", collectProjectNames, [])
290
+ .option("--profile <id>", "Audit only this accessibility profile (repeatable)", collectProfileNames, [])
291
+ .option("--flow <id>", "Audit only this flow (repeatable)", collectFlowNames, [])
292
+ .option("--flows-only", "Audit only configured flows, not static routes", false)
293
+ .option("--routes-only", "Audit only static routes, not flows", false)
294
+ .option("--flow-timeout <ms>", "Flow step timeout in milliseconds", "10000")
295
+ .option("--output <path>", "Directory for audit results and reports")
296
+ .option("--no-html", "Do not generate an HTML report")
297
+ .option("--sarif", "Generate a SARIF 2.1.0 report in the audit bundle", false)
298
+ .option("--no-sarif", "Do not generate a SARIF report even when enabled in config")
299
+ .option("--sarif-output <path>", "Also write the SARIF report to this path")
300
+ .option("--junit", "Generate a JUnit XML report in the audit bundle", false)
301
+ .option("--no-junit", "Do not generate a JUnit report even when enabled in config")
302
+ .option("--junit-output <path>", "Also write the JUnit report to this path")
303
+ .option("--no-markdown", "Do not generate a Markdown report")
304
+ .option("--markdown-output <path>", "Also write the Markdown report to this path")
305
+ .option("--github-annotations", "Generate GitHub Actions workflow annotation commands in the audit bundle", false)
306
+ .option("--no-github-annotations", "Do not generate GitHub annotation commands even when enabled in config")
307
+ .option("--github-annotations-output <path>", "Also write GitHub annotation commands to this path")
308
+ .option("--github-step-summary", "Append the Markdown report to GITHUB_STEP_SUMMARY when set", false)
309
+ .option("--no-github-step-summary", "Do not write to GITHUB_STEP_SUMMARY even when enabled in config")
310
+ .option("--no-screenshots", "Do not capture screenshot evidence")
311
+ .option("--full-page-screenshots", "Capture full-page screenshots", false)
312
+ .option("--no-baseline", "Do not compare against a baseline file")
313
+ .option("--baseline <path>", "Compare against this baseline file")
314
+ .option("--create-baseline", "Create a baseline from this audit result", false)
315
+ .option("--force", "Overwrite an existing baseline when used with --create-baseline", false)
316
+ .option("--fail-on-new", "Fail CI policy when new findings meet the severity threshold", false)
317
+ .option("--no-fail-on-new", "Do not fail CI policy on new findings")
318
+ .option("--fail-on-regression", "Fail CI policy when regressed findings meet the severity threshold", false)
319
+ .option("--no-fail-on-regression", "Do not fail CI policy on regressions")
320
+ .option("--fail-on-expired-classification", "Fail CI policy when an accepted classification has expired", false)
321
+ .option("--no-fail-on-expired-classification", "Do not fail CI policy on expired classifications")
322
+ .option("--minimum-severity <severity>", "Minimum severity for CI policy breaches (minor, medium, high, critical)")
323
+ .option("--color <mode>", "Color output for human presentation (auto, always, never)", "auto")
324
+ .option("--verbose", "Include technical finding details in human output", false)
325
+ .addHelpText("after", `
326
+ CI policy flags override the ci section in your a11yst config. CLI values take
327
+ precedence over configuration. Setting --minimum-severity alone does not enable
328
+ the policy; at least one fail-on flag must be enabled.
329
+
330
+ Enabled CI policy requires baseline comparison. Use a baseline file or remove
331
+ --no-baseline. With --create-baseline, policy evaluation compares against any
332
+ baseline that existed before this run; a baseline created during the same run is
333
+ not used for policy evaluation.
334
+
335
+ Exit codes:
336
+ 0 Audit completed; CI policy disabled or passed
337
+ 1 Operational/config error, or CI policy could not be evaluated
338
+ 2 Audit completed but CI policy failed
339
+
340
+ false-positive and not-applicable classifications are excluded from policy
341
+ breaches. accepted-risk, third-party, and manual-review may still block when
342
+ new or regressed. A baseline records known debt; it does not make findings pass.
343
+
344
+ SARIF reports are disabled by default. Use --sarif to generate
345
+ reports/a11yst.sarif inside the audit bundle. Use --sarif-output to
346
+ also write an identical copy to a custom path.
347
+
348
+ JUnit reports are disabled by default. Use --junit to generate
349
+ reports/a11yst.junit.xml inside the audit bundle. Use --junit-output to
350
+ also write an identical copy to a custom path.
351
+
352
+ Markdown reports are generated by default in the audit bundle at
353
+ reports/a11yst.md. Use --no-markdown to skip generation. Use --markdown-output for an
354
+ external copy.
355
+
356
+ GitHub annotation commands are disabled by default. Use --github-annotations
357
+ to write reports/github-annotations.txt in the bundle. Pipe that file to
358
+ stdout in GitHub Actions; a11yst does not upload results automatically.
359
+
360
+ Use --github-step-summary to append the Markdown report to GITHUB_STEP_SUMMARY
361
+ when the environment variable is set in the runner.
362
+
363
+ ${AUDIT_HELP_DISCLAIMER}
364
+ `)
365
+ .action(async function (opts) {
366
+ const progress = createCommandProgress(this, opts);
367
+ const controller = new AbortController();
368
+ const onSignal = () => controller.abort();
369
+ process.once("SIGINT", onSignal);
370
+ process.once("SIGTERM", onSignal);
371
+ try {
372
+ const navigationTimeoutMs = parsePositiveInteger(opts.timeout ?? "30000", "--timeout");
373
+ const stepTimeoutMs = parsePositiveInteger(opts.flowTimeout ?? "10000", "--flow-timeout");
374
+ progress.start("Loading configuration…");
375
+ const config = await loadConfig({
376
+ cwd: resolveCwd(opts.cwd),
377
+ configPath: opts.config,
378
+ });
379
+ progress.succeed("Configuration loaded");
380
+ const { overrides: ciOverrides, explicitFlagsUsed } = parseCiPolicyCliOptions({
381
+ failOnNew: opts.failOnNew,
382
+ noFailOnNew: opts.noFailOnNew,
383
+ failOnRegression: opts.failOnRegression,
384
+ noFailOnRegression: opts.noFailOnRegression,
385
+ failOnExpiredClassification: opts.failOnExpiredClassification,
386
+ noFailOnExpiredClassification: opts.noFailOnExpiredClassification,
387
+ minimumSeverity: opts.minimumSeverity,
388
+ }, argv);
389
+ const resolvedCiPolicy = resolveCiPolicyConfig({
390
+ configPolicy: config.ci,
391
+ cliOverrides: ciOverrides,
392
+ });
393
+ const baselineOverride = typeof opts.baseline === "string" ? opts.baseline : undefined;
394
+ const noBaseline = opts.baseline === false;
395
+ const { executeAudit, resolveSarifReportOptions, resolveJunitReportOptions, resolveMarkdownReportOptions, resolveGitHubAnnotationsOptions, resolveGitHubStepSummaryOptions, } = await import("@a11yst/core");
396
+ const sarifOptions = resolveSarifReportOptions({
397
+ config: config.reports,
398
+ cli: {
399
+ sarif: opts.sarif || undefined,
400
+ noSarif: opts.noSarif || undefined,
401
+ sarifOutput: opts.sarifOutput,
402
+ },
403
+ });
404
+ const sarifExternalOutputPath = opts.sarifOutput
405
+ ? resolve(resolveCwd(opts.cwd), opts.sarifOutput)
406
+ : undefined;
407
+ const junitOptions = resolveJunitReportOptions({
408
+ config: config.reports,
409
+ cli: {
410
+ junit: opts.junit || undefined,
411
+ noJunit: opts.noJunit || undefined,
412
+ junitOutput: opts.junitOutput,
413
+ },
414
+ });
415
+ const junitExternalOutputPath = opts.junitOutput
416
+ ? resolve(resolveCwd(opts.cwd), opts.junitOutput)
417
+ : undefined;
418
+ const markdownOptions = resolveMarkdownReportOptions({
419
+ config: config.reports,
420
+ cli: {
421
+ markdown: opts.markdown === false ? false : undefined,
422
+ noMarkdown: opts.noMarkdown || undefined,
423
+ markdownOutput: opts.markdownOutput,
424
+ },
425
+ });
426
+ const markdownExternalOutputPath = opts.markdownOutput
427
+ ? resolve(resolveCwd(opts.cwd), opts.markdownOutput)
428
+ : undefined;
429
+ const githubAnnotationsOptions = resolveGitHubAnnotationsOptions({
430
+ config: config.reports,
431
+ cli: {
432
+ githubAnnotations: opts.githubAnnotations || undefined,
433
+ noGitHubAnnotations: opts.noGitHubAnnotations || undefined,
434
+ githubAnnotationsOutput: opts.githubAnnotationsOutput,
435
+ },
436
+ });
437
+ const githubAnnotationsExternalOutputPath = opts.githubAnnotationsOutput
438
+ ? resolve(resolveCwd(opts.cwd), opts.githubAnnotationsOutput)
439
+ : undefined;
440
+ const githubStepSummaryOptions = resolveGitHubStepSummaryOptions({
441
+ config: config.reports,
442
+ cli: {
443
+ githubStepSummary: opts.githubStepSummary || undefined,
444
+ noGitHubStepSummary: opts.noGitHubStepSummary || undefined,
445
+ },
446
+ });
447
+ const githubStepSummaryPath = process.env.GITHUB_STEP_SUMMARY?.trim() || undefined;
448
+ const result = await executeAudit(config, {
449
+ headed: opts.headed,
450
+ navigationTimeoutMs,
451
+ stepTimeoutMs,
452
+ noStartServer: opts.startServer === false,
453
+ projectNames: opts.project,
454
+ profileNames: opts.profile,
455
+ flowNames: opts.flow,
456
+ flowsOnly: opts.flowsOnly || undefined,
457
+ routesOnly: opts.routesOnly || undefined,
458
+ outputDir: opts.output,
459
+ html: opts.html === false ? false : undefined,
460
+ screenshots: opts.screenshots === false ? false : undefined,
461
+ fullPageScreenshots: opts.fullPageScreenshots || undefined,
462
+ signal: controller.signal,
463
+ noBaseline: noBaseline || undefined,
464
+ baselinePath: baselineOverride,
465
+ explicitBaselineRequired: Boolean(baselineOverride),
466
+ ciPolicy: resolvedCiPolicy,
467
+ sarif: sarifOptions,
468
+ sarifExternalOutputPath,
469
+ junit: junitOptions,
470
+ junitExternalOutputPath,
471
+ markdown: markdownOptions,
472
+ markdownExternalOutputPath,
473
+ githubAnnotations: githubAnnotationsOptions,
474
+ githubAnnotationsExternalOutputPath,
475
+ githubStepSummary: githubStepSummaryOptions.enabled,
476
+ githubStepSummaryPath,
477
+ progress,
478
+ });
479
+ if (opts.createBaseline) {
480
+ if (result.status === "failed") {
481
+ throw new Error("Cannot create a baseline from a failed audit.");
482
+ }
483
+ await createBaselineAfterAudit({
484
+ cwd: resolveCwd(opts.cwd),
485
+ configPath: opts.config,
486
+ result,
487
+ force: opts.force,
488
+ });
489
+ }
490
+ if (opts.json) {
491
+ writeJson(formatAuditJson(result));
492
+ }
493
+ else {
494
+ const capabilities = resolveTerminalCapabilities();
495
+ const body = formatAuditHuman(result, {
496
+ explicitCiFlagsUsed: explicitFlagsUsed,
497
+ minimumSeverity: resolvedCiPolicy.minimumSeverity,
498
+ sarifExternalPath: sarifExternalOutputPath,
499
+ junitExternalPath: junitExternalOutputPath,
500
+ markdownExternalPath: markdownExternalOutputPath,
501
+ githubAnnotationsExternalPath: githubAnnotationsExternalOutputPath,
502
+ githubStepSummaryWritten: result.githubStepSummaryWritten,
503
+ colorMode: parseColorMode(typeof opts.color === "string" ? opts.color : undefined),
504
+ capabilities,
505
+ presentationMode: resolveTerminalPresentationMode(capabilities),
506
+ verbose: opts.verbose || undefined,
507
+ });
508
+ writeStdout(prependHumanBrandHeader(body, capabilities));
509
+ }
510
+ process.exitCode = getAuditExitCode({
511
+ auditIncomplete: result.status !== "completed",
512
+ policyEvaluation: result.policyEvaluation,
513
+ });
514
+ }
515
+ catch (error) {
516
+ progress.fail("Audit failed");
517
+ if (opts.json) {
518
+ writeJson({
519
+ status: "error",
520
+ message: error instanceof ConfigError
521
+ ? error.message
522
+ : error instanceof Error
523
+ ? error.message
524
+ : String(error),
525
+ code: error instanceof ConfigError ? error.code : "AUDIT_FAILED",
526
+ issues: error instanceof ConfigError ? error.issues : undefined,
527
+ }, process.stdout);
528
+ }
529
+ writeStderr(error instanceof ConfigError
530
+ ? error.format()
531
+ : error instanceof Error
532
+ ? error.message
533
+ : String(error));
534
+ process.exitCode = 1;
535
+ }
536
+ finally {
537
+ progress.stop();
538
+ process.removeListener("SIGINT", onSignal);
539
+ process.removeListener("SIGTERM", onSignal);
540
+ }
541
+ });
542
+ program
543
+ .command("report [resultsPath]")
544
+ .description("Generate HTML, SARIF, JUnit, Markdown, or GitHub annotations reports from persisted audit results")
545
+ .option("--from <path>", "Path to a results.json file (alias for positional argument)")
546
+ .option("--format <format>", "Report format: html, sarif, junit, markdown, or github-annotations", "html")
547
+ .option("--output <path>", "Output directory for HTML or output file path for machine formats")
548
+ .option("--json", "Emit machine-readable JSON on stdout", false)
549
+ .option("--cwd <path>", "Directory used to resolve paths and configuration")
550
+ .addHelpText("after", `
551
+ When --format sarif is used, a11yst reads an existing results.json file,
552
+ generates SARIF 2.1.0 offline, and writes a11yst.sarif by default.
553
+
554
+ When --format junit is used, a11yst reads an existing results.json file,
555
+ generates JUnit XML offline, and writes a11yst.junit.xml by default.
556
+
557
+ When --format markdown is used, a11yst generates reports/a11yst.md offline.
558
+
559
+ When --format github-annotations is used, a11yst generates workflow commands
560
+ offline and writes github-annotations.txt by default.
561
+
562
+ No browser, server, baseline comparison, or policy re-evaluation runs.`)
563
+ .action(async function (resultsPath, opts) {
564
+ const progress = createCommandProgress(this, opts);
565
+ try {
566
+ progress.start("Regenerating report…");
567
+ const format = opts.format === "sarif"
568
+ ? "sarif"
569
+ : opts.format === "junit"
570
+ ? "junit"
571
+ : opts.format === "markdown"
572
+ ? "markdown"
573
+ : opts.format === "github-annotations"
574
+ ? "github-annotations"
575
+ : "html";
576
+ const result = await runReport({
577
+ cwd: resolveCwd(opts.cwd),
578
+ resultsPath: opts.from ?? resultsPath,
579
+ output: opts.output,
580
+ format,
581
+ });
582
+ progress.succeed("Report ready");
583
+ if (opts.json) {
584
+ writeJson(formatReportJson(result));
585
+ }
586
+ else {
587
+ writeStdout(formatReportHuman(result));
588
+ }
589
+ process.exitCode = 0;
590
+ }
591
+ catch (error) {
592
+ progress.fail("Report generation failed");
593
+ const message = error instanceof Error ? error.message : String(error);
594
+ if (opts.json) {
595
+ writeJson({ status: "error", message }, process.stdout);
596
+ }
597
+ writeStderr(message);
598
+ process.exitCode = 1;
599
+ }
600
+ finally {
601
+ progress.stop();
602
+ }
603
+ });
604
+ program
605
+ .command("profiles")
606
+ .description("List accessibility profiles, capabilities, and limitations")
607
+ .option("--json", "Emit machine-readable JSON on stdout", false)
608
+ .action((opts) => {
609
+ const profiles = runProfiles();
610
+ if (opts.json) {
611
+ writeJson({ profiles });
612
+ }
613
+ else {
614
+ writeStdout(formatProfilesHuman(profiles));
615
+ }
616
+ process.exitCode = 0;
617
+ });
618
+ program
619
+ .command("doctor")
620
+ .description("Check environment readiness for a11yst")
621
+ .option("--json", "Emit machine-readable JSON on stdout", false)
622
+ .option("--cwd <path>", "Directory to check")
623
+ .action(async function (opts) {
624
+ const progress = createCommandProgress(this, opts);
625
+ try {
626
+ progress.start("Running diagnostics…");
627
+ const report = await runDoctor(resolveCwd(opts.cwd));
628
+ progress.succeed("Diagnostics complete");
629
+ if (opts.json) {
630
+ writeJson(formatDoctorJson(report));
631
+ }
632
+ else {
633
+ writeStdout(prependHumanBrandHeader(formatDoctorHuman(report)));
634
+ }
635
+ process.exitCode = exitCodeFromDoctorStatus(report.status);
636
+ }
637
+ catch (error) {
638
+ progress.fail("Diagnostics failed");
639
+ writeStderr(error instanceof Error ? error.message : String(error));
640
+ process.exitCode = 1;
641
+ }
642
+ finally {
643
+ progress.stop();
644
+ }
645
+ });
646
+ program
647
+ .command("baseline")
648
+ .description("Manage versioned accessibility baselines")
649
+ .addCommand(new Command("create")
650
+ .description("Create a baseline from audit results")
651
+ .option("--from <path>", "Results JSON path (defaults to latest results)")
652
+ .option("--force", "Overwrite an existing baseline file", false)
653
+ .option("--json", "Emit machine-readable JSON on stdout", false)
654
+ .option("--config <path>", "Path to a configuration file")
655
+ .option("--cwd <path>", "Directory to load configuration from")
656
+ .action(async (opts) => {
657
+ try {
658
+ const result = await runBaselineCreate({
659
+ cwd: resolveCwd(opts.cwd),
660
+ configPath: opts.config,
661
+ from: opts.from,
662
+ force: opts.force,
663
+ });
664
+ if (opts.json) {
665
+ writeJson(formatBaselineCreateJson(result));
666
+ }
667
+ else {
668
+ writeStdout(formatBaselineCreateHuman(result));
669
+ }
670
+ process.exitCode = 0;
671
+ }
672
+ catch (error) {
673
+ const message = error instanceof Error ? error.message : String(error);
674
+ if (opts.json) {
675
+ writeJson({ status: "error", message }, process.stdout);
676
+ }
677
+ writeStderr(message);
678
+ process.exitCode = exitCodeFromError(error);
679
+ }
680
+ }))
681
+ .addCommand(new Command("status")
682
+ .description("Show baseline file status and latest comparison")
683
+ .option("--json", "Emit machine-readable JSON on stdout", false)
684
+ .option("--config <path>", "Path to a configuration file")
685
+ .option("--cwd <path>", "Directory to load configuration from")
686
+ .option("--baseline <path>", "Baseline file path override")
687
+ .action(async (opts) => {
688
+ try {
689
+ const result = await runBaselineStatus({
690
+ cwd: resolveCwd(opts.cwd),
691
+ configPath: opts.config,
692
+ baselineOverride: opts.baseline,
693
+ });
694
+ if (opts.json) {
695
+ writeJson(formatBaselineStatusJson(result));
696
+ }
697
+ else {
698
+ writeStdout(formatBaselineStatusHuman(result));
699
+ }
700
+ process.exitCode = 0;
701
+ }
702
+ catch (error) {
703
+ const message = error instanceof Error ? error.message : String(error);
704
+ if (opts.json) {
705
+ writeJson({ status: "error", message }, process.stdout);
706
+ }
707
+ writeStderr(message);
708
+ process.exitCode = 1;
709
+ }
710
+ }))
711
+ .addCommand(new Command("update")
712
+ .description("Preview or apply baseline updates from audit results")
713
+ .option("--from <path>", "Results JSON path (defaults to latest results)")
714
+ .option("--dry-run", "Preview changes without writing", false)
715
+ .option("--accept-new", "Add new findings to the baseline", false)
716
+ .option("--remove-resolved", "Remove resolved findings from the baseline", false)
717
+ .option("--yes", "Apply changes without interactive confirmation", false)
718
+ .option("--json", "Emit machine-readable JSON on stdout", false)
719
+ .option("--config <path>", "Path to a configuration file")
720
+ .option("--cwd <path>", "Directory to load configuration from")
721
+ .action(async (opts) => {
722
+ try {
723
+ const result = await runBaselineUpdate({
724
+ cwd: resolveCwd(opts.cwd),
725
+ configPath: opts.config,
726
+ from: opts.from,
727
+ dryRun: opts.dryRun,
728
+ acceptNew: opts.acceptNew,
729
+ removeResolved: opts.removeResolved,
730
+ yes: opts.yes,
731
+ });
732
+ if (opts.json) {
733
+ writeJson(formatBaselineUpdateJson(result));
734
+ }
735
+ else {
736
+ writeStdout(formatBaselineUpdateHuman(result));
737
+ }
738
+ process.exitCode = 0;
739
+ }
740
+ catch (error) {
741
+ const pending = error;
742
+ if (pending.preview) {
743
+ if (opts.json) {
744
+ writeJson(formatBaselineUpdateJson(pending.preview));
745
+ }
746
+ else {
747
+ writeStdout(formatBaselineUpdateHuman(pending.preview));
748
+ }
749
+ writeStderr(error instanceof Error ? error.message : String(error));
750
+ process.exitCode = 2;
751
+ return;
752
+ }
753
+ const message = error instanceof Error ? error.message : String(error);
754
+ if (opts.json) {
755
+ writeJson({ status: "error", message }, process.stdout);
756
+ }
757
+ writeStderr(message);
758
+ process.exitCode = exitCodeFromError(error);
759
+ }
760
+ }))
761
+ .addCommand(new Command("migrate")
762
+ .description("Migrate a baseline file to the current schema version")
763
+ .option("--dry-run", "Preview migration without writing", false)
764
+ .option("--yes", "Apply migration without interactive confirmation", false)
765
+ .option("--json", "Emit machine-readable JSON on stdout", false)
766
+ .option("--config <path>", "Path to a configuration file")
767
+ .option("--cwd <path>", "Directory to load configuration from")
768
+ .option("--baseline <path>", "Baseline file path override")
769
+ .action(async (opts) => {
770
+ try {
771
+ const result = await runBaselineMigrate({
772
+ cwd: resolveCwd(opts.cwd),
773
+ configPath: opts.config,
774
+ baselineOverride: opts.baseline,
775
+ dryRun: opts.dryRun,
776
+ yes: opts.yes,
777
+ });
778
+ if (opts.json) {
779
+ writeJson(formatBaselineMigrateJson(result));
780
+ }
781
+ else {
782
+ writeStdout(formatBaselineMigrateHuman(result));
783
+ }
784
+ process.exitCode = 0;
785
+ }
786
+ catch (error) {
787
+ const message = error instanceof Error ? error.message : String(error);
788
+ if (opts.json) {
789
+ writeJson({ status: "error", message }, process.stdout);
790
+ }
791
+ writeStderr(message);
792
+ process.exitCode = exitCodeFromError(error);
793
+ }
794
+ }));
795
+ program
796
+ .command("findings")
797
+ .description("List findings from the latest audit results or a results file")
798
+ .option("--json", "Emit machine-readable JSON on stdout", false)
799
+ .option("--from <path>", "Results JSON path (defaults to latest results)")
800
+ .option("--config <path>", "Path to a configuration file")
801
+ .option("--cwd <path>", "Directory to load configuration from")
802
+ .option("--status <status>", "Filter by lifecycle status (repeatable)", collectFindingStatus, [])
803
+ .option("--disposition <disposition>", "Filter by classification disposition (repeatable)", collectFindingDisposition, [])
804
+ .option("--project <name>", "Filter by project (repeatable)", collectProjectNames, [])
805
+ .option("--rule <ruleId>", "Filter by rule id (repeatable)", collectRuleNames, [])
806
+ .option("--profile <id>", "Filter by accessibility profile (repeatable)", collectProfileNames, [])
807
+ .option("--flow <id>", "Filter by flow id (repeatable)", collectFlowNames, [])
808
+ .option("--checkpoint <id>", "Filter by checkpoint id (repeatable)", collectCheckpointNames, [])
809
+ .action(async (opts) => {
810
+ try {
811
+ const result = await runFindings({
812
+ cwd: resolveCwd(opts.cwd),
813
+ configPath: opts.config,
814
+ from: opts.from,
815
+ filters: {
816
+ status: opts.status,
817
+ disposition: opts.disposition,
818
+ project: opts.project,
819
+ rule: opts.rule,
820
+ profile: opts.profile,
821
+ flow: opts.flow,
822
+ checkpoint: opts.checkpoint,
823
+ },
824
+ });
825
+ if (opts.json) {
826
+ writeJson(formatFindingsJson(result));
827
+ }
828
+ else {
829
+ writeStdout(formatFindingsHuman(result));
830
+ }
831
+ process.exitCode = 0;
832
+ }
833
+ catch (error) {
834
+ const message = error instanceof Error ? error.message : String(error);
835
+ if (opts.json) {
836
+ writeJson({ status: "error", message }, process.stdout);
837
+ }
838
+ writeStderr(message);
839
+ process.exitCode = 1;
840
+ }
841
+ });
842
+ program
843
+ .command("classify <finding-id>")
844
+ .description("Classify a finding in the baseline")
845
+ .requiredOption("--disposition <disposition>", "Classification disposition")
846
+ .requiredOption("--reason <text>", "Classification reason")
847
+ .option("--owner <name>", "Classification owner")
848
+ .option("--ticket <id>", "Tracking ticket id")
849
+ .option("--expires <date>", "Expiry date (YYYY-MM-DD)")
850
+ .option("--review <date>", "Review date (YYYY-MM-DD)")
851
+ .option("--notes <text>", "Additional notes")
852
+ .option("--from <path>", "Results JSON path (defaults to latest results)")
853
+ .option("--yes", "Apply without interactive confirmation", false)
854
+ .option("--json", "Emit machine-readable JSON on stdout", false)
855
+ .option("--config <path>", "Path to a configuration file")
856
+ .option("--cwd <path>", "Directory to load configuration from")
857
+ .action(async (findingId, opts) => {
858
+ try {
859
+ const result = await runClassify({
860
+ cwd: resolveCwd(opts.cwd),
861
+ configPath: opts.config,
862
+ findingId,
863
+ from: opts.from,
864
+ disposition: parseFindingsDisposition(opts.disposition),
865
+ reason: opts.reason,
866
+ owner: opts.owner,
867
+ ticket: opts.ticket,
868
+ expires: opts.expires,
869
+ review: opts.review,
870
+ notes: opts.notes,
871
+ yes: opts.yes,
872
+ });
873
+ if (opts.json) {
874
+ writeJson(formatClassifyJson(result));
875
+ }
876
+ else {
877
+ writeStdout(formatClassifyHuman(result));
878
+ }
879
+ process.exitCode = 0;
880
+ }
881
+ catch (error) {
882
+ const preview = formatClassifyPreviewFromError(error);
883
+ if (preview) {
884
+ if (opts.json) {
885
+ writeJson(formatClassifyJson(preview));
886
+ }
887
+ else {
888
+ writeStdout(formatClassifyHuman(preview));
889
+ }
890
+ writeStderr(error instanceof Error ? error.message : String(error));
891
+ process.exitCode = 2;
892
+ return;
893
+ }
894
+ const message = error instanceof Error ? error.message : String(error);
895
+ if (opts.json) {
896
+ writeJson({ status: "error", message }, process.stdout);
897
+ }
898
+ writeStderr(message);
899
+ process.exitCode = 1;
900
+ }
901
+ });
902
+ program
903
+ .command("unclassify <finding-id>")
904
+ .description("Remove a finding classification from the baseline")
905
+ .option("--from <path>", "Results JSON path (defaults to latest results)")
906
+ .option("--yes", "Apply without interactive confirmation", false)
907
+ .option("--json", "Emit machine-readable JSON on stdout", false)
908
+ .option("--config <path>", "Path to a configuration file")
909
+ .option("--cwd <path>", "Directory to load configuration from")
910
+ .action(async (findingId, opts) => {
911
+ try {
912
+ const result = await runUnclassify({
913
+ cwd: resolveCwd(opts.cwd),
914
+ configPath: opts.config,
915
+ findingId,
916
+ from: opts.from,
917
+ yes: opts.yes,
918
+ });
919
+ if (opts.json) {
920
+ writeJson(formatUnclassifyJson(result));
921
+ }
922
+ else {
923
+ writeStdout(formatUnclassifyHuman(result));
924
+ }
925
+ process.exitCode = 0;
926
+ }
927
+ catch (error) {
928
+ const preview = formatUnclassifyPreviewFromError(error);
929
+ if (preview) {
930
+ if (opts.json) {
931
+ writeJson(formatUnclassifyJson(preview));
932
+ }
933
+ else {
934
+ writeStdout(formatUnclassifyHuman(preview));
935
+ }
936
+ writeStderr(error instanceof Error ? error.message : String(error));
937
+ process.exitCode = 2;
938
+ return;
939
+ }
940
+ const message = error instanceof Error ? error.message : String(error);
941
+ if (opts.json) {
942
+ writeJson({ status: "error", message }, process.stdout);
943
+ }
944
+ writeStderr(message);
945
+ process.exitCode = 1;
946
+ }
947
+ });
948
+ return program;
949
+ }
950
+ //# sourceMappingURL=command-program.js.map