@vertaaux/cli 0.2.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 (198) hide show
  1. package/README.md +345 -0
  2. package/dist/auth/ci-token.d.ts +49 -0
  3. package/dist/auth/ci-token.d.ts.map +1 -0
  4. package/dist/auth/ci-token.js +83 -0
  5. package/dist/auth/device-flow.d.ts +66 -0
  6. package/dist/auth/device-flow.d.ts.map +1 -0
  7. package/dist/auth/device-flow.js +156 -0
  8. package/dist/auth/token-store.d.ts +53 -0
  9. package/dist/auth/token-store.d.ts.map +1 -0
  10. package/dist/auth/token-store.js +78 -0
  11. package/dist/baseline/diff.d.ts +57 -0
  12. package/dist/baseline/diff.d.ts.map +1 -0
  13. package/dist/baseline/diff.js +152 -0
  14. package/dist/baseline/hash.d.ts +54 -0
  15. package/dist/baseline/hash.d.ts.map +1 -0
  16. package/dist/baseline/hash.js +66 -0
  17. package/dist/baseline/manager.d.ts +89 -0
  18. package/dist/baseline/manager.d.ts.map +1 -0
  19. package/dist/baseline/manager.js +157 -0
  20. package/dist/cache/index.d.ts +8 -0
  21. package/dist/cache/index.d.ts.map +1 -0
  22. package/dist/cache/index.js +7 -0
  23. package/dist/cache/route-cache.d.ts +119 -0
  24. package/dist/cache/route-cache.d.ts.map +1 -0
  25. package/dist/cache/route-cache.js +213 -0
  26. package/dist/ci/changed-routes.d.ts +95 -0
  27. package/dist/ci/changed-routes.d.ts.map +1 -0
  28. package/dist/ci/changed-routes.js +304 -0
  29. package/dist/ci/github-api.d.ts +68 -0
  30. package/dist/ci/github-api.d.ts.map +1 -0
  31. package/dist/ci/github-api.js +138 -0
  32. package/dist/ci/gitlab-api.d.ts +75 -0
  33. package/dist/ci/gitlab-api.d.ts.map +1 -0
  34. package/dist/ci/gitlab-api.js +180 -0
  35. package/dist/ci/index.d.ts +6 -0
  36. package/dist/ci/index.d.ts.map +1 -0
  37. package/dist/ci/index.js +4 -0
  38. package/dist/commands/audit.d.ts +58 -0
  39. package/dist/commands/audit.d.ts.map +1 -0
  40. package/dist/commands/audit.js +862 -0
  41. package/dist/commands/baseline.d.ts +22 -0
  42. package/dist/commands/baseline.d.ts.map +1 -0
  43. package/dist/commands/baseline.js +210 -0
  44. package/dist/commands/comment.d.ts +14 -0
  45. package/dist/commands/comment.d.ts.map +1 -0
  46. package/dist/commands/comment.js +363 -0
  47. package/dist/commands/diff.d.ts +24 -0
  48. package/dist/commands/diff.d.ts.map +1 -0
  49. package/dist/commands/diff.js +196 -0
  50. package/dist/commands/doctor.d.ts +58 -0
  51. package/dist/commands/doctor.d.ts.map +1 -0
  52. package/dist/commands/doctor.js +338 -0
  53. package/dist/commands/download.d.ts +12 -0
  54. package/dist/commands/download.d.ts.map +1 -0
  55. package/dist/commands/download.js +183 -0
  56. package/dist/commands/explain.d.ts +62 -0
  57. package/dist/commands/explain.d.ts.map +1 -0
  58. package/dist/commands/explain.js +302 -0
  59. package/dist/commands/init.d.ts +12 -0
  60. package/dist/commands/init.d.ts.map +1 -0
  61. package/dist/commands/init.js +212 -0
  62. package/dist/commands/login.d.ts +14 -0
  63. package/dist/commands/login.d.ts.map +1 -0
  64. package/dist/commands/login.js +222 -0
  65. package/dist/commands/policy.d.ts +13 -0
  66. package/dist/commands/policy.d.ts.map +1 -0
  67. package/dist/commands/policy.js +347 -0
  68. package/dist/commands/upload.d.ts +12 -0
  69. package/dist/commands/upload.d.ts.map +1 -0
  70. package/dist/commands/upload.js +158 -0
  71. package/dist/config/defaults.d.ts +21 -0
  72. package/dist/config/defaults.d.ts.map +1 -0
  73. package/dist/config/defaults.js +49 -0
  74. package/dist/config/loader.d.ts +66 -0
  75. package/dist/config/loader.d.ts.map +1 -0
  76. package/dist/config/loader.js +167 -0
  77. package/dist/config/schema.d.ts +55 -0
  78. package/dist/config/schema.d.ts.map +1 -0
  79. package/dist/config/schema.js +6 -0
  80. package/dist/index.d.ts +9 -0
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/index.js +1090 -0
  83. package/dist/interactive/fix-wizard.d.ts +44 -0
  84. package/dist/interactive/fix-wizard.d.ts.map +1 -0
  85. package/dist/interactive/fix-wizard.js +286 -0
  86. package/dist/interactive/init-wizard.d.ts +32 -0
  87. package/dist/interactive/init-wizard.d.ts.map +1 -0
  88. package/dist/interactive/init-wizard.js +193 -0
  89. package/dist/interactive/prompts.d.ts +62 -0
  90. package/dist/interactive/prompts.d.ts.map +1 -0
  91. package/dist/interactive/prompts.js +78 -0
  92. package/dist/monorepo/detector.d.ts +70 -0
  93. package/dist/monorepo/detector.d.ts.map +1 -0
  94. package/dist/monorepo/detector.js +278 -0
  95. package/dist/monorepo/index.d.ts +9 -0
  96. package/dist/monorepo/index.d.ts.map +1 -0
  97. package/dist/monorepo/index.js +8 -0
  98. package/dist/monorepo/workspace.d.ts +142 -0
  99. package/dist/monorepo/workspace.d.ts.map +1 -0
  100. package/dist/monorepo/workspace.js +171 -0
  101. package/dist/output/envelope.d.ts +21 -0
  102. package/dist/output/envelope.d.ts.map +1 -0
  103. package/dist/output/envelope.js +27 -0
  104. package/dist/output/factory.d.ts +73 -0
  105. package/dist/output/factory.d.ts.map +1 -0
  106. package/dist/output/factory.js +60 -0
  107. package/dist/output/formats.d.ts +11 -0
  108. package/dist/output/formats.d.ts.map +1 -0
  109. package/dist/output/formats.js +41 -0
  110. package/dist/output/html.d.ts +45 -0
  111. package/dist/output/html.d.ts.map +1 -0
  112. package/dist/output/html.js +607 -0
  113. package/dist/output/human.d.ts +41 -0
  114. package/dist/output/human.d.ts.map +1 -0
  115. package/dist/output/human.js +274 -0
  116. package/dist/output/json.d.ts +42 -0
  117. package/dist/output/json.d.ts.map +1 -0
  118. package/dist/output/json.js +37 -0
  119. package/dist/output/junit.d.ts +56 -0
  120. package/dist/output/junit.d.ts.map +1 -0
  121. package/dist/output/junit.js +135 -0
  122. package/dist/output/markdown.d.ts +77 -0
  123. package/dist/output/markdown.d.ts.map +1 -0
  124. package/dist/output/markdown.js +411 -0
  125. package/dist/output/sarif.d.ts +160 -0
  126. package/dist/output/sarif.d.ts.map +1 -0
  127. package/dist/output/sarif.js +207 -0
  128. package/dist/policy/evaluator.d.ts +111 -0
  129. package/dist/policy/evaluator.d.ts.map +1 -0
  130. package/dist/policy/evaluator.js +362 -0
  131. package/dist/policy/index.d.ts +15 -0
  132. package/dist/policy/index.d.ts.map +1 -0
  133. package/dist/policy/index.js +11 -0
  134. package/dist/policy/loader.d.ts +97 -0
  135. package/dist/policy/loader.d.ts.map +1 -0
  136. package/dist/policy/loader.js +281 -0
  137. package/dist/policy/schema.d.ts +297 -0
  138. package/dist/policy/schema.d.ts.map +1 -0
  139. package/dist/policy/schema.js +230 -0
  140. package/dist/quality-gate/evaluator.d.ts +58 -0
  141. package/dist/quality-gate/evaluator.d.ts.map +1 -0
  142. package/dist/quality-gate/evaluator.js +274 -0
  143. package/dist/quality-gate/index.d.ts +10 -0
  144. package/dist/quality-gate/index.d.ts.map +1 -0
  145. package/dist/quality-gate/index.js +7 -0
  146. package/dist/quality-gate/types.d.ts +103 -0
  147. package/dist/quality-gate/types.d.ts.map +1 -0
  148. package/dist/quality-gate/types.js +23 -0
  149. package/dist/templates/azure-devops.d.ts +25 -0
  150. package/dist/templates/azure-devops.d.ts.map +1 -0
  151. package/dist/templates/azure-devops.js +109 -0
  152. package/dist/templates/circleci.d.ts +28 -0
  153. package/dist/templates/circleci.d.ts.map +1 -0
  154. package/dist/templates/circleci.js +86 -0
  155. package/dist/templates/github-actions.d.ts +81 -0
  156. package/dist/templates/github-actions.d.ts.map +1 -0
  157. package/dist/templates/github-actions.js +393 -0
  158. package/dist/templates/gitlab-ci.d.ts +26 -0
  159. package/dist/templates/gitlab-ci.d.ts.map +1 -0
  160. package/dist/templates/gitlab-ci.js +70 -0
  161. package/dist/templates/index.d.ts +72 -0
  162. package/dist/templates/index.d.ts.map +1 -0
  163. package/dist/templates/index.js +112 -0
  164. package/dist/templates/jenkins.d.ts +26 -0
  165. package/dist/templates/jenkins.d.ts.map +1 -0
  166. package/dist/templates/jenkins.js +110 -0
  167. package/dist/ui/banner.d.ts +31 -0
  168. package/dist/ui/banner.d.ts.map +1 -0
  169. package/dist/ui/banner.js +84 -0
  170. package/dist/ui/diagnostics.d.ts +39 -0
  171. package/dist/ui/diagnostics.d.ts.map +1 -0
  172. package/dist/ui/diagnostics.js +153 -0
  173. package/dist/ui/spinner.d.ts +61 -0
  174. package/dist/ui/spinner.d.ts.map +1 -0
  175. package/dist/ui/spinner.js +101 -0
  176. package/dist/ui/table.d.ts +63 -0
  177. package/dist/ui/table.d.ts.map +1 -0
  178. package/dist/ui/table.js +236 -0
  179. package/dist/utils/client.d.ts +82 -0
  180. package/dist/utils/client.d.ts.map +1 -0
  181. package/dist/utils/client.js +128 -0
  182. package/dist/utils/detect-env.d.ts +59 -0
  183. package/dist/utils/detect-env.d.ts.map +1 -0
  184. package/dist/utils/detect-env.js +115 -0
  185. package/dist/utils/exit-codes.d.ts +47 -0
  186. package/dist/utils/exit-codes.d.ts.map +1 -0
  187. package/dist/utils/exit-codes.js +61 -0
  188. package/dist/utils/logger.d.ts +87 -0
  189. package/dist/utils/logger.d.ts.map +1 -0
  190. package/dist/utils/logger.js +185 -0
  191. package/dist/utils/sanitize.d.ts +36 -0
  192. package/dist/utils/sanitize.d.ts.map +1 -0
  193. package/dist/utils/sanitize.js +64 -0
  194. package/dist/utils/validators.d.ts +41 -0
  195. package/dist/utils/validators.d.ts.map +1 -0
  196. package/dist/utils/validators.js +123 -0
  197. package/package.json +63 -0
  198. package/schemas/vertaaux.config.schema.json +103 -0
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Baseline command for VertaaUX CLI.
3
+ *
4
+ * Manages baseline files for tracking technical debt.
5
+ * - Create baseline from audit results
6
+ * - Add individual issues to baseline (ignore)
7
+ * - List baseline summary
8
+ */
9
+ import type { Command } from "commander";
10
+ export interface BaselineCommandOptions {
11
+ path?: string;
12
+ fromFile?: string;
13
+ ignore?: string;
14
+ reason?: string;
15
+ list?: boolean;
16
+ base?: string;
17
+ }
18
+ /**
19
+ * Register the baseline command with the Commander program.
20
+ */
21
+ export declare function registerBaselineCommand(program: Command): void;
22
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/commands/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoIzC,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA8H9D"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Baseline command for VertaaUX CLI.
3
+ *
4
+ * Manages baseline files for tracking technical debt.
5
+ * - Create baseline from audit results
6
+ * - Add individual issues to baseline (ignore)
7
+ * - List baseline summary
8
+ */
9
+ import { readFile } from "fs/promises";
10
+ import { existsSync } from "fs";
11
+ import { ExitCode } from "../utils/exit-codes.js";
12
+ import { loadBaseline, saveBaseline, createBaseline, addToBaseline, DEFAULT_BASELINE_PATH, } from "../baseline/manager.js";
13
+ import { generateFingerprint } from "../baseline/hash.js";
14
+ const DEFAULT_API_BASE = "https://vertaaux.ai/v1";
15
+ /**
16
+ * Get API base URL from environment.
17
+ */
18
+ function getApiBase() {
19
+ return (process.env.VERTAAUX_API_BASE || DEFAULT_API_BASE).replace(/\/$/, "");
20
+ }
21
+ /**
22
+ * Get API key from environment.
23
+ */
24
+ function getApiKey() {
25
+ const key = process.env.VERTAAUX_API_KEY;
26
+ if (!key) {
27
+ throw new Error("VERTAAUX_API_KEY is required");
28
+ }
29
+ return key;
30
+ }
31
+ /**
32
+ * Fetch audit results from API.
33
+ */
34
+ async function fetchAudit(jobId) {
35
+ const base = getApiBase();
36
+ const url = `${base}/audit/${jobId}`;
37
+ const res = await fetch(url, {
38
+ method: "GET",
39
+ headers: {
40
+ "Content-Type": "application/json",
41
+ "X-API-Key": getApiKey(),
42
+ },
43
+ });
44
+ if (!res.ok) {
45
+ let detail = res.statusText;
46
+ try {
47
+ const data = await res.json();
48
+ detail = data.error || data.message || detail;
49
+ }
50
+ catch {
51
+ // ignore
52
+ }
53
+ throw new Error(`HTTP ${res.status}: ${detail}`);
54
+ }
55
+ return (await res.json());
56
+ }
57
+ /**
58
+ * Normalize issues from various API response formats.
59
+ */
60
+ function normalizeIssues(issues) {
61
+ if (Array.isArray(issues))
62
+ return issues;
63
+ if (issues && typeof issues === "object") {
64
+ const values = Object.values(issues);
65
+ return values.flatMap((value) => Array.isArray(value) ? value : []);
66
+ }
67
+ return [];
68
+ }
69
+ /**
70
+ * Find an issue by ID from audit results.
71
+ */
72
+ function findIssueById(issues, id) {
73
+ // Try exact match first
74
+ const exact = issues.find((issue) => issue.id === id || issue.ruleId === id || issue.rule_id === id);
75
+ if (exact)
76
+ return exact;
77
+ // Try fingerprint match
78
+ for (const issue of issues) {
79
+ const fp = generateFingerprint(issue);
80
+ if (fp === id || fp.startsWith(id)) {
81
+ return issue;
82
+ }
83
+ }
84
+ return undefined;
85
+ }
86
+ /**
87
+ * Format baseline summary for display.
88
+ */
89
+ function formatBaselineSummary(baseline) {
90
+ const lines = [];
91
+ lines.push(`Baseline: ${baseline.url}`);
92
+ lines.push(`Issues: ${baseline.issues.length}`);
93
+ lines.push(`Created: ${baseline.created}`);
94
+ lines.push(`Updated: ${baseline.updated}`);
95
+ // Count by severity
96
+ const bySeverity = {};
97
+ for (const issue of baseline.issues) {
98
+ const sev = issue.severity || "unknown";
99
+ bySeverity[sev] = (bySeverity[sev] || 0) + 1;
100
+ }
101
+ const severityCounts = Object.entries(bySeverity)
102
+ .map(([sev, count]) => `${count} ${sev}`)
103
+ .join(", ");
104
+ if (severityCounts) {
105
+ lines.push(`Breakdown: ${severityCounts}`);
106
+ }
107
+ return lines.join("\n");
108
+ }
109
+ /**
110
+ * Register the baseline command with the Commander program.
111
+ */
112
+ export function registerBaselineCommand(program) {
113
+ program
114
+ .command("baseline [job-id]")
115
+ .description("Create or update baseline from audit results")
116
+ .option("--path <path>", "Baseline file path", DEFAULT_BASELINE_PATH)
117
+ .option("--from-file <file>", "Create baseline from JSON audit file")
118
+ .option("--ignore <id>", "Add single issue to existing baseline by ID")
119
+ .option("--reason <text>", "Reason for baseline/ignore (optional)")
120
+ .option("--list", "Show baselined issues count and last update")
121
+ .action(async (jobIdArg, cmdOptions) => {
122
+ try {
123
+ const baselinePath = cmdOptions.path || DEFAULT_BASELINE_PATH;
124
+ // Handle --list: show baseline summary
125
+ if (cmdOptions.list) {
126
+ const baseline = await loadBaseline(baselinePath);
127
+ if (!baseline) {
128
+ console.error("No baseline found.");
129
+ console.error(`Create one with: vertaa baseline <job-id> --path ${baselinePath}`);
130
+ return;
131
+ }
132
+ console.error(formatBaselineSummary(baseline));
133
+ return;
134
+ }
135
+ // Handle --ignore: add single issue to baseline
136
+ if (cmdOptions.ignore) {
137
+ const issueId = cmdOptions.ignore;
138
+ let baseline = await loadBaseline(baselinePath);
139
+ if (!baseline) {
140
+ console.error("No baseline found. Create one first with: vertaa baseline <job-id>");
141
+ process.exit(ExitCode.ERROR);
142
+ }
143
+ // Need to find the issue to add it properly
144
+ // If we have a job ID, fetch the audit; otherwise use a minimal issue
145
+ let issue;
146
+ if (jobIdArg) {
147
+ const audit = await fetchAudit(jobIdArg);
148
+ const issues = normalizeIssues(audit.issues);
149
+ const found = findIssueById(issues, issueId);
150
+ if (!found) {
151
+ console.error(`Issue "${issueId}" not found in job ${jobIdArg}`);
152
+ process.exit(ExitCode.ERROR);
153
+ }
154
+ issue = found;
155
+ }
156
+ else {
157
+ // Create minimal issue from ID only
158
+ issue = { id: issueId, description: `Ignored: ${issueId}` };
159
+ }
160
+ baseline = addToBaseline(baseline, issue, cmdOptions.reason);
161
+ await saveBaseline(baseline, baselinePath);
162
+ console.error(`Added issue ${issueId} to baseline`);
163
+ if (cmdOptions.reason) {
164
+ console.error(`Reason: ${cmdOptions.reason}`);
165
+ }
166
+ return;
167
+ }
168
+ // Handle --from-file: create baseline from JSON file
169
+ if (cmdOptions.fromFile) {
170
+ const filePath = cmdOptions.fromFile;
171
+ if (!existsSync(filePath)) {
172
+ console.error(`File not found: ${filePath}`);
173
+ process.exit(ExitCode.ERROR);
174
+ }
175
+ const content = await readFile(filePath, "utf-8");
176
+ const data = JSON.parse(content);
177
+ const issues = normalizeIssues(data.issues);
178
+ const url = data.url || "unknown";
179
+ const baseline = createBaseline(issues, url);
180
+ await saveBaseline(baseline, baselinePath);
181
+ console.error(`Baseline created with ${issues.length} issues`);
182
+ console.error(`Saved to: ${baselinePath}`);
183
+ return;
184
+ }
185
+ // Handle job-id: create baseline from API audit
186
+ if (!jobIdArg) {
187
+ console.error("Provide job ID or use --from-file or --list");
188
+ console.error("Usage: vertaa baseline <job-id>");
189
+ console.error(" vertaa baseline --from-file <audit.json>");
190
+ console.error(" vertaa baseline --list");
191
+ process.exit(ExitCode.ERROR);
192
+ }
193
+ const audit = await fetchAudit(jobIdArg);
194
+ if (audit.status !== "completed") {
195
+ console.error(`Audit ${jobIdArg} is not completed (status: ${audit.status})`);
196
+ process.exit(ExitCode.ERROR);
197
+ }
198
+ const issues = normalizeIssues(audit.issues);
199
+ const url = audit.url || "unknown";
200
+ const baseline = createBaseline(issues, url);
201
+ await saveBaseline(baseline, baselinePath);
202
+ console.error(`Baseline created with ${issues.length} issues`);
203
+ console.error(`Saved to: ${baselinePath}`);
204
+ }
205
+ catch (error) {
206
+ console.error("Error:", error instanceof Error ? error.message : String(error));
207
+ process.exit(ExitCode.ERROR);
208
+ }
209
+ });
210
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Comment command for VertaaUX CLI.
3
+ *
4
+ * Generates markdown for PR comments from audit results.
5
+ * Supports posting to GitHub and GitLab via their APIs.
6
+ * Uses sticky comment semantics to update existing comments.
7
+ */
8
+ import { Command } from "commander";
9
+ /**
10
+ * Register the comment command with the Commander program.
11
+ */
12
+ export declare function registerCommentCommand(program: Command): void;
13
+ export { formatMarkdownComment as generateComment } from "../output/markdown.js";
14
+ //# sourceMappingURL=comment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment.d.ts","sourceRoot":"","sources":["../../src/commands/comment.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgZpC;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6F7D;AAGD,OAAO,EAAE,qBAAqB,IAAI,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,363 @@
1
+ /**
2
+ * Comment command for VertaaUX CLI.
3
+ *
4
+ * Generates markdown for PR comments from audit results.
5
+ * Supports posting to GitHub and GitLab via their APIs.
6
+ * Uses sticky comment semantics to update existing comments.
7
+ */
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { loadBaseline } from "../baseline/manager.js";
11
+ import { ExitCode } from "../utils/exit-codes.js";
12
+ import { formatMarkdownComment, categorizeIssuesForComment, } from "../output/markdown.js";
13
+ import { createEnvelope, writeJsonOutput, writeOutput } from "../output/envelope.js";
14
+ import { resolveCommandFormat } from "../output/formats.js";
15
+ import { postOrUpdateGitHubComment, parseRepository, extractPRNumber, } from "../ci/github-api.js";
16
+ import { postOrUpdateGitLabNote, getGitLabConfig, extractMRIid, } from "../ci/gitlab-api.js";
17
+ /**
18
+ * Default path for latest audit results.
19
+ */
20
+ const LATEST_AUDIT_PATH = ".vertaaux/latest-audit.json";
21
+ /**
22
+ * Hidden header identifier for sticky comments.
23
+ */
24
+ const COMMENT_HEADER = "vertaaux-audit";
25
+ /**
26
+ * Normalize issues from various API response formats.
27
+ */
28
+ function normalizeIssues(issues) {
29
+ if (Array.isArray(issues))
30
+ return issues;
31
+ if (issues && typeof issues === "object") {
32
+ const values = Object.values(issues);
33
+ return values.flatMap((value) => Array.isArray(value) ? value : []);
34
+ }
35
+ return [];
36
+ }
37
+ /**
38
+ * Detect CI context from environment variables.
39
+ *
40
+ * Checks for GitHub Actions and GitLab CI environments.
41
+ * Returns null if not in a recognized CI environment.
42
+ */
43
+ function detectCIContext() {
44
+ // GitHub Actions
45
+ if (process.env.GITHUB_ACTIONS) {
46
+ const token = process.env.GITHUB_TOKEN;
47
+ if (!token)
48
+ return null;
49
+ const repo = process.env.GITHUB_REPOSITORY; // 'owner/repo'
50
+ const parsed = repo ? parseRepository(repo) : null;
51
+ const prNumber = extractPRNumber();
52
+ if (!parsed || !prNumber)
53
+ return null;
54
+ return {
55
+ platform: "github",
56
+ token,
57
+ repo: parsed.repo,
58
+ owner: parsed.owner,
59
+ prNumber,
60
+ };
61
+ }
62
+ // GitLab CI
63
+ if (process.env.GITLAB_CI) {
64
+ const config = getGitLabConfig();
65
+ if (!config)
66
+ return null;
67
+ const mrIid = extractMRIid();
68
+ if (!mrIid)
69
+ return null;
70
+ return {
71
+ platform: "gitlab",
72
+ token: config.token,
73
+ apiUrl: config.apiUrl,
74
+ projectId: config.projectId,
75
+ mrIid,
76
+ };
77
+ }
78
+ return null;
79
+ }
80
+ /**
81
+ * Post comment to GitHub PR.
82
+ */
83
+ async function postToGitHub(context, markdown) {
84
+ if (!context.owner || !context.repo || !context.prNumber) {
85
+ throw new Error("Missing GitHub context (owner, repo, or PR number)");
86
+ }
87
+ const result = await postOrUpdateGitHubComment({
88
+ token: context.token,
89
+ owner: context.owner,
90
+ repo: context.repo,
91
+ prNumber: context.prNumber,
92
+ body: markdown,
93
+ header: COMMENT_HEADER,
94
+ });
95
+ return { url: result.html_url };
96
+ }
97
+ /**
98
+ * Post note to GitLab MR.
99
+ */
100
+ async function postToGitLab(context, markdown) {
101
+ if (!context.apiUrl || !context.projectId || !context.mrIid) {
102
+ throw new Error("Missing GitLab context (apiUrl, projectId, or mrIid)");
103
+ }
104
+ const result = await postOrUpdateGitLabNote({
105
+ token: context.token,
106
+ apiUrl: context.apiUrl,
107
+ projectId: context.projectId,
108
+ mrIid: context.mrIid,
109
+ body: markdown,
110
+ header: COMMENT_HEADER,
111
+ });
112
+ return { url: result.web_url };
113
+ }
114
+ /**
115
+ * Post comment to the appropriate CI platform.
116
+ */
117
+ async function postComment(context, markdown) {
118
+ if (context.platform === "github") {
119
+ return postToGitHub(context, markdown);
120
+ }
121
+ else if (context.platform === "gitlab") {
122
+ return postToGitLab(context, markdown);
123
+ }
124
+ throw new Error(`Unsupported CI platform: ${context.platform}`);
125
+ }
126
+ /**
127
+ * Handle the comment command.
128
+ */
129
+ async function handleComment(options) {
130
+ let auditResult;
131
+ // Load audit results
132
+ if (options.input) {
133
+ // From file
134
+ const inputPath = path.resolve(process.cwd(), options.input);
135
+ if (!fs.existsSync(inputPath)) {
136
+ console.error(`Error: Input file not found: ${options.input}`);
137
+ process.exit(ExitCode.ERROR);
138
+ }
139
+ const content = fs.readFileSync(inputPath, "utf-8");
140
+ auditResult = JSON.parse(content);
141
+ }
142
+ else if (!process.stdin.isTTY) {
143
+ // From stdin
144
+ const chunks = [];
145
+ for await (const chunk of process.stdin) {
146
+ chunks.push(chunk);
147
+ }
148
+ const content = Buffer.concat(chunks).toString("utf-8");
149
+ auditResult = JSON.parse(content);
150
+ }
151
+ else {
152
+ // Try latest audit file
153
+ const latestPath = path.resolve(process.cwd(), LATEST_AUDIT_PATH);
154
+ if (fs.existsSync(latestPath)) {
155
+ const content = fs.readFileSync(latestPath, "utf-8");
156
+ auditResult = JSON.parse(content);
157
+ }
158
+ else {
159
+ console.error("Error: No audit results found. Provide --input or pipe results.");
160
+ process.exit(ExitCode.ERROR);
161
+ }
162
+ }
163
+ // Load baseline if specified
164
+ let baseline = null;
165
+ if (options.baseline) {
166
+ baseline = await loadBaseline(options.baseline);
167
+ }
168
+ else {
169
+ // Try default baseline
170
+ baseline = await loadBaseline();
171
+ }
172
+ // Categorize issues
173
+ const issues = normalizeIssues(auditResult.issues);
174
+ const { newIssues, fixedIssues, existingIssues } = categorizeIssuesForComment(issues, baseline);
175
+ // Build comment data
176
+ const commentData = {
177
+ auditId: auditResult.job_id,
178
+ url: auditResult.url,
179
+ newIssues,
180
+ fixedIssues,
181
+ existingIssues,
182
+ scores: auditResult.scores,
183
+ };
184
+ // Generate output
185
+ const isJson = options.format === "json";
186
+ const jsonData = {
187
+ auditId: auditResult.job_id,
188
+ url: auditResult.url,
189
+ newIssues: newIssues.length,
190
+ existingIssues: existingIssues.length,
191
+ fixedIssues: fixedIssues.length,
192
+ issues: {
193
+ new: newIssues,
194
+ existing: existingIssues,
195
+ fixed: fixedIssues,
196
+ },
197
+ };
198
+ let output;
199
+ if (isJson) {
200
+ // JSON format - will be wrapped in envelope at output time
201
+ output = JSON.stringify(jsonData, null, 2);
202
+ }
203
+ else {
204
+ // Markdown format (default)
205
+ output = formatMarkdownComment(commentData, {
206
+ groupBy: options.groupBy || "severity",
207
+ collapse: options.collapse ?? newIssues.length > 3,
208
+ collapseThreshold: 3,
209
+ includeEvidence: true,
210
+ includeFixes: true,
211
+ baseUrl: "https://vertaaux.ai",
212
+ });
213
+ }
214
+ // Handle posting
215
+ if (options.post) {
216
+ // Check if we should skip posting
217
+ if (options.noPostIfClean && newIssues.length === 0) {
218
+ console.error("No new issues found. Skipping comment post (--no-post-if-clean).");
219
+ // Still write output if requested
220
+ if (options.output) {
221
+ const outputPath = path.resolve(process.cwd(), options.output);
222
+ fs.writeFileSync(outputPath, output, "utf-8");
223
+ console.error(`Comment written to: ${outputPath}`);
224
+ }
225
+ return;
226
+ }
227
+ // Detect or build CI context
228
+ let context = null;
229
+ // Try explicit options first
230
+ if (options.githubToken || process.env.GITHUB_TOKEN) {
231
+ const token = options.githubToken || process.env.GITHUB_TOKEN;
232
+ const repo = options.repo || process.env.GITHUB_REPOSITORY;
233
+ const prNumber = options.pr || extractPRNumber();
234
+ if (repo && prNumber) {
235
+ const parsed = parseRepository(repo);
236
+ if (parsed) {
237
+ context = {
238
+ platform: "github",
239
+ token,
240
+ owner: parsed.owner,
241
+ repo: parsed.repo,
242
+ prNumber,
243
+ };
244
+ }
245
+ }
246
+ }
247
+ else if (options.gitlabToken || process.env.GITLAB_TOKEN || process.env.CI_JOB_TOKEN) {
248
+ const config = getGitLabConfig();
249
+ if (config) {
250
+ const mrIid = options.pr || extractMRIid();
251
+ if (mrIid) {
252
+ context = {
253
+ platform: "gitlab",
254
+ token: config.token,
255
+ apiUrl: config.apiUrl,
256
+ projectId: config.projectId,
257
+ mrIid,
258
+ };
259
+ }
260
+ }
261
+ }
262
+ // Fall back to auto-detection
263
+ if (!context) {
264
+ context = detectCIContext();
265
+ }
266
+ if (!context) {
267
+ console.error("Error: Cannot post comment - not in a recognized CI environment.");
268
+ console.error("Provide --github-token with --repo and --pr, or run in GitHub Actions/GitLab CI.");
269
+ // Output for debugging via stdout
270
+ if (isJson) {
271
+ writeJsonOutput(jsonData, "comment");
272
+ }
273
+ else {
274
+ writeOutput(output);
275
+ }
276
+ process.exit(ExitCode.ERROR);
277
+ }
278
+ try {
279
+ const result = await postComment(context, output);
280
+ console.error(`Comment posted: ${result.url}`);
281
+ }
282
+ catch (error) {
283
+ console.error(`Error posting comment: ${error instanceof Error ? error.message : String(error)}`);
284
+ // Output for debugging even on failure
285
+ console.error("\nGenerated output (for debugging):");
286
+ if (isJson) {
287
+ writeJsonOutput(jsonData, "comment");
288
+ }
289
+ else {
290
+ writeOutput(output);
291
+ }
292
+ process.exit(ExitCode.ERROR);
293
+ }
294
+ }
295
+ // Write output to file or stdout
296
+ if (options.output) {
297
+ const outputPath = path.resolve(process.cwd(), options.output);
298
+ const fileContent = isJson
299
+ ? JSON.stringify(createEnvelope(jsonData, "comment"), null, 2)
300
+ : output;
301
+ fs.writeFileSync(outputPath, fileContent, "utf-8");
302
+ console.error(`Comment written to: ${outputPath}`);
303
+ }
304
+ else if (!options.post) {
305
+ // Only print to stdout if not posting (posting already logs URL)
306
+ if (isJson) {
307
+ writeJsonOutput(jsonData, "comment");
308
+ }
309
+ else {
310
+ writeOutput(output);
311
+ }
312
+ }
313
+ }
314
+ /**
315
+ * Register the comment command with the Commander program.
316
+ */
317
+ export function registerCommentCommand(program) {
318
+ program
319
+ .command("comment")
320
+ .description("Generate or post PR comment markdown from audit results")
321
+ .option("--input <file>", "Path to audit results JSON")
322
+ .option("--baseline <file>", "Path to baseline JSON for diff comparison")
323
+ .option("--format <type>", "Output format: markdown|json (default: markdown)")
324
+ .option("-o, --output <file>", "Write to file instead of stdout")
325
+ .option("--collapse", "Use collapsible sections (default: true for >3 issues)")
326
+ .option("--no-collapse", "Disable collapsible sections")
327
+ .option("--group-by <field>", "Group issues by: severity|category|route|file|component (default: severity)")
328
+ // Posting options
329
+ .option("--post", "Post/update comment to PR (requires CI context)")
330
+ .option("--github-token <token>", "GitHub token (default: $GITHUB_TOKEN)")
331
+ .option("--gitlab-token <token>", "GitLab token (default: $GITLAB_TOKEN or $CI_JOB_TOKEN)")
332
+ .option("--pr <number>", "PR/MR number (auto-detect from CI env vars)", (val) => parseInt(val, 10))
333
+ .option("--repo <owner/repo>", "Repository (auto-detect from CI env vars)")
334
+ .option("--no-post-if-clean", "Don't post comment if no new issues")
335
+ .action(async (cmdOptions, cmd) => {
336
+ try {
337
+ // Validate format using per-command registry
338
+ const machineMode = cmd.optsWithGlobals?.().machine || false;
339
+ const validatedFormat = resolveCommandFormat("comment", cmdOptions.format, machineMode);
340
+ await handleComment({
341
+ input: cmdOptions.input,
342
+ baseline: cmdOptions.baseline,
343
+ format: validatedFormat,
344
+ output: cmdOptions.output,
345
+ collapse: cmdOptions.collapse,
346
+ groupBy: cmdOptions.groupBy,
347
+ post: cmdOptions.post,
348
+ githubToken: cmdOptions.githubToken,
349
+ gitlabToken: cmdOptions.gitlabToken,
350
+ pr: cmdOptions.pr,
351
+ repo: cmdOptions.repo,
352
+ // Commander --no-X flags become X = false
353
+ noPostIfClean: cmdOptions.postIfClean === false,
354
+ });
355
+ }
356
+ catch (error) {
357
+ console.error("Error:", error instanceof Error ? error.message : String(error));
358
+ process.exit(ExitCode.ERROR);
359
+ }
360
+ });
361
+ }
362
+ // Re-export for backward compatibility
363
+ export { formatMarkdownComment as generateComment } from "../output/markdown.js";
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Diff command for VertaaUX CLI.
3
+ *
4
+ * Compares current audit results against baseline or previous audit.
5
+ * Shows three categories: new, fixed, still present.
6
+ *
7
+ * Exit codes:
8
+ * - 0: No new issues
9
+ * - 1: New issues found
10
+ * - 2: Error
11
+ */
12
+ import type { Command } from "commander";
13
+ export interface DiffCommandOptions {
14
+ baseline?: string;
15
+ compare?: string;
16
+ format?: string;
17
+ verbose?: boolean;
18
+ fromFile?: string;
19
+ }
20
+ /**
21
+ * Register the diff command with the Commander program.
22
+ */
23
+ export declare function registerDiffCommand(program: Command): void;
24
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiFzC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2I1D"}