@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.
- package/README.md +345 -0
- package/dist/auth/ci-token.d.ts +49 -0
- package/dist/auth/ci-token.d.ts.map +1 -0
- package/dist/auth/ci-token.js +83 -0
- package/dist/auth/device-flow.d.ts +66 -0
- package/dist/auth/device-flow.d.ts.map +1 -0
- package/dist/auth/device-flow.js +156 -0
- package/dist/auth/token-store.d.ts +53 -0
- package/dist/auth/token-store.d.ts.map +1 -0
- package/dist/auth/token-store.js +78 -0
- package/dist/baseline/diff.d.ts +57 -0
- package/dist/baseline/diff.d.ts.map +1 -0
- package/dist/baseline/diff.js +152 -0
- package/dist/baseline/hash.d.ts +54 -0
- package/dist/baseline/hash.d.ts.map +1 -0
- package/dist/baseline/hash.js +66 -0
- package/dist/baseline/manager.d.ts +89 -0
- package/dist/baseline/manager.d.ts.map +1 -0
- package/dist/baseline/manager.js +157 -0
- package/dist/cache/index.d.ts +8 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +7 -0
- package/dist/cache/route-cache.d.ts +119 -0
- package/dist/cache/route-cache.d.ts.map +1 -0
- package/dist/cache/route-cache.js +213 -0
- package/dist/ci/changed-routes.d.ts +95 -0
- package/dist/ci/changed-routes.d.ts.map +1 -0
- package/dist/ci/changed-routes.js +304 -0
- package/dist/ci/github-api.d.ts +68 -0
- package/dist/ci/github-api.d.ts.map +1 -0
- package/dist/ci/github-api.js +138 -0
- package/dist/ci/gitlab-api.d.ts +75 -0
- package/dist/ci/gitlab-api.d.ts.map +1 -0
- package/dist/ci/gitlab-api.js +180 -0
- package/dist/ci/index.d.ts +6 -0
- package/dist/ci/index.d.ts.map +1 -0
- package/dist/ci/index.js +4 -0
- package/dist/commands/audit.d.ts +58 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +862 -0
- package/dist/commands/baseline.d.ts +22 -0
- package/dist/commands/baseline.d.ts.map +1 -0
- package/dist/commands/baseline.js +210 -0
- package/dist/commands/comment.d.ts +14 -0
- package/dist/commands/comment.d.ts.map +1 -0
- package/dist/commands/comment.js +363 -0
- package/dist/commands/diff.d.ts +24 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +196 -0
- package/dist/commands/doctor.d.ts +58 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +338 -0
- package/dist/commands/download.d.ts +12 -0
- package/dist/commands/download.d.ts.map +1 -0
- package/dist/commands/download.js +183 -0
- package/dist/commands/explain.d.ts +62 -0
- package/dist/commands/explain.d.ts.map +1 -0
- package/dist/commands/explain.js +302 -0
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +212 -0
- package/dist/commands/login.d.ts +14 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +222 -0
- package/dist/commands/policy.d.ts +13 -0
- package/dist/commands/policy.d.ts.map +1 -0
- package/dist/commands/policy.js +347 -0
- package/dist/commands/upload.d.ts +12 -0
- package/dist/commands/upload.d.ts.map +1 -0
- package/dist/commands/upload.js +158 -0
- package/dist/config/defaults.d.ts +21 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +49 -0
- package/dist/config/loader.d.ts +66 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +167 -0
- package/dist/config/schema.d.ts +55 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +6 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1090 -0
- package/dist/interactive/fix-wizard.d.ts +44 -0
- package/dist/interactive/fix-wizard.d.ts.map +1 -0
- package/dist/interactive/fix-wizard.js +286 -0
- package/dist/interactive/init-wizard.d.ts +32 -0
- package/dist/interactive/init-wizard.d.ts.map +1 -0
- package/dist/interactive/init-wizard.js +193 -0
- package/dist/interactive/prompts.d.ts +62 -0
- package/dist/interactive/prompts.d.ts.map +1 -0
- package/dist/interactive/prompts.js +78 -0
- package/dist/monorepo/detector.d.ts +70 -0
- package/dist/monorepo/detector.d.ts.map +1 -0
- package/dist/monorepo/detector.js +278 -0
- package/dist/monorepo/index.d.ts +9 -0
- package/dist/monorepo/index.d.ts.map +1 -0
- package/dist/monorepo/index.js +8 -0
- package/dist/monorepo/workspace.d.ts +142 -0
- package/dist/monorepo/workspace.d.ts.map +1 -0
- package/dist/monorepo/workspace.js +171 -0
- package/dist/output/envelope.d.ts +21 -0
- package/dist/output/envelope.d.ts.map +1 -0
- package/dist/output/envelope.js +27 -0
- package/dist/output/factory.d.ts +73 -0
- package/dist/output/factory.d.ts.map +1 -0
- package/dist/output/factory.js +60 -0
- package/dist/output/formats.d.ts +11 -0
- package/dist/output/formats.d.ts.map +1 -0
- package/dist/output/formats.js +41 -0
- package/dist/output/html.d.ts +45 -0
- package/dist/output/html.d.ts.map +1 -0
- package/dist/output/html.js +607 -0
- package/dist/output/human.d.ts +41 -0
- package/dist/output/human.d.ts.map +1 -0
- package/dist/output/human.js +274 -0
- package/dist/output/json.d.ts +42 -0
- package/dist/output/json.d.ts.map +1 -0
- package/dist/output/json.js +37 -0
- package/dist/output/junit.d.ts +56 -0
- package/dist/output/junit.d.ts.map +1 -0
- package/dist/output/junit.js +135 -0
- package/dist/output/markdown.d.ts +77 -0
- package/dist/output/markdown.d.ts.map +1 -0
- package/dist/output/markdown.js +411 -0
- package/dist/output/sarif.d.ts +160 -0
- package/dist/output/sarif.d.ts.map +1 -0
- package/dist/output/sarif.js +207 -0
- package/dist/policy/evaluator.d.ts +111 -0
- package/dist/policy/evaluator.d.ts.map +1 -0
- package/dist/policy/evaluator.js +362 -0
- package/dist/policy/index.d.ts +15 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +11 -0
- package/dist/policy/loader.d.ts +97 -0
- package/dist/policy/loader.d.ts.map +1 -0
- package/dist/policy/loader.js +281 -0
- package/dist/policy/schema.d.ts +297 -0
- package/dist/policy/schema.d.ts.map +1 -0
- package/dist/policy/schema.js +230 -0
- package/dist/quality-gate/evaluator.d.ts +58 -0
- package/dist/quality-gate/evaluator.d.ts.map +1 -0
- package/dist/quality-gate/evaluator.js +274 -0
- package/dist/quality-gate/index.d.ts +10 -0
- package/dist/quality-gate/index.d.ts.map +1 -0
- package/dist/quality-gate/index.js +7 -0
- package/dist/quality-gate/types.d.ts +103 -0
- package/dist/quality-gate/types.d.ts.map +1 -0
- package/dist/quality-gate/types.js +23 -0
- package/dist/templates/azure-devops.d.ts +25 -0
- package/dist/templates/azure-devops.d.ts.map +1 -0
- package/dist/templates/azure-devops.js +109 -0
- package/dist/templates/circleci.d.ts +28 -0
- package/dist/templates/circleci.d.ts.map +1 -0
- package/dist/templates/circleci.js +86 -0
- package/dist/templates/github-actions.d.ts +81 -0
- package/dist/templates/github-actions.d.ts.map +1 -0
- package/dist/templates/github-actions.js +393 -0
- package/dist/templates/gitlab-ci.d.ts +26 -0
- package/dist/templates/gitlab-ci.d.ts.map +1 -0
- package/dist/templates/gitlab-ci.js +70 -0
- package/dist/templates/index.d.ts +72 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +112 -0
- package/dist/templates/jenkins.d.ts +26 -0
- package/dist/templates/jenkins.d.ts.map +1 -0
- package/dist/templates/jenkins.js +110 -0
- package/dist/ui/banner.d.ts +31 -0
- package/dist/ui/banner.d.ts.map +1 -0
- package/dist/ui/banner.js +84 -0
- package/dist/ui/diagnostics.d.ts +39 -0
- package/dist/ui/diagnostics.d.ts.map +1 -0
- package/dist/ui/diagnostics.js +153 -0
- package/dist/ui/spinner.d.ts +61 -0
- package/dist/ui/spinner.d.ts.map +1 -0
- package/dist/ui/spinner.js +101 -0
- package/dist/ui/table.d.ts +63 -0
- package/dist/ui/table.d.ts.map +1 -0
- package/dist/ui/table.js +236 -0
- package/dist/utils/client.d.ts +82 -0
- package/dist/utils/client.d.ts.map +1 -0
- package/dist/utils/client.js +128 -0
- package/dist/utils/detect-env.d.ts +59 -0
- package/dist/utils/detect-env.d.ts.map +1 -0
- package/dist/utils/detect-env.js +115 -0
- package/dist/utils/exit-codes.d.ts +47 -0
- package/dist/utils/exit-codes.d.ts.map +1 -0
- package/dist/utils/exit-codes.js +61 -0
- package/dist/utils/logger.d.ts +87 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +185 -0
- package/dist/utils/sanitize.d.ts +36 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +64 -0
- package/dist/utils/validators.d.ts +41 -0
- package/dist/utils/validators.d.ts.map +1 -0
- package/dist/utils/validators.js +123 -0
- package/package.json +63 -0
- package/schemas/vertaaux.config.schema.json +103 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy file schema for vertaa.policy.yml.
|
|
3
|
+
*
|
|
4
|
+
* Enables policy-as-code for organizations to define:
|
|
5
|
+
* - Quality thresholds and assertions
|
|
6
|
+
* - Rule severity overrides
|
|
7
|
+
* - Branch-specific policies
|
|
8
|
+
* - Bypass labels for emergency deploys
|
|
9
|
+
*
|
|
10
|
+
* Modeled on Semgrep/ESLint policy patterns.
|
|
11
|
+
*
|
|
12
|
+
* Implements CICD-17: Policy-as-code support.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* JSON Schema for vertaa.policy.yml validation.
|
|
16
|
+
*
|
|
17
|
+
* Used for:
|
|
18
|
+
* - Runtime validation with Ajv
|
|
19
|
+
* - IDE autocompletion and validation
|
|
20
|
+
* - Documentation generation
|
|
21
|
+
*/
|
|
22
|
+
export const policyJsonSchema = {
|
|
23
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
24
|
+
$id: "https://vertaaux.ai/schemas/policy.json",
|
|
25
|
+
title: "VertaaUX Policy",
|
|
26
|
+
description: "Policy-as-code configuration for VertaaUX quality gates. Commit this file to your repository to define organization-wide UX standards.",
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
$schema: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "JSON Schema reference for IDE support",
|
|
32
|
+
},
|
|
33
|
+
version: {
|
|
34
|
+
type: "number",
|
|
35
|
+
enum: [1],
|
|
36
|
+
description: "Policy file format version (currently only version 1)",
|
|
37
|
+
},
|
|
38
|
+
name: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Human-readable policy name",
|
|
41
|
+
},
|
|
42
|
+
description: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "Policy description for documentation",
|
|
45
|
+
},
|
|
46
|
+
assertions: {
|
|
47
|
+
type: "object",
|
|
48
|
+
description: "Threshold assertions that must pass",
|
|
49
|
+
properties: {
|
|
50
|
+
overall_score: {
|
|
51
|
+
type: "number",
|
|
52
|
+
minimum: 0,
|
|
53
|
+
maximum: 100,
|
|
54
|
+
description: "Minimum overall score (0-100)",
|
|
55
|
+
},
|
|
56
|
+
accessibility_score: {
|
|
57
|
+
type: "number",
|
|
58
|
+
minimum: 0,
|
|
59
|
+
maximum: 100,
|
|
60
|
+
description: "Minimum accessibility score (0-100)",
|
|
61
|
+
},
|
|
62
|
+
ux_score: {
|
|
63
|
+
type: "number",
|
|
64
|
+
minimum: 0,
|
|
65
|
+
maximum: 100,
|
|
66
|
+
description: "Minimum UX score (0-100)",
|
|
67
|
+
},
|
|
68
|
+
performance_score: {
|
|
69
|
+
type: "number",
|
|
70
|
+
minimum: 0,
|
|
71
|
+
maximum: 100,
|
|
72
|
+
description: "Minimum performance score (0-100)",
|
|
73
|
+
},
|
|
74
|
+
fail_on: {
|
|
75
|
+
type: "string",
|
|
76
|
+
enum: ["error", "warning", "info"],
|
|
77
|
+
description: "Fail on issues at or above this severity",
|
|
78
|
+
},
|
|
79
|
+
max_new_errors: {
|
|
80
|
+
type: "number",
|
|
81
|
+
minimum: 0,
|
|
82
|
+
description: "Maximum new error-severity issues allowed",
|
|
83
|
+
},
|
|
84
|
+
max_new_warnings: {
|
|
85
|
+
type: "number",
|
|
86
|
+
minimum: 0,
|
|
87
|
+
description: "Maximum new warning-severity issues allowed",
|
|
88
|
+
},
|
|
89
|
+
max_new_total: {
|
|
90
|
+
type: "number",
|
|
91
|
+
minimum: 0,
|
|
92
|
+
description: "Maximum total new issues allowed",
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
additionalProperties: false,
|
|
96
|
+
},
|
|
97
|
+
rules: {
|
|
98
|
+
type: "object",
|
|
99
|
+
description: "Rule-specific severity overrides",
|
|
100
|
+
additionalProperties: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
severity: {
|
|
104
|
+
type: "string",
|
|
105
|
+
enum: ["error", "warning", "info", "ignore"],
|
|
106
|
+
description: "Override severity for this rule",
|
|
107
|
+
},
|
|
108
|
+
reason: {
|
|
109
|
+
type: "string",
|
|
110
|
+
description: "Reason for override (for audit trail)",
|
|
111
|
+
},
|
|
112
|
+
paths: {
|
|
113
|
+
type: "array",
|
|
114
|
+
items: { type: "string" },
|
|
115
|
+
description: "Apply override only to these paths (glob patterns)",
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
required: ["severity"],
|
|
119
|
+
additionalProperties: false,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
branches: {
|
|
123
|
+
type: "object",
|
|
124
|
+
description: "Branch-specific policy overrides",
|
|
125
|
+
additionalProperties: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
pattern: {
|
|
129
|
+
type: "string",
|
|
130
|
+
description: "Branch pattern (glob). If not specified, uses exact match.",
|
|
131
|
+
},
|
|
132
|
+
assertions: {
|
|
133
|
+
$ref: "#/properties/assertions",
|
|
134
|
+
description: "Override assertions for this branch",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
additionalProperties: false,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
bypass_labels: {
|
|
141
|
+
type: "array",
|
|
142
|
+
items: { type: "string" },
|
|
143
|
+
description: "PR labels that bypass quality gate entirely (e.g., emergency-fix)",
|
|
144
|
+
},
|
|
145
|
+
exclude_paths: {
|
|
146
|
+
type: "array",
|
|
147
|
+
items: { type: "string" },
|
|
148
|
+
description: "Path patterns to exclude from auditing",
|
|
149
|
+
},
|
|
150
|
+
required_version: {
|
|
151
|
+
type: "string",
|
|
152
|
+
description: "Required minimum CLI version (semver range, e.g., >=1.0.0)",
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
required: ["version", "assertions"],
|
|
156
|
+
additionalProperties: false,
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Default policy with sensible enterprise defaults.
|
|
160
|
+
*
|
|
161
|
+
* - Only fail on errors (not warnings/info)
|
|
162
|
+
* - Only fail on NEW errors (existing debt allowed)
|
|
163
|
+
* - Common emergency bypass labels
|
|
164
|
+
*/
|
|
165
|
+
export const DEFAULT_POLICY = {
|
|
166
|
+
version: 1,
|
|
167
|
+
assertions: {
|
|
168
|
+
fail_on: "error",
|
|
169
|
+
max_new_errors: 0,
|
|
170
|
+
},
|
|
171
|
+
bypass_labels: ["skip-vertaa", "emergency-fix"],
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Policy templates for different strictness levels.
|
|
175
|
+
*/
|
|
176
|
+
export const POLICY_TEMPLATES = {
|
|
177
|
+
/**
|
|
178
|
+
* Basic template - fail only on new errors.
|
|
179
|
+
*/
|
|
180
|
+
basic: {
|
|
181
|
+
version: 1,
|
|
182
|
+
name: "Basic Policy",
|
|
183
|
+
description: "Block new error-severity issues only",
|
|
184
|
+
assertions: {
|
|
185
|
+
fail_on: "error",
|
|
186
|
+
max_new_errors: 0,
|
|
187
|
+
},
|
|
188
|
+
bypass_labels: ["skip-vertaa", "emergency-fix"],
|
|
189
|
+
},
|
|
190
|
+
/**
|
|
191
|
+
* Strict template - fail on warnings, require minimum scores.
|
|
192
|
+
*/
|
|
193
|
+
strict: {
|
|
194
|
+
version: 1,
|
|
195
|
+
name: "Strict Policy",
|
|
196
|
+
description: "Maintain high quality standards with score thresholds and zero tolerance for new issues",
|
|
197
|
+
assertions: {
|
|
198
|
+
fail_on: "warning",
|
|
199
|
+
overall_score: 80,
|
|
200
|
+
max_new_errors: 0,
|
|
201
|
+
max_new_warnings: 0,
|
|
202
|
+
},
|
|
203
|
+
branches: {
|
|
204
|
+
main: {
|
|
205
|
+
assertions: {
|
|
206
|
+
overall_score: 85,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
"release/*": {
|
|
210
|
+
pattern: "release/*",
|
|
211
|
+
assertions: {
|
|
212
|
+
overall_score: 90,
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
bypass_labels: ["emergency-fix"],
|
|
217
|
+
},
|
|
218
|
+
/**
|
|
219
|
+
* Lenient template - fail only on critical errors.
|
|
220
|
+
*/
|
|
221
|
+
lenient: {
|
|
222
|
+
version: 1,
|
|
223
|
+
name: "Lenient Policy",
|
|
224
|
+
description: "Minimal quality gate for gradual adoption",
|
|
225
|
+
assertions: {
|
|
226
|
+
fail_on: "error",
|
|
227
|
+
},
|
|
228
|
+
bypass_labels: ["skip-vertaa", "emergency-fix", "wip"],
|
|
229
|
+
},
|
|
230
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality gate evaluation for CI pipelines.
|
|
3
|
+
*
|
|
4
|
+
* Determines whether a CI build should pass or fail based on audit results.
|
|
5
|
+
* Key principle: Only fail on NEW issues, not existing technical debt.
|
|
6
|
+
*
|
|
7
|
+
* Implements:
|
|
8
|
+
* - CICD-06: Fail on new issues only
|
|
9
|
+
* - CICD-07: Threshold enforcement
|
|
10
|
+
* - CICD-13: Budget-aware evaluation
|
|
11
|
+
*/
|
|
12
|
+
import type { QualityGateConfig, QualityGateResult } from "./types.js";
|
|
13
|
+
import type { BaselineFile } from "../baseline/manager.js";
|
|
14
|
+
import type { Issue } from "../baseline/hash.js";
|
|
15
|
+
/**
|
|
16
|
+
* Audit result type for quality gate evaluation.
|
|
17
|
+
*/
|
|
18
|
+
export interface AuditResultForGate {
|
|
19
|
+
/** Issues found during audit */
|
|
20
|
+
issues?: Issue[] | Record<string, Issue[]>;
|
|
21
|
+
/** Score breakdown */
|
|
22
|
+
scores?: Record<string, number | unknown>;
|
|
23
|
+
/** Any runtime error */
|
|
24
|
+
error?: unknown;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Options for quality gate evaluation.
|
|
28
|
+
*/
|
|
29
|
+
export interface EvaluateOptions {
|
|
30
|
+
/** Audit result with issues and scores */
|
|
31
|
+
auditResult: AuditResultForGate;
|
|
32
|
+
/** Baseline for comparison (optional - if missing, all issues treated as new) */
|
|
33
|
+
baseline?: BaselineFile | null;
|
|
34
|
+
/** Quality gate configuration */
|
|
35
|
+
config: QualityGateConfig;
|
|
36
|
+
/** PR labels for bypass check */
|
|
37
|
+
labels?: string[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Evaluate quality gate based on audit results.
|
|
41
|
+
*
|
|
42
|
+
* Evaluation order:
|
|
43
|
+
* 1. Check bypass labels (short-circuit if matched)
|
|
44
|
+
* 2. Compute diff against baseline (new vs existing)
|
|
45
|
+
* 3. Count new issues by severity
|
|
46
|
+
* 4. Check thresholds against scores
|
|
47
|
+
* 5. Build violations list
|
|
48
|
+
* 6. Determine exit code
|
|
49
|
+
*
|
|
50
|
+
* @param options - Evaluation options
|
|
51
|
+
* @returns Quality gate result with pass/fail status and violations
|
|
52
|
+
*/
|
|
53
|
+
export declare function evaluateQualityGate(options: EvaluateOptions): QualityGateResult;
|
|
54
|
+
/**
|
|
55
|
+
* Format quality gate result for human-readable output.
|
|
56
|
+
*/
|
|
57
|
+
export declare function formatQualityGateResult(result: QualityGateResult): string;
|
|
58
|
+
//# sourceMappingURL=evaluator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/quality-gate/evaluator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAGjD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,gCAAgC;IAChC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3C,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IAC1C,wBAAwB;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,0CAA0C;IAC1C,WAAW,EAAE,kBAAkB,CAAC;IAChC,iFAAiF;IACjF,QAAQ,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/B,iCAAiC;IACjC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAiGD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,iBAAiB,CAmJ/E;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAyCzE"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality gate evaluation for CI pipelines.
|
|
3
|
+
*
|
|
4
|
+
* Determines whether a CI build should pass or fail based on audit results.
|
|
5
|
+
* Key principle: Only fail on NEW issues, not existing technical debt.
|
|
6
|
+
*
|
|
7
|
+
* Implements:
|
|
8
|
+
* - CICD-06: Fail on new issues only
|
|
9
|
+
* - CICD-07: Threshold enforcement
|
|
10
|
+
* - CICD-13: Budget-aware evaluation
|
|
11
|
+
*/
|
|
12
|
+
import { computeDiff } from "../baseline/diff.js";
|
|
13
|
+
import { ExitCode } from "../utils/exit-codes.js";
|
|
14
|
+
/** Severity ranking for comparison */
|
|
15
|
+
const SEVERITY_RANK = {
|
|
16
|
+
error: 3,
|
|
17
|
+
critical: 3,
|
|
18
|
+
warning: 2,
|
|
19
|
+
serious: 2,
|
|
20
|
+
info: 1,
|
|
21
|
+
minor: 1,
|
|
22
|
+
moderate: 1,
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Map common severity aliases to canonical values.
|
|
26
|
+
*/
|
|
27
|
+
function normalizeSeverity(severity) {
|
|
28
|
+
const lower = (severity || "info").toLowerCase();
|
|
29
|
+
switch (lower) {
|
|
30
|
+
case "error":
|
|
31
|
+
case "critical":
|
|
32
|
+
return "error";
|
|
33
|
+
case "warning":
|
|
34
|
+
case "serious":
|
|
35
|
+
return "warning";
|
|
36
|
+
default:
|
|
37
|
+
return "info";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get severity rank for comparison.
|
|
42
|
+
*/
|
|
43
|
+
function severityRank(severity) {
|
|
44
|
+
return SEVERITY_RANK[severity.toLowerCase()] ?? 0;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Normalize issues from various API response formats to array.
|
|
48
|
+
*/
|
|
49
|
+
function normalizeIssues(issues) {
|
|
50
|
+
if (Array.isArray(issues)) {
|
|
51
|
+
return issues;
|
|
52
|
+
}
|
|
53
|
+
if (issues && typeof issues === "object") {
|
|
54
|
+
const values = Object.values(issues);
|
|
55
|
+
return values.flatMap((value) => Array.isArray(value) ? value : []);
|
|
56
|
+
}
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Count issues by normalized severity.
|
|
61
|
+
*/
|
|
62
|
+
function countBySeverity(issues) {
|
|
63
|
+
const counts = { error: 0, warning: 0, info: 0 };
|
|
64
|
+
for (const issue of issues) {
|
|
65
|
+
const normalized = normalizeSeverity(issue.severity || "info");
|
|
66
|
+
counts[normalized]++;
|
|
67
|
+
}
|
|
68
|
+
return counts;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Extract numeric scores from audit result.
|
|
72
|
+
*/
|
|
73
|
+
function extractScores(scores) {
|
|
74
|
+
const result = {};
|
|
75
|
+
if (!scores)
|
|
76
|
+
return result;
|
|
77
|
+
for (const [key, value] of Object.entries(scores)) {
|
|
78
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
79
|
+
result[key] = value;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Create empty baseline for when no baseline is provided.
|
|
86
|
+
*/
|
|
87
|
+
function createEmptyBaseline() {
|
|
88
|
+
return {
|
|
89
|
+
version: 1,
|
|
90
|
+
created: new Date().toISOString(),
|
|
91
|
+
updated: new Date().toISOString(),
|
|
92
|
+
url: "",
|
|
93
|
+
issues: [],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Evaluate quality gate based on audit results.
|
|
98
|
+
*
|
|
99
|
+
* Evaluation order:
|
|
100
|
+
* 1. Check bypass labels (short-circuit if matched)
|
|
101
|
+
* 2. Compute diff against baseline (new vs existing)
|
|
102
|
+
* 3. Count new issues by severity
|
|
103
|
+
* 4. Check thresholds against scores
|
|
104
|
+
* 5. Build violations list
|
|
105
|
+
* 6. Determine exit code
|
|
106
|
+
*
|
|
107
|
+
* @param options - Evaluation options
|
|
108
|
+
* @returns Quality gate result with pass/fail status and violations
|
|
109
|
+
*/
|
|
110
|
+
export function evaluateQualityGate(options) {
|
|
111
|
+
const { auditResult, baseline, config, labels = [] } = options;
|
|
112
|
+
// Initialize summary
|
|
113
|
+
const scores = extractScores(auditResult.scores);
|
|
114
|
+
const summary = {
|
|
115
|
+
newIssues: { error: 0, warning: 0, info: 0 },
|
|
116
|
+
fixedIssues: 0,
|
|
117
|
+
existingIssues: 0,
|
|
118
|
+
scores,
|
|
119
|
+
};
|
|
120
|
+
// 1. Check bypass labels first
|
|
121
|
+
const matchedBypass = config.bypassLabels.find((bypassLabel) => labels.some((prLabel) => prLabel.toLowerCase() === bypassLabel.toLowerCase()));
|
|
122
|
+
if (matchedBypass) {
|
|
123
|
+
return {
|
|
124
|
+
passed: true,
|
|
125
|
+
exitCode: ExitCode.SUCCESS,
|
|
126
|
+
violations: [],
|
|
127
|
+
summary,
|
|
128
|
+
bypassed: true,
|
|
129
|
+
bypassReason: `Label "${matchedBypass}" bypasses quality gate`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// 2. Normalize issues and compute diff
|
|
133
|
+
const currentIssues = normalizeIssues(auditResult.issues);
|
|
134
|
+
// Use provided baseline or empty baseline (treats all issues as new)
|
|
135
|
+
const effectiveBaseline = baseline || createEmptyBaseline();
|
|
136
|
+
const diff = computeDiff(currentIssues, effectiveBaseline);
|
|
137
|
+
// Update summary
|
|
138
|
+
summary.newIssues = countBySeverity(diff.new);
|
|
139
|
+
summary.fixedIssues = diff.summary.fixedCount;
|
|
140
|
+
summary.existingIssues = diff.summary.presentCount;
|
|
141
|
+
// 3. Build violations list
|
|
142
|
+
const violations = [];
|
|
143
|
+
// 3a. Check new issues against failOn severity
|
|
144
|
+
if (config.failOn !== "none") {
|
|
145
|
+
const failOnRank = severityRank(config.failOn);
|
|
146
|
+
for (const issue of diff.new) {
|
|
147
|
+
const issueSeverity = issue.severity || "info";
|
|
148
|
+
if (severityRank(issueSeverity) >= failOnRank) {
|
|
149
|
+
violations.push({
|
|
150
|
+
type: "new_issue",
|
|
151
|
+
severity: normalizeSeverity(issueSeverity),
|
|
152
|
+
message: `New ${issueSeverity} issue: ${issue.ruleId || issue.rule_id || issue.id || "unknown"} - ${(issue.description || issue.title || "").slice(0, 100)}`,
|
|
153
|
+
details: {
|
|
154
|
+
ruleId: issue.ruleId || issue.rule_id || issue.id,
|
|
155
|
+
selector: issue.selector,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// 3b. Check existing issues if failOnExisting is enabled (legacy mode)
|
|
162
|
+
if (config.failOnExisting && config.failOn !== "none") {
|
|
163
|
+
const failOnRank = severityRank(config.failOn);
|
|
164
|
+
for (const issue of diff.present) {
|
|
165
|
+
const issueSeverity = issue.severity || "info";
|
|
166
|
+
if (severityRank(issueSeverity) >= failOnRank) {
|
|
167
|
+
violations.push({
|
|
168
|
+
type: "new_issue", // Using same type for consistency
|
|
169
|
+
severity: normalizeSeverity(issueSeverity),
|
|
170
|
+
message: `Existing ${issueSeverity} issue: ${issue.ruleId || issue.rule_id || issue.id || "unknown"} - ${(issue.description || issue.title || "").slice(0, 100)}`,
|
|
171
|
+
details: {
|
|
172
|
+
ruleId: issue.ruleId || issue.rule_id || issue.id,
|
|
173
|
+
selector: issue.selector,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// 3c. Check max new counts
|
|
180
|
+
for (const [severityKey, max] of Object.entries(config.maxNew)) {
|
|
181
|
+
const severity = severityKey;
|
|
182
|
+
const count = summary.newIssues[severity];
|
|
183
|
+
if (count > max) {
|
|
184
|
+
violations.push({
|
|
185
|
+
type: "max_exceeded",
|
|
186
|
+
severity,
|
|
187
|
+
message: `Too many new ${severity} issues: ${count} (max allowed: ${max})`,
|
|
188
|
+
details: {
|
|
189
|
+
count,
|
|
190
|
+
threshold: max,
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// 3d. Check score thresholds
|
|
196
|
+
for (const [category, threshold] of Object.entries(config.thresholds)) {
|
|
197
|
+
if (threshold === undefined)
|
|
198
|
+
continue;
|
|
199
|
+
const actual = scores[category];
|
|
200
|
+
if (actual !== undefined && actual < threshold) {
|
|
201
|
+
violations.push({
|
|
202
|
+
type: "threshold",
|
|
203
|
+
severity: "error",
|
|
204
|
+
message: `${category} score ${actual} is below threshold ${threshold}`,
|
|
205
|
+
details: {
|
|
206
|
+
actual,
|
|
207
|
+
threshold,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// 4. Determine exit code
|
|
213
|
+
let exitCode = ExitCode.SUCCESS;
|
|
214
|
+
if (violations.length > 0) {
|
|
215
|
+
// Check if any threshold violations exist
|
|
216
|
+
const hasThresholdViolation = violations.some((v) => v.type === "threshold");
|
|
217
|
+
// Check if any new issue or max exceeded violations exist
|
|
218
|
+
const hasIssueViolation = violations.some((v) => v.type === "new_issue" || v.type === "max_exceeded");
|
|
219
|
+
// Exit code 1 (issues) takes precedence over exit code 3 (threshold)
|
|
220
|
+
if (hasIssueViolation) {
|
|
221
|
+
exitCode = ExitCode.ISSUES_FOUND;
|
|
222
|
+
}
|
|
223
|
+
else if (hasThresholdViolation) {
|
|
224
|
+
exitCode = ExitCode.THRESHOLD_BREACH;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
passed: violations.length === 0,
|
|
229
|
+
exitCode,
|
|
230
|
+
violations,
|
|
231
|
+
summary,
|
|
232
|
+
bypassed: false,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Format quality gate result for human-readable output.
|
|
237
|
+
*/
|
|
238
|
+
export function formatQualityGateResult(result) {
|
|
239
|
+
const lines = [];
|
|
240
|
+
// Header
|
|
241
|
+
if (result.bypassed) {
|
|
242
|
+
lines.push(`Quality Gate: BYPASSED (${result.bypassReason})`);
|
|
243
|
+
}
|
|
244
|
+
else if (result.passed) {
|
|
245
|
+
lines.push("Quality Gate: PASSED");
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
lines.push("Quality Gate: FAILED");
|
|
249
|
+
}
|
|
250
|
+
// Summary
|
|
251
|
+
lines.push("");
|
|
252
|
+
lines.push("Summary:");
|
|
253
|
+
lines.push(` New issues: ${result.summary.newIssues.error} errors, ${result.summary.newIssues.warning} warnings, ${result.summary.newIssues.info} info`);
|
|
254
|
+
lines.push(` Fixed: ${result.summary.fixedIssues}`);
|
|
255
|
+
lines.push(` Existing: ${result.summary.existingIssues}`);
|
|
256
|
+
// Scores
|
|
257
|
+
const scoreEntries = Object.entries(result.summary.scores);
|
|
258
|
+
if (scoreEntries.length > 0) {
|
|
259
|
+
lines.push("");
|
|
260
|
+
lines.push("Scores:");
|
|
261
|
+
for (const [category, score] of scoreEntries) {
|
|
262
|
+
lines.push(` ${category}: ${score}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Violations
|
|
266
|
+
if (result.violations.length > 0) {
|
|
267
|
+
lines.push("");
|
|
268
|
+
lines.push(`Violations (${result.violations.length}):`);
|
|
269
|
+
for (const violation of result.violations) {
|
|
270
|
+
lines.push(` - [${violation.severity.toUpperCase()}] ${violation.message}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return lines.join("\n");
|
|
274
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality gate module for CI/CD integration.
|
|
3
|
+
*
|
|
4
|
+
* Exports evaluator and types for build gating based on audit results.
|
|
5
|
+
*/
|
|
6
|
+
export { evaluateQualityGate, formatQualityGateResult } from "./evaluator.js";
|
|
7
|
+
export type { AuditResultForGate, EvaluateOptions, } from "./evaluator.js";
|
|
8
|
+
export { DEFAULT_QUALITY_GATE_CONFIG } from "./types.js";
|
|
9
|
+
export type { QualityGateConfig, QualityGateResult, GateViolation, ThresholdConfig, } from "./types.js";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/quality-gate/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC9E,YAAY,EACV,kBAAkB,EAClB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AACzD,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,eAAe,GAChB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality gate module for CI/CD integration.
|
|
3
|
+
*
|
|
4
|
+
* Exports evaluator and types for build gating based on audit results.
|
|
5
|
+
*/
|
|
6
|
+
export { evaluateQualityGate, formatQualityGateResult } from "./evaluator.js";
|
|
7
|
+
export { DEFAULT_QUALITY_GATE_CONFIG } from "./types.js";
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality gate type definitions.
|
|
3
|
+
*
|
|
4
|
+
* Quality gates control when CI builds should fail based on audit results.
|
|
5
|
+
* Key principle: Fail only on NEW issues (not existing technical debt).
|
|
6
|
+
*
|
|
7
|
+
* Implements CICD-06: Fail on new issues only, not existing technical debt.
|
|
8
|
+
*/
|
|
9
|
+
import type { ExitCodeType } from "../utils/exit-codes.js";
|
|
10
|
+
/**
|
|
11
|
+
* Quality gate configuration.
|
|
12
|
+
*/
|
|
13
|
+
export interface QualityGateConfig {
|
|
14
|
+
/** Fail on issues at or above this severity. 'none' disables severity check. */
|
|
15
|
+
failOn: "error" | "warning" | "info" | "none";
|
|
16
|
+
/** Score thresholds (0-100). Undefined means threshold not enforced. */
|
|
17
|
+
thresholds: ThresholdConfig;
|
|
18
|
+
/** Maximum allowed new issues by severity */
|
|
19
|
+
maxNew: {
|
|
20
|
+
error: number;
|
|
21
|
+
warning: number;
|
|
22
|
+
info: number;
|
|
23
|
+
};
|
|
24
|
+
/** Whether to fail on existing issues (legacy mode, default: false) */
|
|
25
|
+
failOnExisting: boolean;
|
|
26
|
+
/** PR labels that bypass quality gate entirely */
|
|
27
|
+
bypassLabels: string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Score threshold configuration.
|
|
31
|
+
*
|
|
32
|
+
* Each field is optional - only specified thresholds are enforced.
|
|
33
|
+
*/
|
|
34
|
+
export interface ThresholdConfig {
|
|
35
|
+
/** Overall score threshold (0-100) */
|
|
36
|
+
overall?: number;
|
|
37
|
+
/** Accessibility score threshold (0-100) */
|
|
38
|
+
accessibility?: number;
|
|
39
|
+
/** UX score threshold (0-100) */
|
|
40
|
+
ux?: number;
|
|
41
|
+
/** Performance score threshold (0-100) */
|
|
42
|
+
performance?: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Individual quality gate violation.
|
|
46
|
+
*
|
|
47
|
+
* Violations are categorized by type for clear reporting:
|
|
48
|
+
* - new_issue: A new issue above failOn severity
|
|
49
|
+
* - threshold: Score below minimum threshold
|
|
50
|
+
* - max_exceeded: More new issues than allowed by maxNew config
|
|
51
|
+
*/
|
|
52
|
+
export interface GateViolation {
|
|
53
|
+
/** Type of violation */
|
|
54
|
+
type: "new_issue" | "threshold" | "max_exceeded";
|
|
55
|
+
/** Severity level of the violation */
|
|
56
|
+
severity: "error" | "warning" | "info";
|
|
57
|
+
/** Human-readable message */
|
|
58
|
+
message: string;
|
|
59
|
+
/** Additional details for debugging */
|
|
60
|
+
details?: {
|
|
61
|
+
ruleId?: string;
|
|
62
|
+
selector?: string;
|
|
63
|
+
actual?: number;
|
|
64
|
+
threshold?: number;
|
|
65
|
+
count?: number;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Result of quality gate evaluation.
|
|
70
|
+
*/
|
|
71
|
+
export interface QualityGateResult {
|
|
72
|
+
/** Whether the gate passed (true = CI should succeed) */
|
|
73
|
+
passed: boolean;
|
|
74
|
+
/** Exit code to use for process.exit() */
|
|
75
|
+
exitCode: ExitCodeType;
|
|
76
|
+
/** List of violations (empty if passed) */
|
|
77
|
+
violations: GateViolation[];
|
|
78
|
+
/** Summary counts for reporting */
|
|
79
|
+
summary: {
|
|
80
|
+
newIssues: {
|
|
81
|
+
error: number;
|
|
82
|
+
warning: number;
|
|
83
|
+
info: number;
|
|
84
|
+
};
|
|
85
|
+
fixedIssues: number;
|
|
86
|
+
existingIssues: number;
|
|
87
|
+
scores: Record<string, number>;
|
|
88
|
+
};
|
|
89
|
+
/** Whether gate was bypassed via label */
|
|
90
|
+
bypassed: boolean;
|
|
91
|
+
/** Reason for bypass (label name) */
|
|
92
|
+
bypassReason?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Default quality gate configuration.
|
|
96
|
+
*
|
|
97
|
+
* Defaults are designed to be adoption-friendly:
|
|
98
|
+
* - Only fail on errors (not warnings/info)
|
|
99
|
+
* - Only fail on NEW errors (existing debt allowed)
|
|
100
|
+
* - No score thresholds by default (opt-in)
|
|
101
|
+
*/
|
|
102
|
+
export declare const DEFAULT_QUALITY_GATE_CONFIG: QualityGateConfig;
|
|
103
|
+
//# sourceMappingURL=types.d.ts.map
|