@qulib/core 0.7.0 → 0.9.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 (116) hide show
  1. package/README.md +30 -5
  2. package/bin/qulib.js +2 -3
  3. package/dist/__tests__/fixtures/api-fixture-repo/app/api/orders/route.d.ts +7 -0
  4. package/dist/__tests__/fixtures/api-fixture-repo/app/api/orders/route.d.ts.map +1 -0
  5. package/dist/__tests__/fixtures/api-fixture-repo/app/api/orders/route.js +7 -0
  6. package/dist/__tests__/fixtures/api-fixture-repo/app/api/users/route.d.ts +10 -0
  7. package/dist/__tests__/fixtures/api-fixture-repo/app/api/users/route.d.ts.map +1 -0
  8. package/dist/__tests__/fixtures/api-fixture-repo/app/api/users/route.js +9 -0
  9. package/dist/__tests__/fixtures/api-fixture-repo/pages/api/health.d.ts +9 -0
  10. package/dist/__tests__/fixtures/api-fixture-repo/pages/api/health.d.ts.map +1 -0
  11. package/dist/__tests__/fixtures/api-fixture-repo/pages/api/health.js +10 -0
  12. package/dist/__tests__/playwright-available.d.ts +32 -0
  13. package/dist/__tests__/playwright-available.d.ts.map +1 -0
  14. package/dist/__tests__/playwright-available.js +35 -0
  15. package/dist/adapters/api-adapter.d.ts +26 -0
  16. package/dist/adapters/api-adapter.d.ts.map +1 -1
  17. package/dist/adapters/api-adapter.js +156 -2
  18. package/dist/adapters/ci-results-adapter.d.ts +67 -0
  19. package/dist/adapters/ci-results-adapter.d.ts.map +1 -0
  20. package/dist/adapters/ci-results-adapter.js +143 -0
  21. package/dist/adapters/cypress-e2e-adapter.d.ts.map +1 -1
  22. package/dist/adapters/cypress-e2e-adapter.js +25 -2
  23. package/dist/adapters/playwright-adapter.d.ts.map +1 -1
  24. package/dist/adapters/playwright-adapter.js +94 -2
  25. package/dist/adapters/pr-metadata-adapter.d.ts +75 -0
  26. package/dist/adapters/pr-metadata-adapter.d.ts.map +1 -0
  27. package/dist/adapters/pr-metadata-adapter.js +146 -0
  28. package/dist/adapters/validate-specs.d.ts +55 -0
  29. package/dist/adapters/validate-specs.d.ts.map +1 -0
  30. package/dist/adapters/validate-specs.js +67 -0
  31. package/dist/baseline/baseline.d.ts +54 -0
  32. package/dist/baseline/baseline.d.ts.map +1 -0
  33. package/dist/baseline/baseline.js +252 -0
  34. package/dist/baseline/baseline.schema.d.ts +233 -0
  35. package/dist/baseline/baseline.schema.d.ts.map +1 -0
  36. package/dist/baseline/baseline.schema.js +59 -0
  37. package/dist/cli/confidence-run.d.ts +16 -0
  38. package/dist/cli/confidence-run.d.ts.map +1 -0
  39. package/dist/cli/confidence-run.js +158 -0
  40. package/dist/cli/index.d.ts +11 -1
  41. package/dist/cli/index.d.ts.map +1 -1
  42. package/dist/cli/index.js +80 -4
  43. package/dist/cli/scaffold-run.d.ts +86 -0
  44. package/dist/cli/scaffold-run.d.ts.map +1 -0
  45. package/dist/cli/scaffold-run.js +232 -0
  46. package/dist/cli/score-automation-run.d.ts +25 -0
  47. package/dist/cli/score-automation-run.d.ts.map +1 -0
  48. package/dist/cli/score-automation-run.js +123 -0
  49. package/dist/examples/notquality-dogfood/fixture.d.ts +166 -0
  50. package/dist/examples/notquality-dogfood/fixture.d.ts.map +1 -0
  51. package/dist/examples/notquality-dogfood/fixture.js +174 -0
  52. package/dist/examples/notquality-dogfood/run.d.ts +34 -0
  53. package/dist/examples/notquality-dogfood/run.d.ts.map +1 -0
  54. package/dist/examples/notquality-dogfood/run.js +139 -0
  55. package/dist/index.d.ts +18 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +13 -0
  58. package/dist/recipes/a11y.d.ts +36 -0
  59. package/dist/recipes/a11y.d.ts.map +1 -0
  60. package/dist/recipes/a11y.js +118 -0
  61. package/dist/recipes/auth.d.ts +38 -0
  62. package/dist/recipes/auth.d.ts.map +1 -0
  63. package/dist/recipes/auth.js +156 -0
  64. package/dist/recipes/index.d.ts +26 -0
  65. package/dist/recipes/index.d.ts.map +1 -0
  66. package/dist/recipes/index.js +41 -0
  67. package/dist/recipes/nav.d.ts +34 -0
  68. package/dist/recipes/nav.d.ts.map +1 -0
  69. package/dist/recipes/nav.js +128 -0
  70. package/dist/recipes/seed.d.ts +34 -0
  71. package/dist/recipes/seed.d.ts.map +1 -0
  72. package/dist/recipes/seed.js +87 -0
  73. package/dist/scaffold-tests.d.ts +21 -0
  74. package/dist/scaffold-tests.d.ts.map +1 -1
  75. package/dist/scaffold-tests.js +12 -2
  76. package/dist/schemas/automation-maturity.schema.d.ts +8 -8
  77. package/dist/schemas/automation-maturity.schema.d.ts.map +1 -1
  78. package/dist/schemas/automation-maturity.schema.js +1 -0
  79. package/dist/schemas/confidence.schema.d.ts +526 -0
  80. package/dist/schemas/confidence.schema.d.ts.map +1 -0
  81. package/dist/schemas/confidence.schema.js +161 -0
  82. package/dist/schemas/gap-analysis.schema.d.ts +8 -8
  83. package/dist/schemas/gap-analysis.schema.js +1 -1
  84. package/dist/schemas/index.d.ts +3 -0
  85. package/dist/schemas/index.d.ts.map +1 -1
  86. package/dist/schemas/index.js +3 -0
  87. package/dist/schemas/public-surface.schema.d.ts +5 -5
  88. package/dist/schemas/recipe.schema.d.ts +66 -0
  89. package/dist/schemas/recipe.schema.d.ts.map +1 -0
  90. package/dist/schemas/recipe.schema.js +45 -0
  91. package/dist/schemas/repo-analysis.schema.d.ts +7 -7
  92. package/dist/schemas/views.schema.d.ts +234 -0
  93. package/dist/schemas/views.schema.d.ts.map +1 -0
  94. package/dist/schemas/views.schema.js +82 -0
  95. package/dist/tools/repo/api-surface.d.ts +59 -0
  96. package/dist/tools/repo/api-surface.d.ts.map +1 -0
  97. package/dist/tools/repo/api-surface.js +414 -0
  98. package/dist/tools/scoring/api-coverage.d.ts +74 -0
  99. package/dist/tools/scoring/api-coverage.d.ts.map +1 -0
  100. package/dist/tools/scoring/api-coverage.js +158 -0
  101. package/dist/tools/scoring/automation-maturity.d.ts +11 -1
  102. package/dist/tools/scoring/automation-maturity.d.ts.map +1 -1
  103. package/dist/tools/scoring/automation-maturity.js +43 -9
  104. package/dist/tools/scoring/confidence-from-qulib.d.ts +34 -0
  105. package/dist/tools/scoring/confidence-from-qulib.d.ts.map +1 -0
  106. package/dist/tools/scoring/confidence-from-qulib.js +206 -0
  107. package/dist/tools/scoring/confidence-views.d.ts +40 -0
  108. package/dist/tools/scoring/confidence-views.d.ts.map +1 -0
  109. package/dist/tools/scoring/confidence-views.js +163 -0
  110. package/dist/tools/scoring/confidence.d.ts +32 -0
  111. package/dist/tools/scoring/confidence.d.ts.map +1 -0
  112. package/dist/tools/scoring/confidence.js +180 -0
  113. package/dist/tools/scoring/levels.d.ts +15 -0
  114. package/dist/tools/scoring/levels.d.ts.map +1 -0
  115. package/dist/tools/scoring/levels.js +21 -0
  116. package/package.json +15 -7
@@ -0,0 +1,252 @@
1
+ import { readFile, writeFile, mkdir, readdir, unlink } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import { join, extname } from 'node:path';
4
+ import { BaselineSnapshotSchema, BaselineDeltaSchema, } from './baseline.schema.js';
5
+ const BASELINE_DIR_NAME = '.qulib-baselines';
6
+ /**
7
+ * Resolve the directory where baselines for the given URL are stored.
8
+ * Each URL gets its own subdirectory under baseDir, keyed by a stable slug.
9
+ */
10
+ function resolveBaselineDir(baseDir, urlSlug) {
11
+ return join(baseDir, urlSlug);
12
+ }
13
+ /**
14
+ * Produce a filesystem-safe slug from a URL.
15
+ * e.g. "https://my-app.vercel.app/admin" → "my-app_vercel_app__admin"
16
+ */
17
+ export function slugifyUrl(url) {
18
+ return url
19
+ .replace(/^https?:\/\//, '')
20
+ .replace(/[^a-zA-Z0-9._-]/g, '_')
21
+ .replace(/__+/g, '_')
22
+ .replace(/^_|_$/g, '')
23
+ .slice(0, 80);
24
+ }
25
+ /**
26
+ * Return the default root for baseline storage: `<cwd>/.qulib-baselines`.
27
+ * Callers may supply an explicit `baseDir` to override.
28
+ */
29
+ export function defaultBaselineRoot() {
30
+ return join(process.cwd(), BASELINE_DIR_NAME);
31
+ }
32
+ /**
33
+ * Convert a `GapAnalysis` result into the compact `BaselineGap[]` shape.
34
+ * Drops fields not needed for delta comparison (id, description, recommendation).
35
+ */
36
+ function toBaselineGaps(gaps) {
37
+ return gaps.map((g) => ({
38
+ path: g.path,
39
+ severity: g.severity,
40
+ category: g.category,
41
+ reason: g.reason,
42
+ }));
43
+ }
44
+ /**
45
+ * Save a baseline snapshot derived from the given `GapAnalysis` result.
46
+ *
47
+ * @returns The saved snapshot.
48
+ */
49
+ export async function saveBaseline(analysis, url, options = {}) {
50
+ const baseDir = options.baseDir ?? defaultBaselineRoot();
51
+ const urlSlug = slugifyUrl(url);
52
+ const dir = resolveBaselineDir(baseDir, urlSlug);
53
+ await mkdir(dir, { recursive: true });
54
+ const now = new Date();
55
+ // Include milliseconds (23 chars: "2024-01-01T00-00-00-000") so rapid successive saves
56
+ // do not collide on the same filename within the same second.
57
+ const timestamp = now.toISOString().replace(/[:.]/g, '-').slice(0, 23);
58
+ const id = `${urlSlug}__${timestamp}`;
59
+ const filename = `${id}.json`;
60
+ const snapshot = {
61
+ id,
62
+ url,
63
+ savedAt: now.toISOString(),
64
+ releaseConfidence: analysis.releaseConfidence ?? 0,
65
+ gapCount: analysis.gaps.length,
66
+ gaps: toBaselineGaps(analysis.gaps),
67
+ ...(options.label !== undefined ? { label: options.label } : {}),
68
+ };
69
+ const validated = BaselineSnapshotSchema.parse(snapshot);
70
+ await writeFile(join(dir, filename), JSON.stringify(validated, null, 2), 'utf8');
71
+ return validated;
72
+ }
73
+ /**
74
+ * Load a specific baseline snapshot by its `id`.
75
+ *
76
+ * @throws If the file does not exist or fails schema validation.
77
+ */
78
+ export async function loadBaseline(id, options = {}) {
79
+ const baseDir = options.baseDir ?? defaultBaselineRoot();
80
+ // id encodes the urlSlug: <urlSlug>__<timestamp>
81
+ const doubleUnderIndex = id.lastIndexOf('__');
82
+ if (doubleUnderIndex < 0) {
83
+ throw new Error(`Invalid baseline id (no __ separator): ${id}`);
84
+ }
85
+ const urlSlug = id.slice(0, doubleUnderIndex);
86
+ const dir = resolveBaselineDir(baseDir, urlSlug);
87
+ const filepath = join(dir, `${id}.json`);
88
+ if (!existsSync(filepath)) {
89
+ throw new Error(`Baseline not found: ${id} (${filepath})`);
90
+ }
91
+ const raw = await readFile(filepath, 'utf8');
92
+ let parsed;
93
+ try {
94
+ parsed = JSON.parse(raw);
95
+ }
96
+ catch {
97
+ throw new Error(`Baseline file is not valid JSON: ${filepath}`);
98
+ }
99
+ return BaselineSnapshotSchema.parse(parsed);
100
+ }
101
+ /**
102
+ * List all saved baselines for the given URL, sorted newest-first.
103
+ *
104
+ * Returns an empty array if no baselines exist yet.
105
+ */
106
+ export async function listBaselines(url, options = {}) {
107
+ const baseDir = options.baseDir ?? defaultBaselineRoot();
108
+ const urlSlug = slugifyUrl(url);
109
+ const dir = resolveBaselineDir(baseDir, urlSlug);
110
+ if (!existsSync(dir)) {
111
+ return [];
112
+ }
113
+ let entries;
114
+ try {
115
+ entries = await readdir(dir);
116
+ }
117
+ catch {
118
+ return [];
119
+ }
120
+ const snapshots = [];
121
+ for (const entry of entries) {
122
+ if (extname(entry) !== '.json')
123
+ continue;
124
+ const filepath = join(dir, entry);
125
+ const raw = await readFile(filepath, 'utf8');
126
+ let parsed;
127
+ try {
128
+ parsed = JSON.parse(raw);
129
+ }
130
+ catch {
131
+ continue;
132
+ }
133
+ const result = BaselineSnapshotSchema.safeParse(parsed);
134
+ if (result.success) {
135
+ snapshots.push(result.data);
136
+ }
137
+ }
138
+ // Newest first
139
+ snapshots.sort((a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime());
140
+ return snapshots;
141
+ }
142
+ /**
143
+ * Delete a specific baseline snapshot by its `id`.
144
+ *
145
+ * @throws If the file does not exist.
146
+ */
147
+ export async function deleteBaseline(id, options = {}) {
148
+ const baseDir = options.baseDir ?? defaultBaselineRoot();
149
+ const doubleUnderIndex = id.lastIndexOf('__');
150
+ if (doubleUnderIndex < 0) {
151
+ throw new Error(`Invalid baseline id (no __ separator): ${id}`);
152
+ }
153
+ const urlSlug = id.slice(0, doubleUnderIndex);
154
+ const dir = resolveBaselineDir(baseDir, urlSlug);
155
+ const filepath = join(dir, `${id}.json`);
156
+ if (!existsSync(filepath)) {
157
+ throw new Error(`Baseline not found: ${id}`);
158
+ }
159
+ await unlink(filepath);
160
+ }
161
+ // ---------------------------------------------------------------------------
162
+ // Delta detection
163
+ // ---------------------------------------------------------------------------
164
+ /**
165
+ * Stable key used to match gaps across snapshots for delta detection.
166
+ * Two gaps are "the same problem" when they share path + category.
167
+ */
168
+ function gapKey(g) {
169
+ return `${g.path}|||${g.category}`;
170
+ }
171
+ /**
172
+ * Compare two baseline snapshots and return a structured delta report.
173
+ *
174
+ * - `newGaps`: problems present in `current` but not in `prior`.
175
+ * - `resolvedGaps`: problems present in `prior` but no longer in `current`.
176
+ * - `severityChanges`: same problem (matching key) with a different severity.
177
+ */
178
+ export function compareBaselines(prior, current) {
179
+ const priorMap = new Map();
180
+ for (const g of prior.gaps) {
181
+ priorMap.set(gapKey(g), g);
182
+ }
183
+ const currentMap = new Map();
184
+ for (const g of current.gaps) {
185
+ currentMap.set(gapKey(g), g);
186
+ }
187
+ const severityOrder = {
188
+ critical: 4,
189
+ high: 3,
190
+ medium: 2,
191
+ low: 1,
192
+ };
193
+ const newGaps = [];
194
+ const resolvedGaps = [];
195
+ const severityChanges = [];
196
+ // Gaps in current that were not in prior → new
197
+ for (const [key, g] of currentMap) {
198
+ if (!priorMap.has(key)) {
199
+ newGaps.push({ path: g.path, category: g.category, severity: g.severity, reason: g.reason, status: 'new' });
200
+ }
201
+ else {
202
+ const prev = priorMap.get(key);
203
+ if (prev.severity !== g.severity) {
204
+ const prevOrd = severityOrder[prev.severity];
205
+ const currOrd = severityOrder[g.severity];
206
+ severityChanges.push({
207
+ path: g.path,
208
+ category: g.category,
209
+ severity: g.severity,
210
+ reason: g.reason,
211
+ status: currOrd > prevOrd ? 'severity-increased' : 'severity-decreased',
212
+ });
213
+ }
214
+ }
215
+ }
216
+ // Gaps in prior that are no longer in current → resolved
217
+ for (const [key, g] of priorMap) {
218
+ if (!currentMap.has(key)) {
219
+ resolvedGaps.push({
220
+ path: g.path,
221
+ category: g.category,
222
+ severity: g.severity,
223
+ reason: g.reason,
224
+ status: 'resolved',
225
+ });
226
+ }
227
+ }
228
+ const confidenceDelta = current.releaseConfidence - prior.releaseConfidence;
229
+ const direction = confidenceDelta > 0 ? 'improved' : confidenceDelta < 0 ? 'regressed' : 'unchanged';
230
+ const summary = [
231
+ `Confidence ${direction} (${prior.releaseConfidence} → ${current.releaseConfidence})`,
232
+ newGaps.length > 0 ? `${newGaps.length} new gap(s)` : '',
233
+ resolvedGaps.length > 0 ? `${resolvedGaps.length} resolved gap(s)` : '',
234
+ severityChanges.length > 0 ? `${severityChanges.length} severity change(s)` : '',
235
+ ]
236
+ .filter(Boolean)
237
+ .join(', ');
238
+ const delta = {
239
+ fromId: prior.id,
240
+ toId: current.id,
241
+ fromSavedAt: prior.savedAt,
242
+ toSavedAt: current.savedAt,
243
+ fromReleaseConfidence: prior.releaseConfidence,
244
+ toReleaseConfidence: current.releaseConfidence,
245
+ confidenceDelta,
246
+ newGaps,
247
+ resolvedGaps,
248
+ severityChanges,
249
+ summary,
250
+ };
251
+ return BaselineDeltaSchema.parse(delta);
252
+ }
@@ -0,0 +1,233 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * A snapshot of a single gap found during a scan, stored in a baseline.
4
+ * Intentionally lighter than the full GapSchema: only the fields needed to
5
+ * detect meaningful drift between scans are captured.
6
+ */
7
+ export declare const BaselineGapSchema: z.ZodObject<{
8
+ path: z.ZodString;
9
+ severity: z.ZodEnum<["critical", "high", "medium", "low"]>;
10
+ category: z.ZodEnum<["untested-route", "a11y", "console-error", "broken-link", "auth-surface", "coverage", "untested-api-endpoint"]>;
11
+ reason: z.ZodString;
12
+ }, "strip", z.ZodTypeAny, {
13
+ path: string;
14
+ severity: "critical" | "high" | "medium" | "low";
15
+ reason: string;
16
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
17
+ }, {
18
+ path: string;
19
+ severity: "critical" | "high" | "medium" | "low";
20
+ reason: string;
21
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
22
+ }>;
23
+ export type BaselineGap = z.infer<typeof BaselineGapSchema>;
24
+ /**
25
+ * A persisted baseline snapshot for a given URL, saved by `qulib baseline save`.
26
+ */
27
+ export declare const BaselineSnapshotSchema: z.ZodObject<{
28
+ /** Monotonic slug used as the on-disk filename stem: <url-slug>__<timestamp> */
29
+ id: z.ZodString;
30
+ url: z.ZodString;
31
+ savedAt: z.ZodString;
32
+ releaseConfidence: z.ZodNumber;
33
+ gapCount: z.ZodNumber;
34
+ gaps: z.ZodArray<z.ZodObject<{
35
+ path: z.ZodString;
36
+ severity: z.ZodEnum<["critical", "high", "medium", "low"]>;
37
+ category: z.ZodEnum<["untested-route", "a11y", "console-error", "broken-link", "auth-surface", "coverage", "untested-api-endpoint"]>;
38
+ reason: z.ZodString;
39
+ }, "strip", z.ZodTypeAny, {
40
+ path: string;
41
+ severity: "critical" | "high" | "medium" | "low";
42
+ reason: string;
43
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
44
+ }, {
45
+ path: string;
46
+ severity: "critical" | "high" | "medium" | "low";
47
+ reason: string;
48
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
49
+ }>, "many">;
50
+ label: z.ZodOptional<z.ZodString>;
51
+ }, "strip", z.ZodTypeAny, {
52
+ id: string;
53
+ url: string;
54
+ releaseConfidence: number;
55
+ gaps: {
56
+ path: string;
57
+ severity: "critical" | "high" | "medium" | "low";
58
+ reason: string;
59
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
60
+ }[];
61
+ gapCount: number;
62
+ savedAt: string;
63
+ label?: string | undefined;
64
+ }, {
65
+ id: string;
66
+ url: string;
67
+ releaseConfidence: number;
68
+ gaps: {
69
+ path: string;
70
+ severity: "critical" | "high" | "medium" | "low";
71
+ reason: string;
72
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
73
+ }[];
74
+ gapCount: number;
75
+ savedAt: string;
76
+ label?: string | undefined;
77
+ }>;
78
+ export type BaselineSnapshot = z.infer<typeof BaselineSnapshotSchema>;
79
+ /**
80
+ * A single change in gap status between two baselines.
81
+ */
82
+ export declare const BaselineDeltaItemSchema: z.ZodObject<{
83
+ path: z.ZodString;
84
+ category: z.ZodEnum<["untested-route", "a11y", "console-error", "broken-link", "auth-surface", "coverage", "untested-api-endpoint"]>;
85
+ severity: z.ZodEnum<["critical", "high", "medium", "low"]>;
86
+ reason: z.ZodString;
87
+ status: z.ZodEnum<["new", "resolved", "severity-increased", "severity-decreased"]>;
88
+ }, "strip", z.ZodTypeAny, {
89
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
90
+ path: string;
91
+ severity: "critical" | "high" | "medium" | "low";
92
+ reason: string;
93
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
94
+ }, {
95
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
96
+ path: string;
97
+ severity: "critical" | "high" | "medium" | "low";
98
+ reason: string;
99
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
100
+ }>;
101
+ export type BaselineDeltaItem = z.infer<typeof BaselineDeltaItemSchema>;
102
+ /**
103
+ * The result of comparing two snapshots.
104
+ */
105
+ export declare const BaselineDeltaSchema: z.ZodObject<{
106
+ fromId: z.ZodString;
107
+ toId: z.ZodString;
108
+ fromSavedAt: z.ZodString;
109
+ toSavedAt: z.ZodString;
110
+ fromReleaseConfidence: z.ZodNumber;
111
+ toReleaseConfidence: z.ZodNumber;
112
+ confidenceDelta: z.ZodNumber;
113
+ newGaps: z.ZodArray<z.ZodObject<{
114
+ path: z.ZodString;
115
+ category: z.ZodEnum<["untested-route", "a11y", "console-error", "broken-link", "auth-surface", "coverage", "untested-api-endpoint"]>;
116
+ severity: z.ZodEnum<["critical", "high", "medium", "low"]>;
117
+ reason: z.ZodString;
118
+ status: z.ZodEnum<["new", "resolved", "severity-increased", "severity-decreased"]>;
119
+ }, "strip", z.ZodTypeAny, {
120
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
121
+ path: string;
122
+ severity: "critical" | "high" | "medium" | "low";
123
+ reason: string;
124
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
125
+ }, {
126
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
127
+ path: string;
128
+ severity: "critical" | "high" | "medium" | "low";
129
+ reason: string;
130
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
131
+ }>, "many">;
132
+ resolvedGaps: z.ZodArray<z.ZodObject<{
133
+ path: z.ZodString;
134
+ category: z.ZodEnum<["untested-route", "a11y", "console-error", "broken-link", "auth-surface", "coverage", "untested-api-endpoint"]>;
135
+ severity: z.ZodEnum<["critical", "high", "medium", "low"]>;
136
+ reason: z.ZodString;
137
+ status: z.ZodEnum<["new", "resolved", "severity-increased", "severity-decreased"]>;
138
+ }, "strip", z.ZodTypeAny, {
139
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
140
+ path: string;
141
+ severity: "critical" | "high" | "medium" | "low";
142
+ reason: string;
143
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
144
+ }, {
145
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
146
+ path: string;
147
+ severity: "critical" | "high" | "medium" | "low";
148
+ reason: string;
149
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
150
+ }>, "many">;
151
+ severityChanges: z.ZodArray<z.ZodObject<{
152
+ path: z.ZodString;
153
+ category: z.ZodEnum<["untested-route", "a11y", "console-error", "broken-link", "auth-surface", "coverage", "untested-api-endpoint"]>;
154
+ severity: z.ZodEnum<["critical", "high", "medium", "low"]>;
155
+ reason: z.ZodString;
156
+ status: z.ZodEnum<["new", "resolved", "severity-increased", "severity-decreased"]>;
157
+ }, "strip", z.ZodTypeAny, {
158
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
159
+ path: string;
160
+ severity: "critical" | "high" | "medium" | "low";
161
+ reason: string;
162
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
163
+ }, {
164
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
165
+ path: string;
166
+ severity: "critical" | "high" | "medium" | "low";
167
+ reason: string;
168
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
169
+ }>, "many">;
170
+ summary: z.ZodString;
171
+ }, "strip", z.ZodTypeAny, {
172
+ summary: string;
173
+ fromId: string;
174
+ toId: string;
175
+ fromSavedAt: string;
176
+ toSavedAt: string;
177
+ fromReleaseConfidence: number;
178
+ toReleaseConfidence: number;
179
+ confidenceDelta: number;
180
+ newGaps: {
181
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
182
+ path: string;
183
+ severity: "critical" | "high" | "medium" | "low";
184
+ reason: string;
185
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
186
+ }[];
187
+ resolvedGaps: {
188
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
189
+ path: string;
190
+ severity: "critical" | "high" | "medium" | "low";
191
+ reason: string;
192
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
193
+ }[];
194
+ severityChanges: {
195
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
196
+ path: string;
197
+ severity: "critical" | "high" | "medium" | "low";
198
+ reason: string;
199
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
200
+ }[];
201
+ }, {
202
+ summary: string;
203
+ fromId: string;
204
+ toId: string;
205
+ fromSavedAt: string;
206
+ toSavedAt: string;
207
+ fromReleaseConfidence: number;
208
+ toReleaseConfidence: number;
209
+ confidenceDelta: number;
210
+ newGaps: {
211
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
212
+ path: string;
213
+ severity: "critical" | "high" | "medium" | "low";
214
+ reason: string;
215
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
216
+ }[];
217
+ resolvedGaps: {
218
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
219
+ path: string;
220
+ severity: "critical" | "high" | "medium" | "low";
221
+ reason: string;
222
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
223
+ }[];
224
+ severityChanges: {
225
+ status: "new" | "resolved" | "severity-increased" | "severity-decreased";
226
+ path: string;
227
+ severity: "critical" | "high" | "medium" | "low";
228
+ reason: string;
229
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
230
+ }[];
231
+ }>;
232
+ export type BaselineDelta = z.infer<typeof BaselineDeltaSchema>;
233
+ //# sourceMappingURL=baseline.schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.schema.d.ts","sourceRoot":"","sources":["../../src/baseline/baseline.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;EAa5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,sBAAsB;IACjC,gFAAgF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQhF,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;EAMlC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;GAEG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAY9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * A snapshot of a single gap found during a scan, stored in a baseline.
4
+ * Intentionally lighter than the full GapSchema: only the fields needed to
5
+ * detect meaningful drift between scans are captured.
6
+ */
7
+ export const BaselineGapSchema = z.object({
8
+ path: z.string(),
9
+ severity: z.enum(['critical', 'high', 'medium', 'low']),
10
+ category: z.enum([
11
+ 'untested-route',
12
+ 'a11y',
13
+ 'console-error',
14
+ 'broken-link',
15
+ 'auth-surface',
16
+ 'coverage',
17
+ 'untested-api-endpoint',
18
+ ]),
19
+ reason: z.string(),
20
+ });
21
+ /**
22
+ * A persisted baseline snapshot for a given URL, saved by `qulib baseline save`.
23
+ */
24
+ export const BaselineSnapshotSchema = z.object({
25
+ /** Monotonic slug used as the on-disk filename stem: <url-slug>__<timestamp> */
26
+ id: z.string(),
27
+ url: z.string(),
28
+ savedAt: z.string().datetime(),
29
+ releaseConfidence: z.number().min(0).max(100),
30
+ gapCount: z.number().int().min(0),
31
+ gaps: z.array(BaselineGapSchema),
32
+ label: z.string().optional(),
33
+ });
34
+ /**
35
+ * A single change in gap status between two baselines.
36
+ */
37
+ export const BaselineDeltaItemSchema = z.object({
38
+ path: z.string(),
39
+ category: BaselineGapSchema.shape.category,
40
+ severity: BaselineGapSchema.shape.severity,
41
+ reason: z.string(),
42
+ status: z.enum(['new', 'resolved', 'severity-increased', 'severity-decreased']),
43
+ });
44
+ /**
45
+ * The result of comparing two snapshots.
46
+ */
47
+ export const BaselineDeltaSchema = z.object({
48
+ fromId: z.string(),
49
+ toId: z.string(),
50
+ fromSavedAt: z.string().datetime(),
51
+ toSavedAt: z.string().datetime(),
52
+ fromReleaseConfidence: z.number().min(0).max(100),
53
+ toReleaseConfidence: z.number().min(0).max(100),
54
+ confidenceDelta: z.number(),
55
+ newGaps: z.array(BaselineDeltaItemSchema),
56
+ resolvedGaps: z.array(BaselineDeltaItemSchema),
57
+ severityChanges: z.array(BaselineDeltaItemSchema),
58
+ summary: z.string(),
59
+ });
@@ -0,0 +1,16 @@
1
+ import type { Command } from 'commander';
2
+ import type { ReleaseConfidence } from '../schemas/confidence.schema.js';
3
+ export interface ConfidenceOptions {
4
+ url?: string;
5
+ repo?: string;
6
+ json?: boolean;
7
+ }
8
+ /** Render the human-friendly report for a ReleaseConfidence result. */
9
+ export declare function formatConfidenceReport(rc: ReleaseConfidence, subjectRef: string): string;
10
+ /**
11
+ * Core of the command, factored out of the action handler so node:test can drive it
12
+ * without spawning a subprocess.
13
+ */
14
+ export declare function runConfidence(options: ConfidenceOptions, out?: (line: string) => void): Promise<ReleaseConfidence>;
15
+ export declare function registerConfidenceCommand(program: Command): void;
16
+ //# sourceMappingURL=confidence-run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confidence-run.d.ts","sourceRoot":"","sources":["../../src/cli/confidence-run.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQzC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGzE,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAiBD,uEAAuE;AACvE,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAuCxF;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,iBAAiB,EAC1B,GAAG,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAkC,GACxD,OAAO,CAAC,iBAAiB,CAAC,CAmE5B;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmBhE"}