@vertaaux/cli 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/MIGRATION.md +239 -0
- package/README.md +34 -16
- package/dist/app/interactive-app.d.ts +101 -0
- package/dist/app/interactive-app.d.ts.map +1 -0
- package/dist/app/interactive-app.js +309 -0
- package/dist/app/layout/canvas.d.ts +23 -0
- package/dist/app/layout/canvas.d.ts.map +1 -0
- package/dist/app/layout/canvas.js +36 -0
- package/dist/app/layout/footer.d.ts +31 -0
- package/dist/app/layout/footer.d.ts.map +1 -0
- package/dist/app/layout/footer.js +41 -0
- package/dist/app/layout/header.d.ts +20 -0
- package/dist/app/layout/header.d.ts.map +1 -0
- package/dist/app/layout/header.js +27 -0
- package/dist/app/menu/categories.d.ts +20 -0
- package/dist/app/menu/categories.d.ts.map +1 -0
- package/dist/app/menu/categories.js +181 -0
- package/dist/app/menu/filter.d.ts +17 -0
- package/dist/app/menu/filter.d.ts.map +1 -0
- package/dist/app/menu/filter.js +33 -0
- package/dist/app/menu/menu-view.d.ts +35 -0
- package/dist/app/menu/menu-view.d.ts.map +1 -0
- package/dist/app/menu/menu-view.js +230 -0
- package/dist/app/menu/recent.d.ts +24 -0
- package/dist/app/menu/recent.d.ts.map +1 -0
- package/dist/app/menu/recent.js +49 -0
- package/dist/app/types.d.ts +43 -0
- package/dist/app/types.d.ts.map +1 -0
- package/dist/app/types.js +7 -0
- package/dist/app/views/command-runner.d.ts +36 -0
- package/dist/app/views/command-runner.d.ts.map +1 -0
- package/dist/app/views/command-runner.js +372 -0
- package/dist/app/views/help-overlay.d.ts +21 -0
- package/dist/app/views/help-overlay.d.ts.map +1 -0
- package/dist/app/views/help-overlay.js +45 -0
- package/dist/auth/ci-token.d.ts +8 -2
- package/dist/auth/ci-token.d.ts.map +1 -1
- package/dist/auth/ci-token.js +15 -30
- package/dist/auth/device-flow.d.ts +2 -1
- package/dist/auth/device-flow.d.ts.map +1 -1
- package/dist/auth/device-flow.js +13 -10
- package/dist/auth/token-store.d.ts.map +1 -1
- package/dist/auth/token-store.js +12 -2
- package/dist/baseline/diff.d.ts +2 -2
- package/dist/baseline/diff.d.ts.map +1 -1
- package/dist/baseline/diff.js +15 -34
- package/dist/commands/a11y.d.ts +9 -0
- package/dist/commands/a11y.d.ts.map +1 -0
- package/dist/commands/a11y.js +76 -0
- package/dist/commands/audit/artifacts.d.ts +27 -0
- package/dist/commands/audit/artifacts.d.ts.map +1 -0
- package/dist/commands/audit/artifacts.js +158 -0
- package/dist/commands/audit/ci-detection.d.ts +18 -0
- package/dist/commands/audit/ci-detection.d.ts.map +1 -0
- package/dist/commands/audit/ci-detection.js +71 -0
- package/dist/commands/audit/explain.d.ts +11 -0
- package/dist/commands/audit/explain.d.ts.map +1 -0
- package/dist/commands/audit/explain.js +45 -0
- package/dist/commands/audit/filters.d.ts +17 -0
- package/dist/commands/audit/filters.d.ts.map +1 -0
- package/dist/commands/audit/filters.js +40 -0
- package/dist/commands/audit/index.d.ts +18 -0
- package/dist/commands/audit/index.d.ts.map +1 -0
- package/dist/commands/audit/index.js +564 -0
- package/dist/commands/audit/output.d.ts +32 -0
- package/dist/commands/audit/output.d.ts.map +1 -0
- package/dist/commands/audit/output.js +130 -0
- package/dist/commands/audit/policy.d.ts +19 -0
- package/dist/commands/audit/policy.d.ts.map +1 -0
- package/dist/commands/audit/policy.js +102 -0
- package/dist/commands/audit/scoring.d.ts +23 -0
- package/dist/commands/audit/scoring.d.ts.map +1 -0
- package/dist/commands/audit/scoring.js +70 -0
- package/dist/commands/audit/types.d.ts +88 -0
- package/dist/commands/audit/types.d.ts.map +1 -0
- package/dist/commands/audit/types.js +8 -0
- package/dist/commands/audit.d.ts +2 -60
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +2 -1097
- package/dist/commands/baseline.d.ts +1 -0
- package/dist/commands/baseline.d.ts.map +1 -1
- package/dist/commands/baseline.js +205 -121
- package/dist/commands/comment.d.ts +22 -0
- package/dist/commands/comment.d.ts.map +1 -1
- package/dist/commands/comment.js +122 -58
- package/dist/commands/compare.d.ts +17 -0
- package/dist/commands/compare.d.ts.map +1 -1
- package/dist/commands/compare.js +287 -180
- package/dist/commands/diff.d.ts +5 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +168 -141
- package/dist/commands/doc.d.ts +10 -0
- package/dist/commands/doc.d.ts.map +1 -1
- package/dist/commands/doc.js +134 -76
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +164 -17
- package/dist/commands/download.d.ts +10 -0
- package/dist/commands/download.d.ts.map +1 -1
- package/dist/commands/download.js +169 -112
- package/dist/commands/explain.d.ts +5 -0
- package/dist/commands/explain.d.ts.map +1 -1
- package/dist/commands/explain.js +241 -155
- package/dist/commands/fix-all.d.ts +25 -0
- package/dist/commands/fix-all.d.ts.map +1 -0
- package/dist/commands/fix-all.js +206 -0
- package/dist/commands/fix-plan.d.ts +9 -0
- package/dist/commands/fix-plan.d.ts.map +1 -1
- package/dist/commands/fix-plan.js +152 -89
- package/dist/commands/fix.d.ts +17 -0
- package/dist/commands/fix.d.ts.map +1 -0
- package/dist/commands/fix.js +111 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +94 -42
- package/dist/commands/login.d.ts +18 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +263 -92
- package/dist/commands/patch-review.d.ts +11 -0
- package/dist/commands/patch-review.d.ts.map +1 -1
- package/dist/commands/patch-review.js +159 -97
- package/dist/commands/policy.d.ts +31 -0
- package/dist/commands/policy.d.ts.map +1 -1
- package/dist/commands/policy.js +269 -124
- package/dist/commands/release-notes.d.ts +10 -0
- package/dist/commands/release-notes.d.ts.map +1 -1
- package/dist/commands/release-notes.js +127 -73
- package/dist/commands/scan.d.ts +13 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +133 -0
- package/dist/commands/status.d.ts +9 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +81 -0
- package/dist/commands/suggest.d.ts +10 -0
- package/dist/commands/suggest.d.ts.map +1 -1
- package/dist/commands/suggest.js +153 -82
- package/dist/commands/triage.d.ts +35 -0
- package/dist/commands/triage.d.ts.map +1 -1
- package/dist/commands/triage.js +206 -81
- package/dist/commands/upload.d.ts +9 -0
- package/dist/commands/upload.d.ts.map +1 -1
- package/dist/commands/upload.js +140 -101
- package/dist/commands/verify.d.ts +13 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +118 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -990
- package/dist/interactive/fix-wizard.d.ts +3 -0
- package/dist/interactive/fix-wizard.d.ts.map +1 -1
- package/dist/interactive/fix-wizard.js +130 -112
- package/dist/interactive/init-wizard.d.ts +3 -1
- package/dist/interactive/init-wizard.d.ts.map +1 -1
- package/dist/interactive/init-wizard.js +207 -138
- package/dist/interactive/prompts.d.ts +7 -3
- package/dist/interactive/prompts.d.ts.map +1 -1
- package/dist/interactive/prompts.js +44 -23
- package/dist/output/envelope.d.ts +2 -0
- package/dist/output/envelope.d.ts.map +1 -1
- package/dist/output/envelope.js +18 -2
- package/dist/output/factory.d.ts +2 -1
- package/dist/output/factory.d.ts.map +1 -1
- package/dist/output/html.d.ts +2 -1
- package/dist/output/html.d.ts.map +1 -1
- package/dist/output/html.js +3 -2
- package/dist/output/human.d.ts +2 -1
- package/dist/output/human.d.ts.map +1 -1
- package/dist/output/human.js +3 -2
- package/dist/output/json.d.ts +2 -1
- package/dist/output/json.d.ts.map +1 -1
- package/dist/output/junit.d.ts +2 -1
- package/dist/output/junit.d.ts.map +1 -1
- package/dist/output/sarif.d.ts +2 -1
- package/dist/output/sarif.d.ts.map +1 -1
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/ui/banner.d.ts +34 -0
- package/dist/ui/banner.d.ts.map +1 -1
- package/dist/ui/banner.js +97 -5
- package/dist/ui/diagnostics.d.ts +9 -4
- package/dist/ui/diagnostics.d.ts.map +1 -1
- package/dist/ui/diagnostics.js +32 -82
- package/dist/ui/strings.d.ts +373 -0
- package/dist/ui/strings.d.ts.map +1 -0
- package/dist/ui/strings.js +499 -0
- package/dist/ui/table.d.ts +0 -2
- package/dist/ui/table.d.ts.map +1 -1
- package/dist/ui/table.js +3 -4
- package/dist/utils/api-client.d.ts +46 -0
- package/dist/utils/api-client.d.ts.map +1 -0
- package/dist/utils/api-client.js +170 -0
- package/dist/utils/client.d.ts +29 -18
- package/dist/utils/client.d.ts.map +1 -1
- package/dist/utils/client.js +102 -12
- package/dist/utils/formatters.d.ts +38 -0
- package/dist/utils/formatters.d.ts.map +1 -0
- package/dist/utils/formatters.js +277 -0
- package/dist/utils/url-classify.d.ts.map +1 -1
- package/dist/utils/url-classify.js +24 -3
- package/node_modules/@vertaaux/tui/dist/index.cjs +713 -20
- package/node_modules/@vertaaux/tui/dist/index.cjs.map +1 -1
- package/node_modules/@vertaaux/tui/dist/index.d.cts +361 -4
- package/node_modules/@vertaaux/tui/dist/index.d.ts +361 -4
- package/node_modules/@vertaaux/tui/dist/index.js +689 -21
- package/node_modules/@vertaaux/tui/dist/index.js.map +1 -1
- package/package.json +13 -5
- package/dist/commands/client.d.ts +0 -14
- package/dist/commands/client.d.ts.map +0 -1
- package/dist/commands/client.js +0 -362
- package/dist/commands/drift.d.ts +0 -15
- package/dist/commands/drift.d.ts.map +0 -1
- package/dist/commands/drift.js +0 -309
- package/dist/commands/protect.d.ts +0 -16
- package/dist/commands/protect.d.ts.map +0 -1
- package/dist/commands/protect.js +0 -323
- package/dist/commands/report.d.ts +0 -15
- package/dist/commands/report.d.ts.map +0 -1
- package/dist/commands/report.js +0 -214
- package/dist/policy/sync.d.ts +0 -67
- package/dist/policy/sync.d.ts.map +0 -1
- package/dist/policy/sync.js +0 -147
package/dist/commands/drift.js
DELETED
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Drift detection command for VertaaUX CLI.
|
|
3
|
-
*
|
|
4
|
-
* Compares current audit scores against a baseline and reports
|
|
5
|
-
* per-category regressions with delta magnitudes. Used in local
|
|
6
|
-
* workflows and CI pipelines to detect score regressions before merging.
|
|
7
|
-
*
|
|
8
|
-
* Implements DRIFT-01: CLI drift check command.
|
|
9
|
-
*/
|
|
10
|
-
import fs from "fs";
|
|
11
|
-
import path from "path";
|
|
12
|
-
import chalk from "chalk";
|
|
13
|
-
import { computeDrift, DEFAULT_DRIFT_CONFIG, isPolicyV2, } from "@vertaaux/quality-control";
|
|
14
|
-
import { loadBaseline } from "../baseline/manager.js";
|
|
15
|
-
import { ExitCode } from "../utils/exit-codes.js";
|
|
16
|
-
import { writeJsonOutput, writeOutput } from "../output/envelope.js";
|
|
17
|
-
import { resolveCommandFormat } from "../output/formats.js";
|
|
18
|
-
import { loadPolicy } from "../policy/index.js";
|
|
19
|
-
/**
|
|
20
|
-
* Extract numeric scores from an audit result object.
|
|
21
|
-
*
|
|
22
|
-
* Audit results store scores as Record<string, unknown>; this extracts
|
|
23
|
-
* only the numeric entries.
|
|
24
|
-
*/
|
|
25
|
-
function extractNumericScores(scores) {
|
|
26
|
-
if (!scores)
|
|
27
|
-
return {};
|
|
28
|
-
const result = {};
|
|
29
|
-
for (const [key, value] of Object.entries(scores)) {
|
|
30
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
31
|
-
result[key] = value;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return result;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Format a signed delta value for display.
|
|
38
|
-
*/
|
|
39
|
-
function formatDelta(delta) {
|
|
40
|
-
if (delta > 0)
|
|
41
|
-
return `+${delta}`;
|
|
42
|
-
if (delta < 0)
|
|
43
|
-
return `${delta}`;
|
|
44
|
-
return "0";
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Pad a string to the right to a given width.
|
|
48
|
-
*/
|
|
49
|
-
function padRight(str, width) {
|
|
50
|
-
return str.length >= width ? str : str + " ".repeat(width - str.length);
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Pad a string to the left to a given width.
|
|
54
|
-
*/
|
|
55
|
-
function padLeft(str, width) {
|
|
56
|
-
return str.length >= width ? str : " ".repeat(width - str.length) + str;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Build drift config from policy file if available.
|
|
60
|
-
*
|
|
61
|
-
* If the policy is v2 and has a delta config, extracts max_regression
|
|
62
|
-
* and per_analyzer settings. Otherwise falls back to DEFAULT_DRIFT_CONFIG.
|
|
63
|
-
*/
|
|
64
|
-
function buildDriftConfigFromPolicy(policy) {
|
|
65
|
-
if (!policy)
|
|
66
|
-
return {};
|
|
67
|
-
// Only v2 policies have delta config
|
|
68
|
-
if (policy.version !== 2)
|
|
69
|
-
return {};
|
|
70
|
-
const v2 = policy;
|
|
71
|
-
if (!v2.delta)
|
|
72
|
-
return {};
|
|
73
|
-
const config = {};
|
|
74
|
-
if (v2.delta.max_regression !== undefined) {
|
|
75
|
-
config.max_regression = v2.delta.max_regression;
|
|
76
|
-
}
|
|
77
|
-
if (v2.delta.per_analyzer) {
|
|
78
|
-
config.per_analyzer = v2.delta.per_analyzer;
|
|
79
|
-
}
|
|
80
|
-
return config;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Format the human-readable drift report.
|
|
84
|
-
*/
|
|
85
|
-
function formatDriftReport(result) {
|
|
86
|
-
const lines = [];
|
|
87
|
-
lines.push("");
|
|
88
|
-
lines.push(chalk.bold(`Drift Detection: ${result.url}`));
|
|
89
|
-
lines.push("");
|
|
90
|
-
// Build table data from both regressions and improvements, plus stable categories
|
|
91
|
-
const allCategories = new Set();
|
|
92
|
-
const categoryData = {};
|
|
93
|
-
for (const r of result.regressions) {
|
|
94
|
-
allCategories.add(r.category);
|
|
95
|
-
categoryData[r.category] = {
|
|
96
|
-
previous: r.previous,
|
|
97
|
-
current: r.current,
|
|
98
|
-
delta: -(r.delta), // delta in regressions is positive (previous - current), display as negative
|
|
99
|
-
status: r.exceeded ? "REGRESSION (exceeded)" : "Within tolerance",
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
for (const imp of result.improvements) {
|
|
103
|
-
allCategories.add(imp.category);
|
|
104
|
-
categoryData[imp.category] = {
|
|
105
|
-
previous: imp.previous,
|
|
106
|
-
current: imp.current,
|
|
107
|
-
delta: -(imp.delta), // delta in improvements is negative (previous - current), display as positive
|
|
108
|
-
status: "Improved",
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
// Table header
|
|
112
|
-
const catWidth = 22;
|
|
113
|
-
const numWidth = 10;
|
|
114
|
-
const deltaWidth = 8;
|
|
115
|
-
const statusWidth = 26;
|
|
116
|
-
const header = padRight("Category", catWidth) +
|
|
117
|
-
padLeft("Previous", numWidth) +
|
|
118
|
-
padLeft("Current", numWidth) +
|
|
119
|
-
padLeft("Delta", deltaWidth) +
|
|
120
|
-
" " +
|
|
121
|
-
padRight("Status", statusWidth);
|
|
122
|
-
lines.push(chalk.dim(header));
|
|
123
|
-
lines.push(chalk.dim("-".repeat(catWidth + numWidth * 2 + deltaWidth + 2 + statusWidth)));
|
|
124
|
-
// Sort categories alphabetically
|
|
125
|
-
const sortedCategories = [...allCategories].sort();
|
|
126
|
-
for (const category of sortedCategories) {
|
|
127
|
-
const data = categoryData[category];
|
|
128
|
-
if (!data)
|
|
129
|
-
continue;
|
|
130
|
-
const deltaStr = formatDelta(data.delta);
|
|
131
|
-
let statusStr;
|
|
132
|
-
let deltaDisplay;
|
|
133
|
-
if (data.status === "REGRESSION (exceeded)") {
|
|
134
|
-
statusStr = chalk.red(data.status);
|
|
135
|
-
deltaDisplay = chalk.red(deltaStr);
|
|
136
|
-
}
|
|
137
|
-
else if (data.status === "Within tolerance") {
|
|
138
|
-
statusStr = chalk.yellow(data.status);
|
|
139
|
-
deltaDisplay = chalk.yellow(deltaStr);
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
statusStr = chalk.green(data.status);
|
|
143
|
-
deltaDisplay = chalk.green(deltaStr);
|
|
144
|
-
}
|
|
145
|
-
const row = padRight(category, catWidth) +
|
|
146
|
-
padLeft(String(data.previous), numWidth) +
|
|
147
|
-
padLeft(String(data.current), numWidth) +
|
|
148
|
-
padLeft(deltaDisplay, deltaWidth + 10) + // Extra for ANSI codes
|
|
149
|
-
" " +
|
|
150
|
-
statusStr;
|
|
151
|
-
lines.push(row);
|
|
152
|
-
}
|
|
153
|
-
if (sortedCategories.length === 0) {
|
|
154
|
-
lines.push(chalk.dim(" No category changes detected."));
|
|
155
|
-
}
|
|
156
|
-
lines.push("");
|
|
157
|
-
// Overall summary
|
|
158
|
-
const directionLabel = result.overall.direction === "improving"
|
|
159
|
-
? chalk.green("Improving")
|
|
160
|
-
: result.overall.direction === "regressing"
|
|
161
|
-
? chalk.red("Regressing")
|
|
162
|
-
: chalk.dim("Stable");
|
|
163
|
-
lines.push(`Overall: ${directionLabel} (magnitude: ${result.overall.magnitude})`);
|
|
164
|
-
// Alerts summary
|
|
165
|
-
if (result.alerts.length > 0) {
|
|
166
|
-
const criticalCount = result.alerts.filter((a) => a.severity === "critical").length;
|
|
167
|
-
const warningCount = result.alerts.filter((a) => a.severity === "warning").length;
|
|
168
|
-
const parts = [];
|
|
169
|
-
if (criticalCount > 0)
|
|
170
|
-
parts.push(chalk.red(`${criticalCount} critical`));
|
|
171
|
-
if (warningCount > 0)
|
|
172
|
-
parts.push(chalk.yellow(`${warningCount} warning`));
|
|
173
|
-
lines.push(`Alerts: ${parts.join(", ")} regression${result.alerts.length !== 1 ? "s" : ""} exceeded tolerance`);
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
lines.push(chalk.green("Alerts: None -- all categories within tolerance"));
|
|
177
|
-
}
|
|
178
|
-
lines.push("");
|
|
179
|
-
return lines.join("\n");
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Register the drift command with the Commander program.
|
|
183
|
-
*/
|
|
184
|
-
export function registerDriftCommand(program) {
|
|
185
|
-
const drift = program
|
|
186
|
-
.command("drift")
|
|
187
|
-
.description("Drift detection and regression analysis");
|
|
188
|
-
drift
|
|
189
|
-
.command("check <url>")
|
|
190
|
-
.description("Compare current audit against baseline and report regressions")
|
|
191
|
-
.option("--baseline <path>", "Path to baseline audit results JSON")
|
|
192
|
-
.option("--input <path>", "Path to current audit results JSON (default: .vertaaux/latest-audit.json)")
|
|
193
|
-
.option("--policy <file>", "Policy file with drift thresholds (vertaa.policy.yml)")
|
|
194
|
-
.option("--format <type>", "Output format: human|json")
|
|
195
|
-
.option("--json", "Alias for --format json")
|
|
196
|
-
.action(async (url, options) => {
|
|
197
|
-
try {
|
|
198
|
-
// a. Resolve output format (--json is alias for --format json)
|
|
199
|
-
const explicitFormat = options.json ? "json" : options.format;
|
|
200
|
-
const format = resolveCommandFormat("drift", explicitFormat, false);
|
|
201
|
-
// b. Load baseline
|
|
202
|
-
let baselineData;
|
|
203
|
-
if (options.baseline) {
|
|
204
|
-
baselineData = await loadBaseline(options.baseline);
|
|
205
|
-
if (!baselineData) {
|
|
206
|
-
process.stderr.write(chalk.red(`Error: Baseline file not found: ${options.baseline}\n`));
|
|
207
|
-
process.exit(ExitCode.ERROR);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
baselineData = await loadBaseline();
|
|
212
|
-
}
|
|
213
|
-
if (!baselineData) {
|
|
214
|
-
process.stderr.write(chalk.red("Error: No baseline found. Create one first:\n"));
|
|
215
|
-
process.stderr.write(" vertaa baseline save <url>\n\n");
|
|
216
|
-
process.stderr.write("Or provide a baseline file:\n");
|
|
217
|
-
process.stderr.write(" vertaa drift check <url> --baseline path/to/baseline.json\n");
|
|
218
|
-
process.exit(ExitCode.ERROR);
|
|
219
|
-
}
|
|
220
|
-
// c. Extract previous scores from baseline
|
|
221
|
-
// BaselineFile doesn't have scores directly -- we need audit results with scores.
|
|
222
|
-
// The baseline contains issues, not scores. We need a separate source for previous scores.
|
|
223
|
-
// Look for scores in the baseline file (it may have been extended with scores field).
|
|
224
|
-
const baselineAny = baselineData;
|
|
225
|
-
const previousScores = extractNumericScores(baselineAny.scores ??
|
|
226
|
-
baselineAny.metadata?.scores);
|
|
227
|
-
if (Object.keys(previousScores).length === 0) {
|
|
228
|
-
process.stderr.write(chalk.red("Error: Baseline does not contain score data.\n"));
|
|
229
|
-
process.stderr.write("Re-save the baseline from an audit that includes scores:\n");
|
|
230
|
-
process.stderr.write(" vertaa baseline save <url>\n");
|
|
231
|
-
process.exit(ExitCode.ERROR);
|
|
232
|
-
}
|
|
233
|
-
// d. Obtain current audit scores
|
|
234
|
-
const inputPath = options.input ?? ".vertaaux/latest-audit.json";
|
|
235
|
-
const resolvedInput = path.resolve(process.cwd(), inputPath);
|
|
236
|
-
if (!fs.existsSync(resolvedInput)) {
|
|
237
|
-
if (options.input) {
|
|
238
|
-
process.stderr.write(chalk.red(`Error: Input file not found: ${resolvedInput}\n`));
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
process.stderr.write(chalk.red("Error: No current audit results found.\n\n"));
|
|
242
|
-
process.stderr.write("Run an audit first:\n");
|
|
243
|
-
process.stderr.write(" vertaa audit <url>\n\n");
|
|
244
|
-
process.stderr.write("Or provide results explicitly:\n");
|
|
245
|
-
process.stderr.write(" vertaa drift check <url> --input path/to/audit-results.json\n");
|
|
246
|
-
}
|
|
247
|
-
process.exit(ExitCode.ERROR);
|
|
248
|
-
}
|
|
249
|
-
let currentAuditData;
|
|
250
|
-
try {
|
|
251
|
-
const raw = fs.readFileSync(resolvedInput, "utf-8");
|
|
252
|
-
currentAuditData = JSON.parse(raw);
|
|
253
|
-
}
|
|
254
|
-
catch (err) {
|
|
255
|
-
process.stderr.write(chalk.red(`Error: Failed to parse audit results: ${err instanceof Error ? err.message : String(err)}\n`));
|
|
256
|
-
process.exit(ExitCode.ERROR);
|
|
257
|
-
return; // TypeScript flow control
|
|
258
|
-
}
|
|
259
|
-
// e. Extract current scores
|
|
260
|
-
const currentScores = extractNumericScores(currentAuditData.scores);
|
|
261
|
-
if (Object.keys(currentScores).length === 0) {
|
|
262
|
-
process.stderr.write(chalk.red("Error: Current audit results do not contain scores.\n"));
|
|
263
|
-
process.exit(ExitCode.ERROR);
|
|
264
|
-
}
|
|
265
|
-
// f. Load policy for drift thresholds (optional)
|
|
266
|
-
let driftConfig = {};
|
|
267
|
-
try {
|
|
268
|
-
if (options.policy) {
|
|
269
|
-
const { loadPolicyFile } = await import("../policy/index.js");
|
|
270
|
-
const policy = await loadPolicyFile(options.policy);
|
|
271
|
-
if (isPolicyV2(policy)) {
|
|
272
|
-
driftConfig = buildDriftConfigFromPolicy(policy);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
const policyResult = await loadPolicy();
|
|
277
|
-
if (policyResult.policy && isPolicyV2(policyResult.policy)) {
|
|
278
|
-
driftConfig = buildDriftConfigFromPolicy(policyResult.policy);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
catch {
|
|
283
|
-
// Policy loading is optional -- continue with defaults
|
|
284
|
-
}
|
|
285
|
-
// g. Compute drift
|
|
286
|
-
const driftResult = computeDrift(url, currentScores, previousScores, {
|
|
287
|
-
...DEFAULT_DRIFT_CONFIG,
|
|
288
|
-
...driftConfig,
|
|
289
|
-
});
|
|
290
|
-
// h. Output results
|
|
291
|
-
if (format === "json") {
|
|
292
|
-
writeJsonOutput(driftResult, "drift");
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
const report = formatDriftReport(driftResult);
|
|
296
|
-
writeOutput(report);
|
|
297
|
-
}
|
|
298
|
-
// i. Exit codes
|
|
299
|
-
const hasExceededRegressions = driftResult.alerts.length > 0;
|
|
300
|
-
if (hasExceededRegressions) {
|
|
301
|
-
process.exitCode = ExitCode.ISSUES_FOUND;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
catch (error) {
|
|
305
|
-
process.stderr.write(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}\n`));
|
|
306
|
-
process.exit(ExitCode.ERROR);
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Protect command for VertaaUX CLI.
|
|
3
|
-
*
|
|
4
|
-
* Implements the 3-step conversion flow from the command line:
|
|
5
|
-
* 1. Audit - Captures scores (from latest audit or --input)
|
|
6
|
-
* 2. Protect - Generates policy YAML with score thresholds
|
|
7
|
-
* 3. Monitor - Outputs CI snippet for continuous quality gating
|
|
8
|
-
*
|
|
9
|
-
* Usage: vertaa protect [url] [options]
|
|
10
|
-
*/
|
|
11
|
-
import { Command } from "commander";
|
|
12
|
-
/**
|
|
13
|
-
* Register the protect command with the Commander program.
|
|
14
|
-
*/
|
|
15
|
-
export declare function registerProtectCommand(program: Command): void;
|
|
16
|
-
//# sourceMappingURL=protect.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"protect.d.ts","sourceRoot":"","sources":["../../src/commands/protect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6DpC;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAqC7D"}
|
package/dist/commands/protect.js
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Protect command for VertaaUX CLI.
|
|
3
|
-
*
|
|
4
|
-
* Implements the 3-step conversion flow from the command line:
|
|
5
|
-
* 1. Audit - Captures scores (from latest audit or --input)
|
|
6
|
-
* 2. Protect - Generates policy YAML with score thresholds
|
|
7
|
-
* 3. Monitor - Outputs CI snippet for continuous quality gating
|
|
8
|
-
*
|
|
9
|
-
* Usage: vertaa protect [url] [options]
|
|
10
|
-
*/
|
|
11
|
-
import chalk from "chalk";
|
|
12
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
13
|
-
import { resolve, dirname } from "node:path";
|
|
14
|
-
import { stringify } from "yaml";
|
|
15
|
-
import { generatePolicyFromScores } from "@vertaaux/quality-control";
|
|
16
|
-
import { generateTemplate } from "../templates/index.js";
|
|
17
|
-
import { ExitCode } from "../utils/exit-codes.js";
|
|
18
|
-
import { normalizeIssues, generateFingerprint } from "@vertaaux/quality-control";
|
|
19
|
-
/**
|
|
20
|
-
* Path to the latest audit results cache.
|
|
21
|
-
*/
|
|
22
|
-
const LATEST_AUDIT_PATH = ".vertaaux/latest-audit.json";
|
|
23
|
-
/**
|
|
24
|
-
* Default baseline file path.
|
|
25
|
-
*/
|
|
26
|
-
const DEFAULT_BASELINE_PATH = ".vertaaux/baseline.json";
|
|
27
|
-
/**
|
|
28
|
-
* Maximum age (in ms) for a cached audit to be considered "recent" (1 hour).
|
|
29
|
-
*/
|
|
30
|
-
const MAX_AUDIT_AGE_MS = 60 * 60 * 1000;
|
|
31
|
-
/**
|
|
32
|
-
* Extract numeric scores from audit scores object.
|
|
33
|
-
*/
|
|
34
|
-
function extractNumericScores(scores) {
|
|
35
|
-
if (!scores)
|
|
36
|
-
return {};
|
|
37
|
-
const result = {};
|
|
38
|
-
for (const [key, value] of Object.entries(scores)) {
|
|
39
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
40
|
-
result[key] = value;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return result;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Get the rule ID from an issue for fingerprinting.
|
|
47
|
-
*/
|
|
48
|
-
function getRuleId(issue) {
|
|
49
|
-
return issue.ruleId || issue.rule_id || issue.id || "unknown";
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Register the protect command with the Commander program.
|
|
53
|
-
*/
|
|
54
|
-
export function registerProtectCommand(program) {
|
|
55
|
-
program
|
|
56
|
-
.command("protect [url]")
|
|
57
|
-
.description("Lock current scores as policy thresholds (audit -> protect -> monitor)")
|
|
58
|
-
.option("--buffer <points>", "Points below current scores for threshold", "5")
|
|
59
|
-
.option("--ci <provider>", "CI provider: github|gitlab|circleci|azure|jenkins", "github")
|
|
60
|
-
.option("--output <path>", "Policy output path", "vertaa.policy.yml")
|
|
61
|
-
.option("--no-baseline", "Skip implicit baseline creation")
|
|
62
|
-
.option("--no-ci-snippet", "Skip CI snippet generation")
|
|
63
|
-
.option("--input <path>", "Use existing audit results JSON instead of running audit")
|
|
64
|
-
.option("--format <format>", "Output format: human|json", "human")
|
|
65
|
-
.option("--force", "Overwrite existing policy file without prompting")
|
|
66
|
-
.action(async (urlArg, options) => {
|
|
67
|
-
try {
|
|
68
|
-
await executeProtect(urlArg, options);
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
process.stderr.write(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}\n`));
|
|
72
|
-
process.exit(ExitCode.ERROR);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Execute the protect flow.
|
|
78
|
-
*/
|
|
79
|
-
async function executeProtect(urlArg, options) {
|
|
80
|
-
const buffer = parseInt(options.buffer, 10);
|
|
81
|
-
if (isNaN(buffer) || buffer < 0) {
|
|
82
|
-
process.stderr.write(chalk.red("Error: --buffer must be a non-negative integer\n"));
|
|
83
|
-
process.exit(ExitCode.ERROR);
|
|
84
|
-
}
|
|
85
|
-
// ─── Step 1: Get audit results ──────────────────────────────────────
|
|
86
|
-
let auditData;
|
|
87
|
-
let auditUrl;
|
|
88
|
-
if (options.input) {
|
|
89
|
-
// Read from explicit input file
|
|
90
|
-
const inputPath = resolve(process.cwd(), options.input);
|
|
91
|
-
if (!existsSync(inputPath)) {
|
|
92
|
-
process.stderr.write(chalk.red(`Error: Input file not found: ${inputPath}\n`));
|
|
93
|
-
process.exit(ExitCode.ERROR);
|
|
94
|
-
}
|
|
95
|
-
try {
|
|
96
|
-
const raw = readFileSync(inputPath, "utf-8");
|
|
97
|
-
auditData = JSON.parse(raw);
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
process.stderr.write(chalk.red(`Error: Failed to parse input file: ${err instanceof Error ? err.message : String(err)}\n`));
|
|
101
|
-
process.exit(ExitCode.ERROR);
|
|
102
|
-
return; // TypeScript flow control
|
|
103
|
-
}
|
|
104
|
-
auditUrl = auditData.url || urlArg || "unknown";
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
// Check latest-audit.json
|
|
108
|
-
const latestPath = resolve(process.cwd(), LATEST_AUDIT_PATH);
|
|
109
|
-
if (!existsSync(latestPath)) {
|
|
110
|
-
if (urlArg) {
|
|
111
|
-
process.stderr.write(chalk.red("Error: No recent audit found.\n\n"));
|
|
112
|
-
process.stderr.write(`Run ${chalk.cyan(`vertaa audit ${urlArg}`)} first, then ${chalk.cyan(`vertaa protect ${urlArg}`)}\n`);
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
process.stderr.write(chalk.red("Error: No audit results found.\n\n"));
|
|
116
|
-
process.stderr.write(`Run ${chalk.cyan("vertaa audit <url>")} first, then ${chalk.cyan("vertaa protect <url>")}\n`);
|
|
117
|
-
process.stderr.write(`Or provide results directly: ${chalk.cyan("vertaa protect --input <audit.json>")}\n`);
|
|
118
|
-
}
|
|
119
|
-
process.exit(ExitCode.ERROR);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
try {
|
|
123
|
-
const raw = readFileSync(latestPath, "utf-8");
|
|
124
|
-
auditData = JSON.parse(raw);
|
|
125
|
-
}
|
|
126
|
-
catch (err) {
|
|
127
|
-
process.stderr.write(chalk.red(`Error: Failed to parse latest audit: ${err instanceof Error ? err.message : String(err)}\n`));
|
|
128
|
-
process.exit(ExitCode.ERROR);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
// If URL provided, check it matches the latest audit
|
|
132
|
-
if (urlArg && auditData.url && auditData.url !== urlArg) {
|
|
133
|
-
// Check if the latest audit is for a different URL
|
|
134
|
-
process.stderr.write(chalk.yellow(`Warning: Latest audit is for ${auditData.url}, not ${urlArg}\n`));
|
|
135
|
-
process.stderr.write(`Run ${chalk.cyan(`vertaa audit ${urlArg}`)} first, then ${chalk.cyan(`vertaa protect ${urlArg}`)}\n`);
|
|
136
|
-
process.exit(ExitCode.ERROR);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
// Check if the audit is recent enough (within 1 hour)
|
|
140
|
-
const auditTimestamp = auditData.completed_at || auditData.created_at;
|
|
141
|
-
if (auditTimestamp) {
|
|
142
|
-
const auditAge = Date.now() - new Date(auditTimestamp).getTime();
|
|
143
|
-
if (auditAge > MAX_AUDIT_AGE_MS) {
|
|
144
|
-
const auditAuditUrl = auditData.url || "the URL";
|
|
145
|
-
process.stderr.write(chalk.yellow(`Warning: Latest audit is ${Math.round(auditAge / 60000)} minutes old.\n`));
|
|
146
|
-
process.stderr.write(`Run ${chalk.cyan(`vertaa audit ${auditAuditUrl}`)} for fresh results, then ${chalk.cyan(`vertaa protect ${auditAuditUrl}`)}\n`);
|
|
147
|
-
process.exit(ExitCode.ERROR);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
auditUrl = auditData.url || urlArg || "unknown";
|
|
152
|
-
}
|
|
153
|
-
// Extract numeric scores
|
|
154
|
-
const scores = extractNumericScores(auditData.scores);
|
|
155
|
-
if (Object.keys(scores).length === 0) {
|
|
156
|
-
process.stderr.write(chalk.red("Error: Audit results do not contain scores.\n"));
|
|
157
|
-
process.stderr.write("Ensure the audit completed successfully before running protect.\n");
|
|
158
|
-
process.exit(ExitCode.ERROR);
|
|
159
|
-
}
|
|
160
|
-
// ─── Step 2: Generate policy ────────────────────────────────────────
|
|
161
|
-
const policy = generatePolicyFromScores({
|
|
162
|
-
scores,
|
|
163
|
-
buffer,
|
|
164
|
-
url: auditUrl,
|
|
165
|
-
});
|
|
166
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
167
|
-
const header = [
|
|
168
|
-
`# Generated by vertaa protect on ${today}`,
|
|
169
|
-
`# Thresholds set ${buffer} points below current scores`,
|
|
170
|
-
"",
|
|
171
|
-
].join("\n");
|
|
172
|
-
const yamlContent = header + stringify(policy, { lineWidth: 120 });
|
|
173
|
-
// ─── Step 3: Write policy file ──────────────────────────────────────
|
|
174
|
-
const outputPath = resolve(process.cwd(), options.output);
|
|
175
|
-
if (existsSync(outputPath) && !options.force) {
|
|
176
|
-
process.stderr.write(chalk.yellow(`Policy file already exists at ${options.output}. Use --force to overwrite.\n`));
|
|
177
|
-
process.exit(ExitCode.ERROR);
|
|
178
|
-
}
|
|
179
|
-
// Ensure directory exists
|
|
180
|
-
const outputDir = dirname(outputPath);
|
|
181
|
-
if (!existsSync(outputDir)) {
|
|
182
|
-
mkdirSync(outputDir, { recursive: true });
|
|
183
|
-
}
|
|
184
|
-
writeFileSync(outputPath, yamlContent, "utf-8");
|
|
185
|
-
// ─── Step 4: Create implicit baseline ───────────────────────────────
|
|
186
|
-
let baselineCreated = false;
|
|
187
|
-
if (options.baseline !== false) {
|
|
188
|
-
const baselinePath = resolve(process.cwd(), DEFAULT_BASELINE_PATH);
|
|
189
|
-
// Check if an explicit baseline already exists
|
|
190
|
-
if (existsSync(baselinePath)) {
|
|
191
|
-
try {
|
|
192
|
-
const existingRaw = readFileSync(baselinePath, "utf-8");
|
|
193
|
-
const existing = JSON.parse(existingRaw);
|
|
194
|
-
if (existing.source && existing.source !== "implicit") {
|
|
195
|
-
process.stderr.write(chalk.dim("Explicit baseline exists, keeping it (implicit baseline skipped)\n"));
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
// Overwrite implicit baseline with fresh one
|
|
199
|
-
const baseline = createImplicitBaseline(auditUrl, scores, auditData.issues);
|
|
200
|
-
const baselineDir = dirname(baselinePath);
|
|
201
|
-
if (!existsSync(baselineDir)) {
|
|
202
|
-
mkdirSync(baselineDir, { recursive: true });
|
|
203
|
-
}
|
|
204
|
-
writeFileSync(baselinePath, JSON.stringify(baseline, null, 2) + "\n", "utf-8");
|
|
205
|
-
baselineCreated = true;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
catch {
|
|
209
|
-
// If we can't parse existing baseline, overwrite it
|
|
210
|
-
const baseline = createImplicitBaseline(auditUrl, scores, auditData.issues);
|
|
211
|
-
const baselineDir = dirname(baselinePath);
|
|
212
|
-
if (!existsSync(baselineDir)) {
|
|
213
|
-
mkdirSync(baselineDir, { recursive: true });
|
|
214
|
-
}
|
|
215
|
-
writeFileSync(baselinePath, JSON.stringify(baseline, null, 2) + "\n", "utf-8");
|
|
216
|
-
baselineCreated = true;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
// No baseline exists -- create one
|
|
221
|
-
const baseline = createImplicitBaseline(auditUrl, scores, auditData.issues);
|
|
222
|
-
const baselineDir = dirname(baselinePath);
|
|
223
|
-
if (!existsSync(baselineDir)) {
|
|
224
|
-
mkdirSync(baselineDir, { recursive: true });
|
|
225
|
-
}
|
|
226
|
-
writeFileSync(baselinePath, JSON.stringify(baseline, null, 2) + "\n", "utf-8");
|
|
227
|
-
baselineCreated = true;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
// ─── Step 5: Generate CI snippet ───────────────────────────────────
|
|
231
|
-
let ciSnippetContent = null;
|
|
232
|
-
let ciSnippetPath = null;
|
|
233
|
-
if (options.ciSnippet !== false) {
|
|
234
|
-
const template = generateTemplate(options.ci, {
|
|
235
|
-
auditUrl: auditUrl !== "unknown" ? auditUrl : undefined,
|
|
236
|
-
threshold: policy.assertions.overall_score,
|
|
237
|
-
failOn: "error",
|
|
238
|
-
uploadSarif: true,
|
|
239
|
-
postComment: true,
|
|
240
|
-
});
|
|
241
|
-
if (template) {
|
|
242
|
-
ciSnippetContent = template.content;
|
|
243
|
-
ciSnippetPath = template.path;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
// ─── Step 6: Output ────────────────────────────────────────────────
|
|
247
|
-
if (options.format === "json") {
|
|
248
|
-
// JSON output to stdout
|
|
249
|
-
const output = {
|
|
250
|
-
url: auditUrl,
|
|
251
|
-
policy,
|
|
252
|
-
policyPath: options.output,
|
|
253
|
-
baseline: baselineCreated
|
|
254
|
-
? { path: DEFAULT_BASELINE_PATH, created: true }
|
|
255
|
-
: { path: DEFAULT_BASELINE_PATH, created: false },
|
|
256
|
-
ciSnippet: ciSnippetContent
|
|
257
|
-
? { provider: options.ci, path: ciSnippetPath, content: ciSnippetContent }
|
|
258
|
-
: null,
|
|
259
|
-
};
|
|
260
|
-
process.stdout.write(JSON.stringify(output, null, 2) + "\n");
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
// Human-readable output to stderr (status) and stdout (CI snippet)
|
|
264
|
-
process.stderr.write("\n" + chalk.green.bold("Score protected!") + "\n\n");
|
|
265
|
-
process.stderr.write(chalk.bold("What happened:") + "\n");
|
|
266
|
-
process.stderr.write(` ${chalk.cyan("1. Audit")} ${chalk.dim("-")} Scores captured for ${chalk.underline(auditUrl)}\n`);
|
|
267
|
-
process.stderr.write(` ${chalk.cyan("2. Protect")} ${chalk.dim("-")} Policy saved to ${chalk.underline(options.output)}\n`);
|
|
268
|
-
process.stderr.write(` ${chalk.cyan("3. Monitor")} ${chalk.dim("-")} CI snippet ready (paste into your pipeline)\n`);
|
|
269
|
-
// Policy thresholds
|
|
270
|
-
process.stderr.write("\n" + chalk.bold("Policy thresholds:") + "\n");
|
|
271
|
-
process.stderr.write(` Overall: ${chalk.yellow(String(policy.assertions.overall_score))}\n`);
|
|
272
|
-
if (policy.budgets) {
|
|
273
|
-
for (const [category, budget] of Object.entries(policy.budgets)) {
|
|
274
|
-
const currentScore = scores[category];
|
|
275
|
-
const scoreInfo = currentScore !== undefined
|
|
276
|
-
? chalk.dim(` (current: ${currentScore})`)
|
|
277
|
-
: "";
|
|
278
|
-
process.stderr.write(` ${category}: ${chalk.yellow(String(budget.min_score))}${scoreInfo}\n`);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
if (baselineCreated) {
|
|
282
|
-
process.stderr.write("\n" +
|
|
283
|
-
chalk.dim(`Baseline saved to ${DEFAULT_BASELINE_PATH}\n`));
|
|
284
|
-
}
|
|
285
|
-
// CI snippet
|
|
286
|
-
if (ciSnippetContent && ciSnippetPath) {
|
|
287
|
-
process.stderr.write("\n" +
|
|
288
|
-
chalk.bold(`Next step: Add this to your CI pipeline (${ciSnippetPath}):`) +
|
|
289
|
-
"\n");
|
|
290
|
-
process.stderr.write(chalk.dim("---") + "\n");
|
|
291
|
-
process.stdout.write(ciSnippetContent + "\n");
|
|
292
|
-
process.stderr.write(chalk.dim("---") + "\n");
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Create an implicit baseline from audit data.
|
|
298
|
-
*
|
|
299
|
-
* Produces a BaselineFile with source: "implicit" marker
|
|
300
|
-
* so it can be distinguished from explicit user-created baselines.
|
|
301
|
-
*/
|
|
302
|
-
function createImplicitBaseline(url, scores, rawIssues) {
|
|
303
|
-
const now = new Date().toISOString();
|
|
304
|
-
const issues = normalizeIssues(rawIssues);
|
|
305
|
-
const baselineIssues = issues.map((issue) => ({
|
|
306
|
-
fingerprint: generateFingerprint(issue),
|
|
307
|
-
ruleId: getRuleId(issue),
|
|
308
|
-
severity: issue.severity || "warning",
|
|
309
|
-
category: issue.category || "general",
|
|
310
|
-
description: issue.description || issue.title || "",
|
|
311
|
-
selector: issue.selector,
|
|
312
|
-
baselinedAt: now,
|
|
313
|
-
}));
|
|
314
|
-
return {
|
|
315
|
-
version: 1,
|
|
316
|
-
created: now,
|
|
317
|
-
updated: now,
|
|
318
|
-
url,
|
|
319
|
-
source: "implicit",
|
|
320
|
-
scores,
|
|
321
|
-
issues: baselineIssues,
|
|
322
|
-
};
|
|
323
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Report generation command for VertaaUX CLI.
|
|
3
|
-
*
|
|
4
|
-
* Generates consolidated multi-client reports by delegating
|
|
5
|
-
* data aggregation to the server-side consolidated report API.
|
|
6
|
-
* The CLI handles formatting only -- no data computation.
|
|
7
|
-
*
|
|
8
|
-
* Implements 46-06: CLI report command.
|
|
9
|
-
*/
|
|
10
|
-
import { Command } from "commander";
|
|
11
|
-
/**
|
|
12
|
-
* Register the report command with the Commander program.
|
|
13
|
-
*/
|
|
14
|
-
export declare function registerReportCommand(program: Command): void;
|
|
15
|
-
//# sourceMappingURL=report.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/commands/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwPpC;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyF5D"}
|