@vertaaux/cli 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.md +345 -0
  2. package/dist/auth/ci-token.d.ts +49 -0
  3. package/dist/auth/ci-token.d.ts.map +1 -0
  4. package/dist/auth/ci-token.js +83 -0
  5. package/dist/auth/device-flow.d.ts +66 -0
  6. package/dist/auth/device-flow.d.ts.map +1 -0
  7. package/dist/auth/device-flow.js +156 -0
  8. package/dist/auth/token-store.d.ts +53 -0
  9. package/dist/auth/token-store.d.ts.map +1 -0
  10. package/dist/auth/token-store.js +78 -0
  11. package/dist/baseline/diff.d.ts +57 -0
  12. package/dist/baseline/diff.d.ts.map +1 -0
  13. package/dist/baseline/diff.js +152 -0
  14. package/dist/baseline/hash.d.ts +54 -0
  15. package/dist/baseline/hash.d.ts.map +1 -0
  16. package/dist/baseline/hash.js +66 -0
  17. package/dist/baseline/manager.d.ts +89 -0
  18. package/dist/baseline/manager.d.ts.map +1 -0
  19. package/dist/baseline/manager.js +157 -0
  20. package/dist/cache/index.d.ts +8 -0
  21. package/dist/cache/index.d.ts.map +1 -0
  22. package/dist/cache/index.js +7 -0
  23. package/dist/cache/route-cache.d.ts +119 -0
  24. package/dist/cache/route-cache.d.ts.map +1 -0
  25. package/dist/cache/route-cache.js +213 -0
  26. package/dist/ci/changed-routes.d.ts +95 -0
  27. package/dist/ci/changed-routes.d.ts.map +1 -0
  28. package/dist/ci/changed-routes.js +304 -0
  29. package/dist/ci/github-api.d.ts +68 -0
  30. package/dist/ci/github-api.d.ts.map +1 -0
  31. package/dist/ci/github-api.js +138 -0
  32. package/dist/ci/gitlab-api.d.ts +75 -0
  33. package/dist/ci/gitlab-api.d.ts.map +1 -0
  34. package/dist/ci/gitlab-api.js +180 -0
  35. package/dist/ci/index.d.ts +6 -0
  36. package/dist/ci/index.d.ts.map +1 -0
  37. package/dist/ci/index.js +4 -0
  38. package/dist/commands/audit.d.ts +58 -0
  39. package/dist/commands/audit.d.ts.map +1 -0
  40. package/dist/commands/audit.js +862 -0
  41. package/dist/commands/baseline.d.ts +22 -0
  42. package/dist/commands/baseline.d.ts.map +1 -0
  43. package/dist/commands/baseline.js +210 -0
  44. package/dist/commands/comment.d.ts +14 -0
  45. package/dist/commands/comment.d.ts.map +1 -0
  46. package/dist/commands/comment.js +363 -0
  47. package/dist/commands/diff.d.ts +24 -0
  48. package/dist/commands/diff.d.ts.map +1 -0
  49. package/dist/commands/diff.js +196 -0
  50. package/dist/commands/doctor.d.ts +58 -0
  51. package/dist/commands/doctor.d.ts.map +1 -0
  52. package/dist/commands/doctor.js +338 -0
  53. package/dist/commands/download.d.ts +12 -0
  54. package/dist/commands/download.d.ts.map +1 -0
  55. package/dist/commands/download.js +183 -0
  56. package/dist/commands/explain.d.ts +62 -0
  57. package/dist/commands/explain.d.ts.map +1 -0
  58. package/dist/commands/explain.js +302 -0
  59. package/dist/commands/init.d.ts +12 -0
  60. package/dist/commands/init.d.ts.map +1 -0
  61. package/dist/commands/init.js +212 -0
  62. package/dist/commands/login.d.ts +14 -0
  63. package/dist/commands/login.d.ts.map +1 -0
  64. package/dist/commands/login.js +222 -0
  65. package/dist/commands/policy.d.ts +13 -0
  66. package/dist/commands/policy.d.ts.map +1 -0
  67. package/dist/commands/policy.js +347 -0
  68. package/dist/commands/upload.d.ts +12 -0
  69. package/dist/commands/upload.d.ts.map +1 -0
  70. package/dist/commands/upload.js +158 -0
  71. package/dist/config/defaults.d.ts +21 -0
  72. package/dist/config/defaults.d.ts.map +1 -0
  73. package/dist/config/defaults.js +49 -0
  74. package/dist/config/loader.d.ts +66 -0
  75. package/dist/config/loader.d.ts.map +1 -0
  76. package/dist/config/loader.js +167 -0
  77. package/dist/config/schema.d.ts +55 -0
  78. package/dist/config/schema.d.ts.map +1 -0
  79. package/dist/config/schema.js +6 -0
  80. package/dist/index.d.ts +9 -0
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/index.js +1090 -0
  83. package/dist/interactive/fix-wizard.d.ts +44 -0
  84. package/dist/interactive/fix-wizard.d.ts.map +1 -0
  85. package/dist/interactive/fix-wizard.js +286 -0
  86. package/dist/interactive/init-wizard.d.ts +32 -0
  87. package/dist/interactive/init-wizard.d.ts.map +1 -0
  88. package/dist/interactive/init-wizard.js +193 -0
  89. package/dist/interactive/prompts.d.ts +62 -0
  90. package/dist/interactive/prompts.d.ts.map +1 -0
  91. package/dist/interactive/prompts.js +78 -0
  92. package/dist/monorepo/detector.d.ts +70 -0
  93. package/dist/monorepo/detector.d.ts.map +1 -0
  94. package/dist/monorepo/detector.js +278 -0
  95. package/dist/monorepo/index.d.ts +9 -0
  96. package/dist/monorepo/index.d.ts.map +1 -0
  97. package/dist/monorepo/index.js +8 -0
  98. package/dist/monorepo/workspace.d.ts +142 -0
  99. package/dist/monorepo/workspace.d.ts.map +1 -0
  100. package/dist/monorepo/workspace.js +171 -0
  101. package/dist/output/envelope.d.ts +21 -0
  102. package/dist/output/envelope.d.ts.map +1 -0
  103. package/dist/output/envelope.js +27 -0
  104. package/dist/output/factory.d.ts +73 -0
  105. package/dist/output/factory.d.ts.map +1 -0
  106. package/dist/output/factory.js +60 -0
  107. package/dist/output/formats.d.ts +11 -0
  108. package/dist/output/formats.d.ts.map +1 -0
  109. package/dist/output/formats.js +41 -0
  110. package/dist/output/html.d.ts +45 -0
  111. package/dist/output/html.d.ts.map +1 -0
  112. package/dist/output/html.js +607 -0
  113. package/dist/output/human.d.ts +41 -0
  114. package/dist/output/human.d.ts.map +1 -0
  115. package/dist/output/human.js +274 -0
  116. package/dist/output/json.d.ts +42 -0
  117. package/dist/output/json.d.ts.map +1 -0
  118. package/dist/output/json.js +37 -0
  119. package/dist/output/junit.d.ts +56 -0
  120. package/dist/output/junit.d.ts.map +1 -0
  121. package/dist/output/junit.js +135 -0
  122. package/dist/output/markdown.d.ts +77 -0
  123. package/dist/output/markdown.d.ts.map +1 -0
  124. package/dist/output/markdown.js +411 -0
  125. package/dist/output/sarif.d.ts +160 -0
  126. package/dist/output/sarif.d.ts.map +1 -0
  127. package/dist/output/sarif.js +207 -0
  128. package/dist/policy/evaluator.d.ts +111 -0
  129. package/dist/policy/evaluator.d.ts.map +1 -0
  130. package/dist/policy/evaluator.js +362 -0
  131. package/dist/policy/index.d.ts +15 -0
  132. package/dist/policy/index.d.ts.map +1 -0
  133. package/dist/policy/index.js +11 -0
  134. package/dist/policy/loader.d.ts +97 -0
  135. package/dist/policy/loader.d.ts.map +1 -0
  136. package/dist/policy/loader.js +281 -0
  137. package/dist/policy/schema.d.ts +297 -0
  138. package/dist/policy/schema.d.ts.map +1 -0
  139. package/dist/policy/schema.js +230 -0
  140. package/dist/quality-gate/evaluator.d.ts +58 -0
  141. package/dist/quality-gate/evaluator.d.ts.map +1 -0
  142. package/dist/quality-gate/evaluator.js +274 -0
  143. package/dist/quality-gate/index.d.ts +10 -0
  144. package/dist/quality-gate/index.d.ts.map +1 -0
  145. package/dist/quality-gate/index.js +7 -0
  146. package/dist/quality-gate/types.d.ts +103 -0
  147. package/dist/quality-gate/types.d.ts.map +1 -0
  148. package/dist/quality-gate/types.js +23 -0
  149. package/dist/templates/azure-devops.d.ts +25 -0
  150. package/dist/templates/azure-devops.d.ts.map +1 -0
  151. package/dist/templates/azure-devops.js +109 -0
  152. package/dist/templates/circleci.d.ts +28 -0
  153. package/dist/templates/circleci.d.ts.map +1 -0
  154. package/dist/templates/circleci.js +86 -0
  155. package/dist/templates/github-actions.d.ts +81 -0
  156. package/dist/templates/github-actions.d.ts.map +1 -0
  157. package/dist/templates/github-actions.js +393 -0
  158. package/dist/templates/gitlab-ci.d.ts +26 -0
  159. package/dist/templates/gitlab-ci.d.ts.map +1 -0
  160. package/dist/templates/gitlab-ci.js +70 -0
  161. package/dist/templates/index.d.ts +72 -0
  162. package/dist/templates/index.d.ts.map +1 -0
  163. package/dist/templates/index.js +112 -0
  164. package/dist/templates/jenkins.d.ts +26 -0
  165. package/dist/templates/jenkins.d.ts.map +1 -0
  166. package/dist/templates/jenkins.js +110 -0
  167. package/dist/ui/banner.d.ts +31 -0
  168. package/dist/ui/banner.d.ts.map +1 -0
  169. package/dist/ui/banner.js +84 -0
  170. package/dist/ui/diagnostics.d.ts +39 -0
  171. package/dist/ui/diagnostics.d.ts.map +1 -0
  172. package/dist/ui/diagnostics.js +153 -0
  173. package/dist/ui/spinner.d.ts +61 -0
  174. package/dist/ui/spinner.d.ts.map +1 -0
  175. package/dist/ui/spinner.js +101 -0
  176. package/dist/ui/table.d.ts +63 -0
  177. package/dist/ui/table.d.ts.map +1 -0
  178. package/dist/ui/table.js +236 -0
  179. package/dist/utils/client.d.ts +82 -0
  180. package/dist/utils/client.d.ts.map +1 -0
  181. package/dist/utils/client.js +128 -0
  182. package/dist/utils/detect-env.d.ts +59 -0
  183. package/dist/utils/detect-env.d.ts.map +1 -0
  184. package/dist/utils/detect-env.js +115 -0
  185. package/dist/utils/exit-codes.d.ts +47 -0
  186. package/dist/utils/exit-codes.d.ts.map +1 -0
  187. package/dist/utils/exit-codes.js +61 -0
  188. package/dist/utils/logger.d.ts +87 -0
  189. package/dist/utils/logger.d.ts.map +1 -0
  190. package/dist/utils/logger.js +185 -0
  191. package/dist/utils/sanitize.d.ts +36 -0
  192. package/dist/utils/sanitize.d.ts.map +1 -0
  193. package/dist/utils/sanitize.js +64 -0
  194. package/dist/utils/validators.d.ts +41 -0
  195. package/dist/utils/validators.d.ts.map +1 -0
  196. package/dist/utils/validators.js +123 -0
  197. package/package.json +63 -0
  198. package/schemas/vertaaux.config.schema.json +103 -0
@@ -0,0 +1,207 @@
1
+ /**
2
+ * SARIF 2.1.0 output formatter for CLI.
3
+ *
4
+ * Generates SARIF (Static Analysis Results Interchange Format) output
5
+ * for integration with GitHub Code Scanning and other SARIF consumers.
6
+ *
7
+ * @see https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html
8
+ * @see https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning
9
+ */
10
+ import { generateFingerprint } from "../baseline/hash.js";
11
+ // Package version for tool.driver.version
12
+ const CLI_VERSION = "0.1.0";
13
+ // GitHub Code Scanning limits
14
+ const MAX_RESULTS = 25000;
15
+ /**
16
+ * Map CLI/API severity to SARIF level.
17
+ *
18
+ * - error/critical -> error
19
+ * - warning/serious -> warning
20
+ * - info/minor -> note
21
+ */
22
+ function mapSeverityToLevel(severity) {
23
+ const sev = (severity || "info").toLowerCase();
24
+ switch (sev) {
25
+ case "error":
26
+ case "critical":
27
+ return "error";
28
+ case "warning":
29
+ case "serious":
30
+ return "warning";
31
+ case "info":
32
+ case "minor":
33
+ case "moderate":
34
+ return "note";
35
+ default:
36
+ return "note";
37
+ }
38
+ }
39
+ /**
40
+ * Get rule ID from issue, checking multiple field names.
41
+ */
42
+ function getRuleId(issue) {
43
+ return issue.ruleId || issue.rule_id || issue.id || "unknown";
44
+ }
45
+ /**
46
+ * Normalize issues from various API response formats.
47
+ */
48
+ function normalizeIssues(issues) {
49
+ if (Array.isArray(issues))
50
+ return issues;
51
+ if (issues && typeof issues === "object") {
52
+ const values = Object.values(issues);
53
+ return values.flatMap((value) => Array.isArray(value) ? value : []);
54
+ }
55
+ return [];
56
+ }
57
+ /**
58
+ * Build unique rules array from issues.
59
+ */
60
+ function buildRules(issues) {
61
+ const ruleMap = new Map();
62
+ for (const issue of issues) {
63
+ const ruleId = getRuleId(issue);
64
+ if (ruleMap.has(ruleId))
65
+ continue;
66
+ const rule = {
67
+ id: ruleId,
68
+ shortDescription: {
69
+ text: issue.title || issue.description?.slice(0, 100) || ruleId,
70
+ },
71
+ defaultConfiguration: {
72
+ level: mapSeverityToLevel(issue.severity),
73
+ },
74
+ };
75
+ // Add full description if different from short
76
+ if (issue.description && issue.description.length > 100) {
77
+ rule.fullDescription = { text: issue.description };
78
+ }
79
+ // Add help URI from WCAG reference
80
+ if (issue.wcag_reference) {
81
+ // Extract WCAG criterion and build URL
82
+ const wcagMatch = issue.wcag_reference.match(/\d+\.\d+\.\d+/);
83
+ if (wcagMatch) {
84
+ rule.helpUri = `https://www.w3.org/WAI/WCAG21/Understanding/${wcagMatch[0].replace(/\./g, "-")}.html`;
85
+ }
86
+ }
87
+ // Add category as property
88
+ if (issue.category) {
89
+ rule.properties = {
90
+ category: issue.category,
91
+ };
92
+ }
93
+ ruleMap.set(ruleId, rule);
94
+ }
95
+ return Array.from(ruleMap.values());
96
+ }
97
+ /**
98
+ * Get artifact URI from issue and base URL.
99
+ *
100
+ * Returns relative path if possible, otherwise full URL.
101
+ */
102
+ function getArtifactUri(issue, baseUrl) {
103
+ // If we have a selector, use a relative path based on URL
104
+ // Otherwise use the base URL or a placeholder
105
+ if (baseUrl) {
106
+ try {
107
+ const url = new URL(baseUrl);
108
+ // Return path portion as relative URI
109
+ return url.pathname || "/";
110
+ }
111
+ catch {
112
+ return baseUrl;
113
+ }
114
+ }
115
+ return "index.html";
116
+ }
117
+ /**
118
+ * Build SARIF result from issue.
119
+ */
120
+ function buildResult(issue, baseUrl, options) {
121
+ const ruleId = getRuleId(issue);
122
+ const fingerprint = generateFingerprint(issue);
123
+ const result = {
124
+ ruleId,
125
+ message: {
126
+ text: issue.description || issue.title || "No description provided",
127
+ },
128
+ level: mapSeverityToLevel(issue.severity),
129
+ locations: [
130
+ {
131
+ physicalLocation: {
132
+ artifactLocation: {
133
+ uri: getArtifactUri(issue, baseUrl),
134
+ },
135
+ },
136
+ },
137
+ ],
138
+ partialFingerprints: {
139
+ primaryLocationLineHash: fingerprint,
140
+ },
141
+ };
142
+ // Add suppression if issue is baselined
143
+ if (options?.includeBaseline && options.baselineFingerprints?.has(fingerprint)) {
144
+ result.suppressions = [
145
+ {
146
+ kind: "external",
147
+ justification: "Issue was baselined as existing technical debt",
148
+ },
149
+ ];
150
+ }
151
+ return result;
152
+ }
153
+ /**
154
+ * Format audit result as SARIF 2.1.0 JSON.
155
+ *
156
+ * @param result - Audit result from API
157
+ * @param options - Formatting options
158
+ * @returns SARIF 2.1.0 JSON string
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * const sarif = formatSarif(auditResult, {
163
+ * workingDirectory: process.cwd(),
164
+ * includeBaseline: true,
165
+ * baselineFingerprints: new Set(['abc123...'])
166
+ * });
167
+ * ```
168
+ */
169
+ export function formatSarif(result, options) {
170
+ const issues = normalizeIssues(result.issues);
171
+ const rules = buildRules(issues);
172
+ const results = issues.map((issue) => buildResult(issue, result.url, options));
173
+ // Warn if approaching GitHub limits
174
+ if (results.length > MAX_RESULTS * 0.9) {
175
+ console.error(`Warning: ${results.length} results approaching GitHub limit of ${MAX_RESULTS}`);
176
+ }
177
+ // Truncate if exceeding limit
178
+ const truncatedResults = results.slice(0, MAX_RESULTS);
179
+ if (results.length > MAX_RESULTS) {
180
+ console.error(`Warning: Truncated ${results.length - MAX_RESULTS} results to meet GitHub limit`);
181
+ }
182
+ const sarifLog = {
183
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
184
+ version: "2.1.0",
185
+ runs: [
186
+ {
187
+ tool: {
188
+ driver: {
189
+ name: "VertaaUX",
190
+ version: CLI_VERSION,
191
+ informationUri: "https://vertaaux.ai",
192
+ rules,
193
+ },
194
+ },
195
+ results: truncatedResults,
196
+ },
197
+ ],
198
+ };
199
+ // Add invocation if working directory specified
200
+ if (options?.workingDirectory) {
201
+ sarifLog.runs[0].invocation = {
202
+ workingDirectory: { uri: `file://${options.workingDirectory}` },
203
+ executionSuccessful: !result.error,
204
+ };
205
+ }
206
+ return JSON.stringify(sarifLog, null, 2);
207
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Policy evaluation against audit results.
3
+ *
4
+ * Evaluates audit results against policy assertions,
5
+ * applying rule overrides and path exclusions.
6
+ *
7
+ * Implements CICD-17: Policy-as-code support.
8
+ */
9
+ import type { PolicyFile, RuleOverride } from "./schema.js";
10
+ import type { Issue } from "../baseline/hash.js";
11
+ /**
12
+ * Audit result subset needed for policy evaluation.
13
+ */
14
+ export interface AuditResultForPolicy {
15
+ /** Issues found during audit */
16
+ issues?: Issue[] | Record<string, Issue[]>;
17
+ /** Score breakdown */
18
+ scores?: Record<string, number | unknown>;
19
+ }
20
+ /**
21
+ * Policy violation details.
22
+ */
23
+ export interface PolicyViolation {
24
+ /** Type of violation */
25
+ type: "score_threshold" | "issue_limit" | "severity_breach";
26
+ /** Human-readable message */
27
+ message: string;
28
+ /** Actual value that caused violation */
29
+ actual: number;
30
+ /** Threshold that was breached */
31
+ threshold: number;
32
+ /** Additional context */
33
+ context?: {
34
+ category?: string;
35
+ severity?: string;
36
+ };
37
+ }
38
+ /**
39
+ * Result of policy evaluation.
40
+ */
41
+ export interface PolicyEvaluationResult {
42
+ /** Whether policy passed */
43
+ passed: boolean;
44
+ /** List of policy violations */
45
+ violations: PolicyViolation[];
46
+ /** Issues with applied rule overrides */
47
+ adjustedIssues: Issue[];
48
+ /** Rules that were ignored by policy */
49
+ ignoredRules: string[];
50
+ /** Summary of evaluation */
51
+ summary: {
52
+ /** Issues by severity after adjustments */
53
+ issueCounts: {
54
+ error: number;
55
+ warning: number;
56
+ info: number;
57
+ };
58
+ /** Number of issues ignored by rule overrides */
59
+ ignoredCount: number;
60
+ /** Score breakdown */
61
+ scores: Record<string, number>;
62
+ };
63
+ }
64
+ /**
65
+ * Apply rule overrides to issues.
66
+ *
67
+ * Returns adjusted issues with modified severities,
68
+ * and list of issues that were ignored.
69
+ *
70
+ * @param issues - Original issues from audit
71
+ * @param rules - Rule override configuration
72
+ * @param currentPath - Current file path for path-specific overrides
73
+ * @returns Adjusted issues and list of ignored rule IDs
74
+ */
75
+ export declare function applyRuleOverrides(issues: Issue[], rules: Record<string, RuleOverride>, currentPath?: string): {
76
+ adjusted: Issue[];
77
+ ignored: Issue[];
78
+ ignoredRules: string[];
79
+ };
80
+ /**
81
+ * Filter issues by excluded paths.
82
+ *
83
+ * @param issues - Issues to filter
84
+ * @param excludePaths - Path patterns to exclude
85
+ * @returns Filtered issues
86
+ */
87
+ export declare function filterExcludedPaths(issues: Issue[], excludePaths: string[]): Issue[];
88
+ /**
89
+ * Evaluate audit result against policy.
90
+ *
91
+ * Evaluation order:
92
+ * 1. Apply rule overrides (change severity, ignore rules)
93
+ * 2. Filter issues for excluded paths
94
+ * 3. Check threshold assertions
95
+ * 4. Check issue count limits
96
+ * 5. Check severity breach
97
+ *
98
+ * @param auditResult - Audit result with issues and scores
99
+ * @param newIssues - New issues only (from baseline diff)
100
+ * @param policy - Policy configuration
101
+ * @param options - Additional options
102
+ * @returns Evaluation result with pass/fail status and violations
103
+ */
104
+ export declare function evaluatePolicy(auditResult: AuditResultForPolicy, newIssues: Issue[], policy: PolicyFile, options?: {
105
+ currentPath?: string;
106
+ }): PolicyEvaluationResult;
107
+ /**
108
+ * Format policy evaluation result for human-readable output.
109
+ */
110
+ export declare function formatPolicyResult(result: PolicyEvaluationResult): string;
111
+ //# sourceMappingURL=evaluator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/policy/evaluator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAoB,MAAM,aAAa,CAAC;AAC9E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,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;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,wBAAwB;IACxB,IAAI,EAAE,iBAAiB,GAAG,aAAa,GAAG,iBAAiB,CAAC;IAC5D,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,yBAAyB;IACzB,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,4BAA4B;IAC5B,MAAM,EAAE,OAAO,CAAC;IAEhB,gCAAgC;IAChC,UAAU,EAAE,eAAe,EAAE,CAAC;IAE9B,yCAAyC;IACzC,cAAc,EAAE,KAAK,EAAE,CAAC;IAExB,wCAAwC;IACxC,YAAY,EAAE,MAAM,EAAE,CAAC;IAEvB,4BAA4B;IAC5B,OAAO,EAAE;QACP,2CAA2C;QAC3C,WAAW,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QAC9D,iDAAiD;QACjD,YAAY,EAAE,MAAM,CAAC;QACrB,sBAAsB;QACtB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,CAAC;CACH;AAkGD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,KAAK,EAAE,EACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACnC,WAAW,CAAC,EAAE,MAAM,GACnB;IAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAAC,OAAO,EAAE,KAAK,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,CAsCjE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,KAAK,EAAE,EACf,YAAY,EAAE,MAAM,EAAE,GACrB,KAAK,EAAE,CAcT;AA8ID;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,oBAAoB,EACjC,SAAS,EAAE,KAAK,EAAE,EAClB,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACjC,sBAAsB,CA2CxB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAiDzE"}
@@ -0,0 +1,362 @@
1
+ /**
2
+ * Policy evaluation against audit results.
3
+ *
4
+ * Evaluates audit results against policy assertions,
5
+ * applying rule overrides and path exclusions.
6
+ *
7
+ * Implements CICD-17: Policy-as-code support.
8
+ */
9
+ import { minimatch } from "minimatch";
10
+ /** Severity ranking for comparison */
11
+ const SEVERITY_RANK = {
12
+ error: 3,
13
+ critical: 3,
14
+ warning: 2,
15
+ serious: 2,
16
+ info: 1,
17
+ minor: 1,
18
+ moderate: 1,
19
+ };
20
+ /**
21
+ * Get severity rank for comparison.
22
+ */
23
+ function severityRank(severity) {
24
+ return SEVERITY_RANK[severity.toLowerCase()] ?? 0;
25
+ }
26
+ /**
27
+ * Normalize severity to canonical values.
28
+ */
29
+ function normalizeSeverity(severity) {
30
+ const lower = (severity || "info").toLowerCase();
31
+ switch (lower) {
32
+ case "error":
33
+ case "critical":
34
+ return "error";
35
+ case "warning":
36
+ case "serious":
37
+ return "warning";
38
+ default:
39
+ return "info";
40
+ }
41
+ }
42
+ /**
43
+ * Get the rule ID from an issue, checking multiple field names.
44
+ */
45
+ function getRuleId(issue) {
46
+ return issue.ruleId || issue.rule_id || issue.id || "";
47
+ }
48
+ /**
49
+ * Normalize issues from various API response formats to array.
50
+ */
51
+ function normalizeIssues(issues) {
52
+ if (Array.isArray(issues)) {
53
+ return issues;
54
+ }
55
+ if (issues && typeof issues === "object") {
56
+ const values = Object.values(issues);
57
+ return values.flatMap((value) => Array.isArray(value) ? value : []);
58
+ }
59
+ return [];
60
+ }
61
+ /**
62
+ * Extract numeric scores from audit result.
63
+ */
64
+ function extractScores(scores) {
65
+ const result = {};
66
+ if (!scores)
67
+ return result;
68
+ for (const [key, value] of Object.entries(scores)) {
69
+ if (typeof value === "number" && Number.isFinite(value)) {
70
+ result[key] = value;
71
+ }
72
+ }
73
+ return result;
74
+ }
75
+ /**
76
+ * Count issues by normalized severity.
77
+ */
78
+ function countBySeverity(issues) {
79
+ const counts = { error: 0, warning: 0, info: 0 };
80
+ for (const issue of issues) {
81
+ const normalized = normalizeSeverity(issue.severity || "info");
82
+ counts[normalized]++;
83
+ }
84
+ return counts;
85
+ }
86
+ /**
87
+ * Check if a path matches any of the given patterns.
88
+ */
89
+ function pathMatches(filePath, patterns) {
90
+ return patterns.some((pattern) => minimatch(filePath, pattern));
91
+ }
92
+ /**
93
+ * Apply rule overrides to issues.
94
+ *
95
+ * Returns adjusted issues with modified severities,
96
+ * and list of issues that were ignored.
97
+ *
98
+ * @param issues - Original issues from audit
99
+ * @param rules - Rule override configuration
100
+ * @param currentPath - Current file path for path-specific overrides
101
+ * @returns Adjusted issues and list of ignored rule IDs
102
+ */
103
+ export function applyRuleOverrides(issues, rules, currentPath) {
104
+ const adjusted = [];
105
+ const ignored = [];
106
+ const ignoredRules = new Set();
107
+ for (const issue of issues) {
108
+ const ruleId = getRuleId(issue);
109
+ const override = rules[ruleId];
110
+ // No override for this rule
111
+ if (!override) {
112
+ adjusted.push(issue);
113
+ continue;
114
+ }
115
+ // Check path filter if specified
116
+ if (override.paths && currentPath) {
117
+ if (!pathMatches(currentPath, override.paths)) {
118
+ // Path doesn't match, skip override
119
+ adjusted.push(issue);
120
+ continue;
121
+ }
122
+ }
123
+ // Apply severity override
124
+ if (override.severity === "ignore") {
125
+ ignored.push(issue);
126
+ ignoredRules.add(ruleId);
127
+ }
128
+ else {
129
+ // Change severity
130
+ adjusted.push({
131
+ ...issue,
132
+ severity: override.severity,
133
+ });
134
+ }
135
+ }
136
+ return { adjusted, ignored, ignoredRules: Array.from(ignoredRules) };
137
+ }
138
+ /**
139
+ * Filter issues by excluded paths.
140
+ *
141
+ * @param issues - Issues to filter
142
+ * @param excludePaths - Path patterns to exclude
143
+ * @returns Filtered issues
144
+ */
145
+ export function filterExcludedPaths(issues, excludePaths) {
146
+ if (!excludePaths || excludePaths.length === 0) {
147
+ return issues;
148
+ }
149
+ return issues.filter((issue) => {
150
+ // If issue has a path/file, check if it's excluded
151
+ const issuePath = issue.path;
152
+ if (issuePath && pathMatches(issuePath, excludePaths)) {
153
+ return false;
154
+ }
155
+ return true;
156
+ });
157
+ }
158
+ /**
159
+ * Check score threshold violations.
160
+ */
161
+ function checkScoreThresholds(scores, assertions) {
162
+ const violations = [];
163
+ // Map assertion keys to score keys
164
+ const thresholdMap = [
165
+ {
166
+ assertionKey: "overall_score",
167
+ scoreKeys: ["overall", "total", "ux"],
168
+ label: "overall",
169
+ },
170
+ {
171
+ assertionKey: "accessibility_score",
172
+ scoreKeys: ["accessibility", "a11y"],
173
+ label: "accessibility",
174
+ },
175
+ { assertionKey: "ux_score", scoreKeys: ["ux", "usability"], label: "UX" },
176
+ {
177
+ assertionKey: "performance_score",
178
+ scoreKeys: ["performance", "perf"],
179
+ label: "performance",
180
+ },
181
+ ];
182
+ for (const { assertionKey, scoreKeys, label } of thresholdMap) {
183
+ const threshold = assertions[assertionKey];
184
+ if (threshold === undefined)
185
+ continue;
186
+ // Find the score value using multiple possible keys
187
+ let actual;
188
+ for (const key of scoreKeys) {
189
+ if (scores[key] !== undefined) {
190
+ actual = scores[key];
191
+ break;
192
+ }
193
+ }
194
+ if (actual !== undefined && actual < threshold) {
195
+ violations.push({
196
+ type: "score_threshold",
197
+ message: `${label} score ${actual} is below minimum ${threshold}`,
198
+ actual,
199
+ threshold,
200
+ context: { category: label },
201
+ });
202
+ }
203
+ }
204
+ return violations;
205
+ }
206
+ /**
207
+ * Check issue count violations.
208
+ */
209
+ function checkIssueLimits(counts, assertions) {
210
+ const violations = [];
211
+ // Check max_new_errors
212
+ if (assertions.max_new_errors !== undefined &&
213
+ counts.error > assertions.max_new_errors) {
214
+ violations.push({
215
+ type: "issue_limit",
216
+ message: `${counts.error} error-severity issues exceed maximum ${assertions.max_new_errors}`,
217
+ actual: counts.error,
218
+ threshold: assertions.max_new_errors,
219
+ context: { severity: "error" },
220
+ });
221
+ }
222
+ // Check max_new_warnings
223
+ if (assertions.max_new_warnings !== undefined &&
224
+ counts.warning > assertions.max_new_warnings) {
225
+ violations.push({
226
+ type: "issue_limit",
227
+ message: `${counts.warning} warning-severity issues exceed maximum ${assertions.max_new_warnings}`,
228
+ actual: counts.warning,
229
+ threshold: assertions.max_new_warnings,
230
+ context: { severity: "warning" },
231
+ });
232
+ }
233
+ // Check max_new_total
234
+ const total = counts.error + counts.warning + counts.info;
235
+ if (assertions.max_new_total !== undefined && total > assertions.max_new_total) {
236
+ violations.push({
237
+ type: "issue_limit",
238
+ message: `${total} total issues exceed maximum ${assertions.max_new_total}`,
239
+ actual: total,
240
+ threshold: assertions.max_new_total,
241
+ });
242
+ }
243
+ return violations;
244
+ }
245
+ /**
246
+ * Check severity-based violations.
247
+ */
248
+ function checkSeverityBreach(issues, assertions) {
249
+ if (!assertions.fail_on)
250
+ return [];
251
+ const failOnRank = severityRank(assertions.fail_on);
252
+ const breachingIssues = issues.filter((issue) => severityRank(issue.severity || "info") >= failOnRank);
253
+ if (breachingIssues.length > 0) {
254
+ return [
255
+ {
256
+ type: "severity_breach",
257
+ message: `${breachingIssues.length} issues at or above ${assertions.fail_on} severity`,
258
+ actual: breachingIssues.length,
259
+ threshold: 0,
260
+ context: { severity: assertions.fail_on },
261
+ },
262
+ ];
263
+ }
264
+ return [];
265
+ }
266
+ /**
267
+ * Evaluate audit result against policy.
268
+ *
269
+ * Evaluation order:
270
+ * 1. Apply rule overrides (change severity, ignore rules)
271
+ * 2. Filter issues for excluded paths
272
+ * 3. Check threshold assertions
273
+ * 4. Check issue count limits
274
+ * 5. Check severity breach
275
+ *
276
+ * @param auditResult - Audit result with issues and scores
277
+ * @param newIssues - New issues only (from baseline diff)
278
+ * @param policy - Policy configuration
279
+ * @param options - Additional options
280
+ * @returns Evaluation result with pass/fail status and violations
281
+ */
282
+ export function evaluatePolicy(auditResult, newIssues, policy, options) {
283
+ const { currentPath } = options || {};
284
+ // 1. Apply rule overrides
285
+ const { adjusted, ignored, ignoredRules } = policy.rules
286
+ ? applyRuleOverrides(newIssues, policy.rules, currentPath)
287
+ : { adjusted: newIssues, ignored: [], ignoredRules: [] };
288
+ // 2. Filter excluded paths
289
+ const filtered = policy.exclude_paths
290
+ ? filterExcludedPaths(adjusted, policy.exclude_paths)
291
+ : adjusted;
292
+ // 3. Extract scores
293
+ const scores = extractScores(auditResult.scores);
294
+ // 4. Count issues by severity
295
+ const issueCounts = countBySeverity(filtered);
296
+ // 5. Collect violations
297
+ const violations = [];
298
+ // Score thresholds
299
+ violations.push(...checkScoreThresholds(scores, policy.assertions));
300
+ // Issue limits (applied to new issues)
301
+ violations.push(...checkIssueLimits(issueCounts, policy.assertions));
302
+ // Severity breach
303
+ violations.push(...checkSeverityBreach(filtered, policy.assertions));
304
+ // 6. Determine result
305
+ return {
306
+ passed: violations.length === 0,
307
+ violations,
308
+ adjustedIssues: filtered,
309
+ ignoredRules,
310
+ summary: {
311
+ issueCounts,
312
+ ignoredCount: ignored.length,
313
+ scores,
314
+ },
315
+ };
316
+ }
317
+ /**
318
+ * Format policy evaluation result for human-readable output.
319
+ */
320
+ export function formatPolicyResult(result) {
321
+ const lines = [];
322
+ // Header
323
+ if (result.passed) {
324
+ lines.push("Policy: PASSED");
325
+ }
326
+ else {
327
+ lines.push("Policy: FAILED");
328
+ }
329
+ // Summary
330
+ lines.push("");
331
+ lines.push("Issue Summary (after adjustments):");
332
+ lines.push(` Errors: ${result.summary.issueCounts.error}, Warnings: ${result.summary.issueCounts.warning}, Info: ${result.summary.issueCounts.info}`);
333
+ if (result.summary.ignoredCount > 0) {
334
+ lines.push(` Ignored by policy: ${result.summary.ignoredCount}`);
335
+ }
336
+ // Ignored rules
337
+ if (result.ignoredRules.length > 0) {
338
+ lines.push("");
339
+ lines.push(`Ignored rules: ${result.ignoredRules.join(", ")}`);
340
+ }
341
+ // Scores
342
+ const scoreEntries = Object.entries(result.summary.scores);
343
+ if (scoreEntries.length > 0) {
344
+ lines.push("");
345
+ lines.push("Scores:");
346
+ for (const [category, score] of scoreEntries) {
347
+ lines.push(` ${category}: ${score}`);
348
+ }
349
+ }
350
+ // Violations
351
+ if (result.violations.length > 0) {
352
+ lines.push("");
353
+ lines.push(`Violations (${result.violations.length}):`);
354
+ for (const violation of result.violations) {
355
+ const context = violation.context
356
+ ? ` [${Object.values(violation.context).filter(Boolean).join(", ")}]`
357
+ : "";
358
+ lines.push(` - ${violation.message}${context}`);
359
+ }
360
+ }
361
+ return lines.join("\n");
362
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Policy module for VertaaUX CLI.
3
+ *
4
+ * Provides policy-as-code support for organizations to define
5
+ * quality standards committed to the repository.
6
+ *
7
+ * Implements CICD-17: Policy-as-code support.
8
+ */
9
+ export type { PolicyFile, PolicyAssertions, RuleOverride, BranchPolicy, PolicyTemplate, } from "./schema.js";
10
+ export { policyJsonSchema, DEFAULT_POLICY, POLICY_TEMPLATES, } from "./schema.js";
11
+ export type { PolicyLoadResult, PolicyValidationResult } from "./loader.js";
12
+ export { loadPolicy, loadPolicyFile, validatePolicy, resolveBranchPolicy, getEffectivePolicy, PolicyValidationError, PolicyLoadError, } from "./loader.js";
13
+ export type { AuditResultForPolicy, PolicyViolation, PolicyEvaluationResult, } from "./evaluator.js";
14
+ export { evaluatePolicy, applyRuleOverrides, filterExcludedPaths, formatPolicyResult, } from "./evaluator.js";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/policy/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EACV,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAGrB,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EACL,UAAU,EACV,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,GAChB,MAAM,aAAa,CAAC;AAGrB,YAAY,EACV,oBAAoB,EACpB,eAAe,EACf,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC"}