@vertaaux/cli 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +58 -2
- package/dist/auth/device-flow.js +6 -8
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +165 -6
- package/dist/commands/compare.d.ts +20 -0
- package/dist/commands/compare.d.ts.map +1 -0
- package/dist/commands/compare.js +335 -0
- package/dist/commands/doc.d.ts +18 -0
- package/dist/commands/doc.d.ts.map +1 -0
- package/dist/commands/doc.js +161 -0
- package/dist/commands/download.d.ts.map +1 -1
- package/dist/commands/download.js +9 -8
- package/dist/commands/explain.d.ts +14 -33
- package/dist/commands/explain.d.ts.map +1 -1
- package/dist/commands/explain.js +277 -179
- package/dist/commands/fix-plan.d.ts +15 -0
- package/dist/commands/fix-plan.d.ts.map +1 -0
- package/dist/commands/fix-plan.js +182 -0
- package/dist/commands/patch-review.d.ts +14 -0
- package/dist/commands/patch-review.d.ts.map +1 -0
- package/dist/commands/patch-review.js +200 -0
- package/dist/commands/release-notes.d.ts +17 -0
- package/dist/commands/release-notes.d.ts.map +1 -0
- package/dist/commands/release-notes.js +145 -0
- package/dist/commands/suggest.d.ts +18 -0
- package/dist/commands/suggest.d.ts.map +1 -0
- package/dist/commands/suggest.js +152 -0
- package/dist/commands/triage.d.ts +17 -0
- package/dist/commands/triage.d.ts.map +1 -0
- package/dist/commands/triage.js +205 -0
- package/dist/commands/upload.d.ts.map +1 -1
- package/dist/commands/upload.js +8 -7
- package/dist/index.js +62 -25
- package/dist/output/formats.d.ts.map +1 -1
- package/dist/output/formats.js +14 -0
- package/dist/output/human.d.ts +1 -10
- package/dist/output/human.d.ts.map +1 -1
- package/dist/output/human.js +26 -98
- package/dist/prompts/command-catalog.d.ts +46 -0
- package/dist/prompts/command-catalog.d.ts.map +1 -0
- package/dist/prompts/command-catalog.js +187 -0
- package/dist/ui/spinner.d.ts +10 -35
- package/dist/ui/spinner.d.ts.map +1 -1
- package/dist/ui/spinner.js +11 -58
- package/dist/ui/table.d.ts +1 -18
- package/dist/ui/table.d.ts.map +1 -1
- package/dist/ui/table.js +56 -163
- package/dist/utils/ai-error.d.ts +48 -0
- package/dist/utils/ai-error.d.ts.map +1 -0
- package/dist/utils/ai-error.js +190 -0
- package/dist/utils/detect-env.d.ts +6 -8
- package/dist/utils/detect-env.d.ts.map +1 -1
- package/dist/utils/detect-env.js +6 -25
- package/dist/utils/stdin.d.ts +50 -0
- package/dist/utils/stdin.d.ts.map +1 -0
- package/dist/utils/stdin.js +93 -0
- package/node_modules/@vertaaux/tui/dist/index.cjs +1157 -0
- package/node_modules/@vertaaux/tui/dist/index.cjs.map +1 -0
- package/node_modules/@vertaaux/tui/dist/index.d.cts +609 -0
- package/node_modules/@vertaaux/tui/dist/index.d.ts +609 -0
- package/node_modules/@vertaaux/tui/dist/index.js +1100 -0
- package/node_modules/@vertaaux/tui/dist/index.js.map +1 -0
- package/node_modules/@vertaaux/tui/package.json +64 -0
- package/package.json +12 -5
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fix-plan command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Accepts full audit JSON (via stdin, --file, or --job) and calls the
|
|
5
|
+
* LLM fix-plan endpoint to produce a structured remediation plan with
|
|
6
|
+
* ordered steps, effort estimates, and code hints.
|
|
7
|
+
*
|
|
8
|
+
* Examples:
|
|
9
|
+
* vertaa audit https://example.com --json | vertaa fix-plan
|
|
10
|
+
* vertaa fix-plan --job abc123
|
|
11
|
+
* vertaa fix-plan --file audit.json --json
|
|
12
|
+
*/
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import { ExitCode } from "../utils/exit-codes.js";
|
|
15
|
+
import { resolveApiBase, getApiKey, apiRequest } from "../utils/client.js";
|
|
16
|
+
import { resolveConfig } from "../config/loader.js";
|
|
17
|
+
import { writeJsonOutput, writeOutput } from "../output/envelope.js";
|
|
18
|
+
import { resolveCommandFormat } from "../output/formats.js";
|
|
19
|
+
import { createSpinner, succeedSpinner } from "../ui/spinner.js";
|
|
20
|
+
import { readJsonInput } from "../utils/stdin.js";
|
|
21
|
+
import { handleAiCommandError, AI_TIMEOUT_MS } from "../utils/ai-error.js";
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
function normalizeIssues(issues) {
|
|
26
|
+
let list;
|
|
27
|
+
if (Array.isArray(issues)) {
|
|
28
|
+
list = issues;
|
|
29
|
+
}
|
|
30
|
+
else if (issues && typeof issues === "object") {
|
|
31
|
+
list = Object.values(issues).flatMap((v) => Array.isArray(v) ? v : []);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
return list.map((raw) => {
|
|
37
|
+
const i = raw;
|
|
38
|
+
return {
|
|
39
|
+
id: i.id || i.ruleId || i.rule_id || null,
|
|
40
|
+
title: i.title || i.description || null,
|
|
41
|
+
description: i.description || null,
|
|
42
|
+
severity: i.severity || null,
|
|
43
|
+
category: i.category || null,
|
|
44
|
+
selector: i.selector || null,
|
|
45
|
+
wcag_reference: i.wcag_reference || null,
|
|
46
|
+
recommendation: i.recommendation || i.recommended_fix || null,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Formatters
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
const SEVERITY_COLORS = {
|
|
54
|
+
critical: chalk.red.bold,
|
|
55
|
+
high: chalk.red,
|
|
56
|
+
medium: chalk.yellow,
|
|
57
|
+
low: chalk.cyan,
|
|
58
|
+
};
|
|
59
|
+
const EFFORT_LABELS = {
|
|
60
|
+
trivial: chalk.green("trivial"),
|
|
61
|
+
small: chalk.green("small"),
|
|
62
|
+
medium: chalk.yellow("medium"),
|
|
63
|
+
large: chalk.red("large"),
|
|
64
|
+
};
|
|
65
|
+
const FIX_TYPE_LABELS = {
|
|
66
|
+
code: chalk.blue("code"),
|
|
67
|
+
config: chalk.magenta("config"),
|
|
68
|
+
content: chalk.cyan("content"),
|
|
69
|
+
design: chalk.yellow("design"),
|
|
70
|
+
};
|
|
71
|
+
function formatFixPlanHuman(data) {
|
|
72
|
+
const lines = [];
|
|
73
|
+
lines.push(chalk.bold(`Remediation Plan (${data.items.length} items)`));
|
|
74
|
+
lines.push(chalk.dim(`Estimated total effort: ${data.estimated_total_effort}`));
|
|
75
|
+
lines.push("");
|
|
76
|
+
for (let idx = 0; idx < data.items.length; idx++) {
|
|
77
|
+
const item = data.items[idx];
|
|
78
|
+
const severityFn = SEVERITY_COLORS[item.severity] || chalk.dim;
|
|
79
|
+
const effort = EFFORT_LABELS[item.effort] || chalk.dim(item.effort);
|
|
80
|
+
const fixType = FIX_TYPE_LABELS[item.fix_type] || chalk.dim(item.fix_type);
|
|
81
|
+
lines.push(`${chalk.bold(`${idx + 1}.`)} ${severityFn(`[${item.severity}]`)} ${chalk.bold(item.title)}`);
|
|
82
|
+
lines.push(` Effort: ${effort} Type: ${fixType}${item.id ? chalk.dim(` (${item.id})`) : ""}`);
|
|
83
|
+
for (let s = 0; s < item.steps.length; s++) {
|
|
84
|
+
lines.push(` ${chalk.dim(`${s + 1})`)} ${item.steps[s]}`);
|
|
85
|
+
}
|
|
86
|
+
if (item.code_hint) {
|
|
87
|
+
lines.push(` ${chalk.dim("Hint:")} ${chalk.italic(item.code_hint)}`);
|
|
88
|
+
}
|
|
89
|
+
lines.push("");
|
|
90
|
+
}
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
}
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Command Registration
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
export function registerFixPlanCommand(program) {
|
|
97
|
+
program
|
|
98
|
+
.command("fix-plan")
|
|
99
|
+
.description("Generate a structured remediation plan from audit findings")
|
|
100
|
+
.option("--job <job-id>", "Fetch audit data from a job ID")
|
|
101
|
+
.option("--file <path>", "Load audit JSON from file")
|
|
102
|
+
.option("-f, --format <format>", "Output format: json | human")
|
|
103
|
+
.addHelpText("after", `
|
|
104
|
+
Examples:
|
|
105
|
+
vertaa audit https://example.com --json | vertaa fix-plan
|
|
106
|
+
vertaa fix-plan --job abc123
|
|
107
|
+
vertaa fix-plan --file audit.json --json
|
|
108
|
+
vertaa audit ... --json | vertaa fix-plan --json | jq .
|
|
109
|
+
`)
|
|
110
|
+
.action(async (options, command) => {
|
|
111
|
+
try {
|
|
112
|
+
const globalOpts = command.optsWithGlobals();
|
|
113
|
+
const config = await resolveConfig(globalOpts.config);
|
|
114
|
+
const machineMode = globalOpts.machine || false;
|
|
115
|
+
const format = resolveCommandFormat("fix-plan", options.format, machineMode);
|
|
116
|
+
// Resolve audit data
|
|
117
|
+
let auditPayload;
|
|
118
|
+
if (options.job) {
|
|
119
|
+
const base = resolveApiBase(globalOpts.base);
|
|
120
|
+
const apiKey = getApiKey(config.apiKey);
|
|
121
|
+
const result = await apiRequest(base, `/audit/${options.job}`, { method: "GET" }, apiKey);
|
|
122
|
+
const issues = normalizeIssues(result.issues);
|
|
123
|
+
auditPayload = {
|
|
124
|
+
job_id: result.job_id || options.job,
|
|
125
|
+
url: result.url || null,
|
|
126
|
+
scores: result.scores || null,
|
|
127
|
+
issues,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const input = await readJsonInput(options.file);
|
|
132
|
+
if (!input) {
|
|
133
|
+
console.error("Error: No audit data provided.");
|
|
134
|
+
console.error("Usage:");
|
|
135
|
+
console.error(" vertaa audit https://example.com --json | vertaa fix-plan");
|
|
136
|
+
console.error(" vertaa fix-plan --job <job-id>");
|
|
137
|
+
console.error(" vertaa fix-plan --file audit.json");
|
|
138
|
+
process.exit(ExitCode.ERROR);
|
|
139
|
+
}
|
|
140
|
+
const data = input;
|
|
141
|
+
const innerData = (data.data && typeof data.data === "object" ? data.data : data);
|
|
142
|
+
const issues = normalizeIssues(innerData.issues);
|
|
143
|
+
auditPayload = {
|
|
144
|
+
job_id: innerData.job_id || null,
|
|
145
|
+
url: innerData.url || null,
|
|
146
|
+
scores: innerData.scores || null,
|
|
147
|
+
issues,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (!Array.isArray(auditPayload.issues) ||
|
|
151
|
+
auditPayload.issues.length === 0) {
|
|
152
|
+
console.error("Error: No issues found in audit data.");
|
|
153
|
+
process.exit(ExitCode.ERROR);
|
|
154
|
+
}
|
|
155
|
+
// Auth check
|
|
156
|
+
const base = resolveApiBase(globalOpts.base);
|
|
157
|
+
const apiKey = getApiKey(config.apiKey);
|
|
158
|
+
// Call LLM fix-plan API
|
|
159
|
+
const spinner = createSpinner("Generating remediation plan...");
|
|
160
|
+
try {
|
|
161
|
+
const response = await Promise.race([
|
|
162
|
+
apiRequest(base, "/cli/ai/fix-plan", { method: "POST", body: { audit: auditPayload } }, apiKey),
|
|
163
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
|
|
164
|
+
]);
|
|
165
|
+
succeedSpinner(spinner, "Plan ready");
|
|
166
|
+
if (format === "json") {
|
|
167
|
+
writeJsonOutput(response.data, "fix-plan");
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
writeOutput(formatFixPlanHuman(response.data));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
handleAiCommandError(error, "fix-plan", spinner);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
179
|
+
process.exit(ExitCode.ERROR);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Patch-review command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Reads a diff from stdin and (optionally) findings from a job,
|
|
5
|
+
* calls the LLM patch-review endpoint to produce a SAFE/UNSAFE/NEEDS_REVIEW verdict.
|
|
6
|
+
*
|
|
7
|
+
* Examples:
|
|
8
|
+
* gh pr diff 123 | vertaa patch-review --job abc123
|
|
9
|
+
* git diff HEAD~1 | vertaa patch-review --job abc123
|
|
10
|
+
* cat fix.patch | vertaa patch-review --findings findings.json
|
|
11
|
+
*/
|
|
12
|
+
import { Command } from "commander";
|
|
13
|
+
export declare function registerPatchReviewCommand(program: Command): void;
|
|
14
|
+
//# sourceMappingURL=patch-review.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patch-review.d.ts","sourceRoot":"","sources":["../../src/commands/patch-review.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmIpC,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2HjE"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Patch-review command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Reads a diff from stdin and (optionally) findings from a job,
|
|
5
|
+
* calls the LLM patch-review endpoint to produce a SAFE/UNSAFE/NEEDS_REVIEW verdict.
|
|
6
|
+
*
|
|
7
|
+
* Examples:
|
|
8
|
+
* gh pr diff 123 | vertaa patch-review --job abc123
|
|
9
|
+
* git diff HEAD~1 | vertaa patch-review --job abc123
|
|
10
|
+
* cat fix.patch | vertaa patch-review --findings findings.json
|
|
11
|
+
*/
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import { ExitCode } from "../utils/exit-codes.js";
|
|
14
|
+
import { resolveApiBase, getApiKey, apiRequest } from "../utils/client.js";
|
|
15
|
+
import { resolveConfig } from "../config/loader.js";
|
|
16
|
+
import { writeJsonOutput, writeOutput } from "../output/envelope.js";
|
|
17
|
+
import { resolveCommandFormat } from "../output/formats.js";
|
|
18
|
+
import { createSpinner, succeedSpinner } from "../ui/spinner.js";
|
|
19
|
+
import { readTextInput, readJsonInput } from "../utils/stdin.js";
|
|
20
|
+
import { handleAiCommandError, AI_TIMEOUT_MS } from "../utils/ai-error.js";
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Helpers
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
function normalizeIssues(issues) {
|
|
25
|
+
let list;
|
|
26
|
+
if (Array.isArray(issues)) {
|
|
27
|
+
list = issues;
|
|
28
|
+
}
|
|
29
|
+
else if (issues && typeof issues === "object") {
|
|
30
|
+
list = Object.values(issues).flatMap((v) => Array.isArray(v) ? v : []);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
return list.map((raw) => {
|
|
36
|
+
const i = raw;
|
|
37
|
+
return {
|
|
38
|
+
id: i.id || i.ruleId || i.rule_id || null,
|
|
39
|
+
title: i.title || i.description || null,
|
|
40
|
+
description: i.description || null,
|
|
41
|
+
severity: i.severity || null,
|
|
42
|
+
category: i.category || null,
|
|
43
|
+
selector: i.selector || null,
|
|
44
|
+
wcag_reference: i.wcag_reference || null,
|
|
45
|
+
recommendation: i.recommendation || i.recommended_fix || null,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Formatters
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
const VERDICT_DISPLAY = {
|
|
53
|
+
SAFE: chalk.green.bold("SAFE"),
|
|
54
|
+
UNSAFE: chalk.red.bold("UNSAFE"),
|
|
55
|
+
NEEDS_REVIEW: chalk.yellow.bold("NEEDS REVIEW"),
|
|
56
|
+
};
|
|
57
|
+
const CONCERN_SEVERITY = {
|
|
58
|
+
critical: chalk.red("critical"),
|
|
59
|
+
warning: chalk.yellow("warning"),
|
|
60
|
+
info: chalk.cyan("info"),
|
|
61
|
+
};
|
|
62
|
+
function formatPatchReviewHuman(data) {
|
|
63
|
+
const lines = [];
|
|
64
|
+
const verdictDisplay = VERDICT_DISPLAY[data.verdict] || chalk.bold(data.verdict);
|
|
65
|
+
lines.push(`Verdict: ${verdictDisplay} (confidence: ${data.confidence}%)`);
|
|
66
|
+
lines.push("");
|
|
67
|
+
lines.push(data.summary);
|
|
68
|
+
if (data.concerns.length > 0) {
|
|
69
|
+
lines.push("");
|
|
70
|
+
lines.push(chalk.bold("Concerns:"));
|
|
71
|
+
for (const concern of data.concerns) {
|
|
72
|
+
const sev = CONCERN_SEVERITY[concern.severity] || chalk.dim(concern.severity);
|
|
73
|
+
const loc = concern.location ? chalk.dim(` @ ${concern.location}`) : "";
|
|
74
|
+
lines.push(` ${sev}${loc}: ${concern.description}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (data.findings_addressed.length > 0) {
|
|
78
|
+
lines.push("");
|
|
79
|
+
lines.push(chalk.green(`Findings addressed (${data.findings_addressed.length}):`));
|
|
80
|
+
for (const id of data.findings_addressed) {
|
|
81
|
+
lines.push(` ${chalk.green("+")} ${id}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (data.findings_remaining.length > 0) {
|
|
85
|
+
lines.push("");
|
|
86
|
+
lines.push(chalk.yellow(`Findings remaining (${data.findings_remaining.length}):`));
|
|
87
|
+
for (const id of data.findings_remaining) {
|
|
88
|
+
lines.push(` ${chalk.yellow("-")} ${id}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
}
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Command Registration
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
export function registerPatchReviewCommand(program) {
|
|
97
|
+
program
|
|
98
|
+
.command("patch-review")
|
|
99
|
+
.description("Review a patch/diff against audit findings for safety")
|
|
100
|
+
.option("--job <job-id>", "Fetch findings from a job ID")
|
|
101
|
+
.option("--findings <path>", "Load findings JSON from file")
|
|
102
|
+
.option("--diff-file <path>", "Load diff from file instead of stdin")
|
|
103
|
+
.option("-f, --format <format>", "Output format: json | human")
|
|
104
|
+
.addHelpText("after", `
|
|
105
|
+
Examples:
|
|
106
|
+
gh pr diff 123 | vertaa patch-review --job abc123
|
|
107
|
+
git diff HEAD~1 | vertaa patch-review --job abc123
|
|
108
|
+
vertaa patch-review --diff-file fix.patch --findings audit.json
|
|
109
|
+
`)
|
|
110
|
+
.action(async (options, command) => {
|
|
111
|
+
try {
|
|
112
|
+
const globalOpts = command.optsWithGlobals();
|
|
113
|
+
const config = await resolveConfig(globalOpts.config);
|
|
114
|
+
const machineMode = globalOpts.machine || false;
|
|
115
|
+
const dryRun = globalOpts.dryRun || false;
|
|
116
|
+
const format = resolveCommandFormat("patch-review", options.format, machineMode);
|
|
117
|
+
// Read diff content
|
|
118
|
+
let diffContent;
|
|
119
|
+
if (options.diffFile) {
|
|
120
|
+
const fs = await import("fs");
|
|
121
|
+
const path = await import("path");
|
|
122
|
+
const resolved = path.resolve(process.cwd(), options.diffFile);
|
|
123
|
+
if (!fs.existsSync(resolved)) {
|
|
124
|
+
console.error(`Error: Diff file not found: ${options.diffFile}`);
|
|
125
|
+
process.exit(ExitCode.ERROR);
|
|
126
|
+
}
|
|
127
|
+
diffContent = fs.readFileSync(resolved, "utf-8");
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const text = await readTextInput();
|
|
131
|
+
if (!text) {
|
|
132
|
+
console.error("Error: No diff provided.");
|
|
133
|
+
console.error("Pipe a diff via stdin or use --diff-file:");
|
|
134
|
+
console.error(" gh pr diff 123 | vertaa patch-review --job abc123");
|
|
135
|
+
console.error(" vertaa patch-review --diff-file fix.patch --findings audit.json");
|
|
136
|
+
process.exit(ExitCode.ERROR);
|
|
137
|
+
}
|
|
138
|
+
diffContent = text;
|
|
139
|
+
}
|
|
140
|
+
// Resolve findings
|
|
141
|
+
let findingsList = null;
|
|
142
|
+
let jobId = null;
|
|
143
|
+
if (options.job) {
|
|
144
|
+
const base = resolveApiBase(globalOpts.base);
|
|
145
|
+
const apiKey = getApiKey(config.apiKey);
|
|
146
|
+
const result = await apiRequest(base, `/audit/${options.job}`, { method: "GET" }, apiKey);
|
|
147
|
+
findingsList = normalizeIssues(result.issues);
|
|
148
|
+
jobId = result.job_id || options.job;
|
|
149
|
+
}
|
|
150
|
+
else if (options.findings) {
|
|
151
|
+
const input = await readJsonInput(options.findings);
|
|
152
|
+
if (input) {
|
|
153
|
+
const data = input;
|
|
154
|
+
const innerData = (data.data && typeof data.data === "object" ? data.data : data);
|
|
155
|
+
findingsList = normalizeIssues(innerData.issues);
|
|
156
|
+
jobId = innerData.job_id || null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (dryRun) {
|
|
160
|
+
console.log(chalk.yellow("[dry-run]") + " Would analyze diff against findings");
|
|
161
|
+
console.log(` Diff size: ${diffContent.length} characters`);
|
|
162
|
+
console.log(` Findings: ${findingsList ? findingsList.length : "none"}`);
|
|
163
|
+
console.log(` Job ID: ${jobId || "none"}`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Auth check
|
|
167
|
+
const base = resolveApiBase(globalOpts.base);
|
|
168
|
+
const apiKey = getApiKey(config.apiKey);
|
|
169
|
+
// Call LLM patch-review API
|
|
170
|
+
const spinner = createSpinner("Reviewing patch...");
|
|
171
|
+
try {
|
|
172
|
+
const response = await Promise.race([
|
|
173
|
+
apiRequest(base, "/cli/ai/patch-review", {
|
|
174
|
+
method: "POST",
|
|
175
|
+
body: {
|
|
176
|
+
diff: diffContent,
|
|
177
|
+
findings: findingsList,
|
|
178
|
+
job_id: jobId,
|
|
179
|
+
},
|
|
180
|
+
}, apiKey),
|
|
181
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
|
|
182
|
+
]);
|
|
183
|
+
succeedSpinner(spinner, "Review complete");
|
|
184
|
+
if (format === "json") {
|
|
185
|
+
writeJsonOutput(response.data, "patch-review");
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
writeOutput(formatPatchReviewHuman(response.data));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
handleAiCommandError(error, "patch-review", spinner);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
197
|
+
process.exit(ExitCode.ERROR);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release-notes command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Reads diff data (via stdin, --file, or from two --job-a/--job-b jobs)
|
|
5
|
+
* and calls the LLM release-notes endpoint to produce developer-facing
|
|
6
|
+
* and PM-facing release notes.
|
|
7
|
+
*
|
|
8
|
+
* Default output is markdown; use --format json for structured output.
|
|
9
|
+
*
|
|
10
|
+
* Examples:
|
|
11
|
+
* vertaa diff --job-a abc --job-b def --json | vertaa release-notes
|
|
12
|
+
* vertaa release-notes --file diff.json
|
|
13
|
+
* vertaa release-notes --job-a abc --job-b def
|
|
14
|
+
*/
|
|
15
|
+
import { Command } from "commander";
|
|
16
|
+
export declare function registerReleaseNotesCommand(program: Command): void;
|
|
17
|
+
//# sourceMappingURL=release-notes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release-notes.d.ts","sourceRoot":"","sources":["../../src/commands/release-notes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0EpC,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgHlE"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release-notes command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Reads diff data (via stdin, --file, or from two --job-a/--job-b jobs)
|
|
5
|
+
* and calls the LLM release-notes endpoint to produce developer-facing
|
|
6
|
+
* and PM-facing release notes.
|
|
7
|
+
*
|
|
8
|
+
* Default output is markdown; use --format json for structured output.
|
|
9
|
+
*
|
|
10
|
+
* Examples:
|
|
11
|
+
* vertaa diff --job-a abc --job-b def --json | vertaa release-notes
|
|
12
|
+
* vertaa release-notes --file diff.json
|
|
13
|
+
* vertaa release-notes --job-a abc --job-b def
|
|
14
|
+
*/
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import { ExitCode } from "../utils/exit-codes.js";
|
|
17
|
+
import { resolveApiBase, getApiKey, apiRequest } from "../utils/client.js";
|
|
18
|
+
import { resolveConfig } from "../config/loader.js";
|
|
19
|
+
import { writeJsonOutput, writeOutput } from "../output/envelope.js";
|
|
20
|
+
import { resolveCommandFormat } from "../output/formats.js";
|
|
21
|
+
import { createSpinner, succeedSpinner } from "../ui/spinner.js";
|
|
22
|
+
import { readJsonInput } from "../utils/stdin.js";
|
|
23
|
+
import { handleAiCommandError, AI_TIMEOUT_MS } from "../utils/ai-error.js";
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Formatters
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
function formatReleaseNotesHuman(data) {
|
|
28
|
+
const lines = [];
|
|
29
|
+
lines.push(chalk.bold(data.headline));
|
|
30
|
+
lines.push("");
|
|
31
|
+
lines.push(chalk.bold.blue("Developer Notes"));
|
|
32
|
+
lines.push(chalk.dim("─".repeat(40)));
|
|
33
|
+
lines.push(data.developer_notes);
|
|
34
|
+
lines.push("");
|
|
35
|
+
lines.push(chalk.bold.green("PM / User-Facing Notes"));
|
|
36
|
+
lines.push(chalk.dim("─".repeat(40)));
|
|
37
|
+
lines.push(data.pm_notes);
|
|
38
|
+
return lines.join("\n");
|
|
39
|
+
}
|
|
40
|
+
function formatReleaseNotesMarkdown(data) {
|
|
41
|
+
const lines = [];
|
|
42
|
+
lines.push(`# ${data.headline}`);
|
|
43
|
+
lines.push("");
|
|
44
|
+
lines.push("## Developer Notes");
|
|
45
|
+
lines.push("");
|
|
46
|
+
lines.push(data.developer_notes);
|
|
47
|
+
lines.push("");
|
|
48
|
+
lines.push("## User-Facing Notes");
|
|
49
|
+
lines.push("");
|
|
50
|
+
lines.push(data.pm_notes);
|
|
51
|
+
return lines.join("\n");
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Command Registration
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
export function registerReleaseNotesCommand(program) {
|
|
57
|
+
program
|
|
58
|
+
.command("release-notes")
|
|
59
|
+
.description("Generate developer + PM release notes from audit diff data")
|
|
60
|
+
.option("--file <path>", "Load diff JSON from file")
|
|
61
|
+
.option("--job-a <id>", "First audit job ID (baseline)")
|
|
62
|
+
.option("--job-b <id>", "Second audit job ID (current)")
|
|
63
|
+
.option("-f, --format <format>", "Output format: json | human | markdown")
|
|
64
|
+
.addHelpText("after", `
|
|
65
|
+
Examples:
|
|
66
|
+
vertaa diff --job-a abc --job-b def --json | vertaa release-notes
|
|
67
|
+
vertaa release-notes --file diff.json
|
|
68
|
+
vertaa release-notes --job-a abc --job-b def
|
|
69
|
+
`)
|
|
70
|
+
.action(async (options, command) => {
|
|
71
|
+
try {
|
|
72
|
+
const globalOpts = command.optsWithGlobals();
|
|
73
|
+
const config = await resolveConfig(globalOpts.config);
|
|
74
|
+
const machineMode = globalOpts.machine || false;
|
|
75
|
+
const format = resolveCommandFormat("release-notes", options.format, machineMode);
|
|
76
|
+
// Resolve diff data
|
|
77
|
+
let diffPayload;
|
|
78
|
+
if (options.jobA && options.jobB) {
|
|
79
|
+
// Fetch diff from two jobs via the diff API
|
|
80
|
+
const base = resolveApiBase(globalOpts.base);
|
|
81
|
+
const apiKey = getApiKey(config.apiKey);
|
|
82
|
+
const diffResult = await apiRequest(base, `/diff?job_a=${encodeURIComponent(options.jobA)}&job_b=${encodeURIComponent(options.jobB)}`, { method: "GET" }, apiKey);
|
|
83
|
+
diffPayload = diffResult.data;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Read from stdin or --file
|
|
87
|
+
const input = await readJsonInput(options.file);
|
|
88
|
+
if (!input) {
|
|
89
|
+
console.error("Error: No diff data provided.");
|
|
90
|
+
console.error("Usage:");
|
|
91
|
+
console.error(" vertaa diff --job-a abc --job-b def --json | vertaa release-notes");
|
|
92
|
+
console.error(" vertaa release-notes --file diff.json");
|
|
93
|
+
console.error(" vertaa release-notes --job-a abc --job-b def");
|
|
94
|
+
process.exit(ExitCode.ERROR);
|
|
95
|
+
}
|
|
96
|
+
const data = input;
|
|
97
|
+
const innerData = (data.data && typeof data.data === "object" ? data.data : data);
|
|
98
|
+
// Validate shape
|
|
99
|
+
if (!Array.isArray(innerData.new) || !Array.isArray(innerData.fixed)) {
|
|
100
|
+
console.error("Error: Invalid diff data. Expected { new: [...], fixed: [...], summary: {...} }");
|
|
101
|
+
console.error("Pipe from: vertaa diff --job-a abc --job-b def --json | vertaa release-notes");
|
|
102
|
+
process.exit(ExitCode.ERROR);
|
|
103
|
+
}
|
|
104
|
+
diffPayload = {
|
|
105
|
+
new: innerData.new,
|
|
106
|
+
fixed: innerData.fixed,
|
|
107
|
+
present: innerData.present || null,
|
|
108
|
+
summary: innerData.summary || {
|
|
109
|
+
newCount: innerData.new.length,
|
|
110
|
+
fixedCount: innerData.fixed.length,
|
|
111
|
+
presentCount: Array.isArray(innerData.present) ? innerData.present.length : null,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// Auth check
|
|
116
|
+
const base = resolveApiBase(globalOpts.base);
|
|
117
|
+
const apiKey = getApiKey(config.apiKey);
|
|
118
|
+
// Call LLM release-notes API
|
|
119
|
+
const spinner = createSpinner("Generating release notes...");
|
|
120
|
+
try {
|
|
121
|
+
const response = await Promise.race([
|
|
122
|
+
apiRequest(base, "/cli/ai/release-notes", { method: "POST", body: { diff: diffPayload } }, apiKey),
|
|
123
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
|
|
124
|
+
]);
|
|
125
|
+
succeedSpinner(spinner, "Notes ready");
|
|
126
|
+
if (format === "json") {
|
|
127
|
+
writeJsonOutput(response.data, "release-notes");
|
|
128
|
+
}
|
|
129
|
+
else if (format === "markdown") {
|
|
130
|
+
writeOutput(formatReleaseNotesMarkdown(response.data));
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
writeOutput(formatReleaseNotesHuman(response.data));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
handleAiCommandError(error, "release-notes", spinner);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
142
|
+
process.exit(ExitCode.ERROR);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suggest command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Converts natural language intent into exact CLI command(s).
|
|
5
|
+
* Uses local command catalog matching first, with API fallback
|
|
6
|
+
* for complex intents.
|
|
7
|
+
*
|
|
8
|
+
* Examples:
|
|
9
|
+
* vertaa suggest "check contrast issues"
|
|
10
|
+
* vertaa suggest "compare two pages"
|
|
11
|
+
* vertaa suggest "set up CI quality gate"
|
|
12
|
+
*/
|
|
13
|
+
import { Command } from "commander";
|
|
14
|
+
/**
|
|
15
|
+
* Register the suggest command with the Commander program.
|
|
16
|
+
*/
|
|
17
|
+
export declare function registerSuggestCommand(program: Command): void;
|
|
18
|
+
//# sourceMappingURL=suggest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggest.d.ts","sourceRoot":"","sources":["../../src/commands/suggest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoDpC;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAqH7D"}
|