qa-engineer 0.10.0 → 0.12.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 (76) hide show
  1. package/README.md +167 -59
  2. package/package.json +1 -1
  3. package/packages/engine/bin/qa-engine.mjs +232 -8
  4. package/packages/engine/lib/analysis/har.mjs +34 -0
  5. package/packages/engine/lib/analysis/network.mjs +237 -0
  6. package/packages/engine/lib/analysis/report-html.mjs +47 -734
  7. package/packages/engine/lib/artifacts/manager.mjs +453 -0
  8. package/packages/engine/lib/artifacts/mime.mjs +109 -0
  9. package/packages/engine/lib/artifacts/zip-write.mjs +143 -0
  10. package/packages/engine/lib/report/components/charts.mjs +424 -0
  11. package/packages/engine/lib/report/components/evidence.mjs +207 -0
  12. package/packages/engine/lib/report/components/findings.mjs +258 -0
  13. package/packages/engine/lib/report/components/nav.mjs +99 -0
  14. package/packages/engine/lib/report/components/primitives.mjs +246 -0
  15. package/packages/engine/lib/report/components/runtime.mjs +246 -0
  16. package/packages/engine/lib/report/components/timeline.mjs +65 -0
  17. package/packages/engine/lib/report/core/model.mjs +270 -0
  18. package/packages/engine/lib/report/core/normalize.mjs +226 -0
  19. package/packages/engine/lib/report/core/sections.mjs +978 -0
  20. package/packages/engine/lib/report/export/bundle.mjs +293 -0
  21. package/packages/engine/lib/report/export/html.mjs +183 -0
  22. package/packages/engine/lib/report/export/machine.mjs +290 -0
  23. package/packages/engine/lib/report/export/markdown.mjs +323 -0
  24. package/packages/engine/lib/report/schemas/qa-report.schema.json +555 -0
  25. package/packages/engine/lib/report/theme/css.mjs +529 -0
  26. package/packages/engine/lib/report/theme/tokens.mjs +137 -0
  27. package/packages/engine/lib/report/version.mjs +78 -0
  28. package/packages/engine/package.json +2 -2
  29. package/packages/installer/lib/agents/targets.mjs +90 -0
  30. package/packages/installer/lib/agents/user-level.mjs +80 -0
  31. package/packages/installer/lib/cli/flags.mjs +20 -2
  32. package/packages/installer/lib/commands/doctor.mjs +6 -4
  33. package/packages/installer/lib/commands/install.mjs +134 -93
  34. package/packages/installer/lib/commands/repair.mjs +10 -6
  35. package/packages/installer/lib/commands/self-test.mjs +4 -3
  36. package/packages/installer/lib/commands/uninstall.mjs +14 -8
  37. package/packages/installer/lib/commands/update.mjs +9 -4
  38. package/packages/installer/lib/commands/verify.mjs +13 -12
  39. package/packages/installer/lib/constants.mjs +13 -0
  40. package/packages/installer/lib/core/conflict.mjs +5 -4
  41. package/packages/installer/lib/core/fs-safe.mjs +146 -6
  42. package/packages/installer/lib/core/integrity.mjs +59 -0
  43. package/packages/installer/lib/core/lockfile.mjs +19 -3
  44. package/packages/installer/lib/core/plan.mjs +213 -0
  45. package/packages/installer/lib/core/qa-home.mjs +145 -0
  46. package/packages/installer/lib/core/scope.mjs +274 -0
  47. package/packages/installer/lib/core/validate-install.mjs +37 -12
  48. package/packages/installer/package.json +1 -1
  49. package/packages/installer/schemas/qa-lock.schema.json +119 -21
  50. package/shared/tooling/qa-tool.mjs +41 -5
  51. package/skills/qa-api/scripts/qa-tool.mjs +41 -5
  52. package/skills/qa-audit/scripts/qa-tool.mjs +41 -5
  53. package/skills/qa-debug/SKILL.md +3 -0
  54. package/skills/qa-debug/references/failure-handoff.md +44 -0
  55. package/skills/qa-debug/scripts/qa-tool.mjs +41 -5
  56. package/skills/qa-explore/SKILL.md +26 -10
  57. package/skills/qa-explore/contracts/explore-result.schema.json +517 -11
  58. package/skills/qa-explore/references/api-replay.md +40 -1
  59. package/skills/qa-explore/references/report-pipeline.md +265 -95
  60. package/skills/qa-explore/scripts/qa-tool.mjs +41 -5
  61. package/skills/qa-fix/scripts/qa-tool.mjs +41 -5
  62. package/skills/qa-flaky/scripts/qa-tool.mjs +41 -5
  63. package/skills/qa-init/scripts/qa-tool.mjs +41 -5
  64. package/skills/qa-report/scripts/qa-tool.mjs +41 -5
  65. package/skills/qa-run/README.md +7 -0
  66. package/skills/qa-run/SKILL.md +12 -7
  67. package/skills/qa-run/contracts/execution-result.schema.json +77 -0
  68. package/skills/qa-run/examples/execute-playwright.md +42 -10
  69. package/skills/qa-run/examples/plan-a-run.md +5 -3
  70. package/skills/qa-run/references/artifact-collector.md +2 -1
  71. package/skills/qa-run/references/command-builder.md +18 -1
  72. package/skills/qa-run/references/execution-strategy.md +2 -0
  73. package/skills/qa-run/references/failure-handoff.md +44 -0
  74. package/skills/qa-run/references/playwright-artifacts.md +3 -1
  75. package/skills/qa-run/references/playwright-execution.md +14 -1
  76. package/skills/qa-run/scripts/qa-tool.mjs +41 -5
@@ -0,0 +1,555 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "urn:qa-pack:contract:qa-engineer:qa-report:2",
4
+ "title": "qa-engineer canonical QA report",
5
+ "description": "The producer-neutral report contract. Any agent — Claude Code, Cursor, Codex, Gemini, a future one — emits this and nothing else; the renderer turns it into every output format. There is deliberately no field for a colour, a class, a font, a template, or any other presentation hint: an agent has no mechanism to make its report look different, which is what makes two agents' reports identical.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["contract", "schemaVersion", "metadata", "summary"],
9
+ "properties": {
10
+ "contract": {
11
+ "type": "object",
12
+ "additionalProperties": false,
13
+ "required": ["name", "version"],
14
+ "properties": {
15
+ "name": { "const": "qa-engineer/qa-report" },
16
+ "version": { "type": "string", "pattern": "^2\\.[0-9]+(\\.[0-9]+)?$" }
17
+ }
18
+ },
19
+ "schemaVersion": { "const": "2.0" },
20
+
21
+ "metadata": {
22
+ "description": "What was tested, when, by what. Provenance only — the renderer records it and never varies its behaviour on it.",
23
+ "type": "object",
24
+ "additionalProperties": false,
25
+ "required": ["generatedAt"],
26
+ "properties": {
27
+ "title": { "type": "string", "minLength": 1 },
28
+ "url": { "type": "string", "minLength": 1 },
29
+ "environment": { "type": "string", "minLength": 1 },
30
+ "generatedAt": { "type": "string", "format": "date-time" },
31
+ "durationMs": { "type": "integer", "minimum": 0 },
32
+ "reportVersion": { "type": "string", "minLength": 1 },
33
+ "browserAdapter": {
34
+ "enum": ["playwright-mcp", "cursor-browser", "cdp", "cli-playwright", "cli-other", "unavailable"]
35
+ },
36
+ "producer": {
37
+ "description": "Which agent produced the data. Displayed in the appendix; never branched on.",
38
+ "type": "object",
39
+ "additionalProperties": false,
40
+ "properties": {
41
+ "agent": {
42
+ "type": "string",
43
+ "minLength": 1,
44
+ "description": "claude-code, cursor, opencode, codex, gemini-cli, copilot, chatgpt, antigravity, kimi, or another agent's own name."
45
+ },
46
+ "model": { "type": "string", "minLength": 1 },
47
+ "skill": { "type": "string", "minLength": 1 },
48
+ "version": { "type": "string", "minLength": 1 }
49
+ }
50
+ },
51
+ "authentication": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "properties": {
55
+ "required": { "type": "boolean" },
56
+ "method": {
57
+ "enum": ["none", "form", "sso", "oauth", "saml", "azure-ad", "okta", "auth0", "google", "microsoft", "magic-link", "jwt", "cookie", "session", "basic", "api-key", "unknown"]
58
+ },
59
+ "detectedBy": { "type": "string", "minLength": 1 },
60
+ "sessionSource": { "enum": ["manual-login", "reused-session", "not-required", "failed"] },
61
+ "profile": { "type": "string", "minLength": 1 },
62
+ "note": { "type": "string", "minLength": 1 }
63
+ }
64
+ }
65
+ }
66
+ },
67
+
68
+ "summary": {
69
+ "description": "The decision, and the numbers behind it. Read first and often read alone.",
70
+ "type": "object",
71
+ "additionalProperties": false,
72
+ "required": ["text"],
73
+ "properties": {
74
+ "text": { "type": "string", "minLength": 1 },
75
+ "classification": { "enum": ["pass", "issues-found", "blocked", "insufficient-data"] },
76
+ "verdict": { "enum": ["ship", "ship-with-risks", "do-not-ship", "insufficient-data"] },
77
+ "headline": { "type": "string", "minLength": 1 },
78
+ "health": { "type": "string", "minLength": 1 },
79
+ "risks": { "type": "array", "items": { "type": "string", "minLength": 1 } },
80
+ "recommendedAction": { "type": "string", "minLength": 1 },
81
+ "estimatedFixHours": {
82
+ "type": "object",
83
+ "additionalProperties": false,
84
+ "required": ["low", "high"],
85
+ "properties": {
86
+ "low": { "type": "number", "minimum": 0 },
87
+ "high": { "type": "number", "minimum": 0 }
88
+ }
89
+ },
90
+ "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
91
+ "severityCounts": {
92
+ "type": "object",
93
+ "additionalProperties": false,
94
+ "required": ["critical", "high", "medium", "low"],
95
+ "properties": {
96
+ "critical": { "type": "integer", "minimum": 0 },
97
+ "high": { "type": "integer", "minimum": 0 },
98
+ "medium": { "type": "integer", "minimum": 0 },
99
+ "low": { "type": "integer", "minimum": 0 }
100
+ }
101
+ },
102
+ "scores": {
103
+ "description": "Omit any dimension the run did not measure. A fabricated score is worse than a missing one, because a blank prompts a question and a plausible number ends one.",
104
+ "type": "object",
105
+ "additionalProperties": false,
106
+ "properties": {
107
+ "overall": { "type": "integer", "minimum": 0, "maximum": 100 },
108
+ "functional": { "type": "integer", "minimum": 0, "maximum": 100 },
109
+ "performance": { "type": "integer", "minimum": 0, "maximum": 100 },
110
+ "accessibility": { "type": "integer", "minimum": 0, "maximum": 100 },
111
+ "security": { "type": "integer", "minimum": 0, "maximum": 100 },
112
+ "ux": { "type": "integer", "minimum": 0, "maximum": 100 }
113
+ }
114
+ }
115
+ }
116
+ },
117
+
118
+ "coverage": {
119
+ "description": "What was looked at, and — just as important — what was not.",
120
+ "type": "object",
121
+ "additionalProperties": false,
122
+ "properties": {
123
+ "objective": { "type": "string", "minLength": 1 },
124
+ "covered": { "type": "array", "items": { "type": "string", "minLength": 1 } },
125
+ "notCovered": {
126
+ "type": "array",
127
+ "items": { "type": "string", "minLength": 1 },
128
+ "description": "Each boundary with its reason. A boundary without a reason reads as an omission."
129
+ },
130
+ "dimensionsRun": {
131
+ "type": "array",
132
+ "items": {
133
+ "enum": ["functional", "api", "performance", "security", "ui", "ux", "data", "accessibility", "console"]
134
+ }
135
+ },
136
+ "pages": {
137
+ "type": "array",
138
+ "items": {
139
+ "type": "object",
140
+ "additionalProperties": false,
141
+ "required": ["url", "status"],
142
+ "properties": {
143
+ "url": { "type": "string", "minLength": 1 },
144
+ "title": { "type": "string", "minLength": 1 },
145
+ "status": { "enum": ["ok", "issues", "failed", "blocked", "skipped"] },
146
+ "httpStatus": { "type": "integer", "minimum": 100, "maximum": 599 },
147
+ "findingIds": { "type": "array", "items": { "type": "string", "minLength": 1 } },
148
+ "loadMs": { "type": "integer", "minimum": 0 },
149
+ "requests": { "type": "integer", "minimum": 0 },
150
+ "consoleErrors": { "type": "integer", "minimum": 0 },
151
+ "score": { "type": "integer", "minimum": 0, "maximum": 100 },
152
+ "note": { "type": "string", "minLength": 1 }
153
+ }
154
+ }
155
+ },
156
+ "testCases": {
157
+ "type": "object",
158
+ "additionalProperties": false,
159
+ "required": ["total", "passed", "failed", "blocked", "skipped", "cases"],
160
+ "properties": {
161
+ "total": { "type": "integer", "minimum": 0 },
162
+ "passed": { "type": "integer", "minimum": 0 },
163
+ "failed": { "type": "integer", "minimum": 0 },
164
+ "blocked": { "type": "integer", "minimum": 0 },
165
+ "skipped": { "type": "integer", "minimum": 0 },
166
+ "cases": {
167
+ "type": "array",
168
+ "items": {
169
+ "type": "object",
170
+ "additionalProperties": false,
171
+ "required": ["id", "title", "status"],
172
+ "properties": {
173
+ "id": { "type": "string", "minLength": 1 },
174
+ "title": { "type": "string", "minLength": 1 },
175
+ "status": { "enum": ["pass", "fail", "blocked", "skipped"] },
176
+ "findingId": { "type": "string", "minLength": 1 },
177
+ "source": { "enum": ["user-supplied", "derived", "exploratory"] },
178
+ "page": { "type": "string", "minLength": 1 },
179
+ "note": { "type": "string", "minLength": 1 }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ },
185
+ "timeline": {
186
+ "type": "array",
187
+ "items": {
188
+ "type": "object",
189
+ "additionalProperties": false,
190
+ "required": ["phase", "label"],
191
+ "properties": {
192
+ "phase": {
193
+ "enum": ["launch", "authentication", "navigation", "functional", "api", "performance", "security", "accessibility", "ui", "data", "analysis", "reporting"]
194
+ },
195
+ "label": { "type": "string", "minLength": 1 },
196
+ "startedAt": { "type": "string", "format": "date-time" },
197
+ "durationMs": { "type": "integer", "minimum": 0 },
198
+ "status": { "enum": ["ok", "warned", "failed", "skipped"] },
199
+ "detail": { "type": "string", "minLength": 1 }
200
+ }
201
+ }
202
+ }
203
+ }
204
+ },
205
+
206
+ "issues": {
207
+ "description": "The defects. Called issues here and rendered as findings; same thing.",
208
+ "type": "array",
209
+ "items": {
210
+ "type": "object",
211
+ "additionalProperties": false,
212
+ "required": ["id", "severity", "dimension", "title", "actual", "expected", "fixDirection", "status", "evidence"],
213
+ "properties": {
214
+ "id": { "type": "string", "minLength": 1 },
215
+ "severity": { "enum": ["critical", "high", "medium", "low"] },
216
+ "dimension": {
217
+ "enum": ["functional", "api", "performance", "security", "ui", "ux", "data", "accessibility", "console"]
218
+ },
219
+ "title": { "type": "string", "minLength": 1 },
220
+ "actual": { "type": "string", "minLength": 1 },
221
+ "expected": { "type": "string", "minLength": 1 },
222
+ "fixDirection": { "type": "string", "minLength": 1 },
223
+ "status": {
224
+ "enum": ["confirmed", "validated-user-report", "could-not-reproduce", "partial", "fixed-in-run"]
225
+ },
226
+ "repro": { "type": "string", "minLength": 1 },
227
+ "steps": { "type": "array", "items": { "type": "string", "minLength": 1 } },
228
+ "businessImpact": { "type": "string", "minLength": 1 },
229
+ "developerNotes": { "type": "string", "minLength": 1 },
230
+ "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
231
+ "page": { "type": "string", "minLength": 1 },
232
+ "tags": { "type": "array", "items": { "type": "string", "minLength": 1 } },
233
+ "affectedApis": { "type": "array", "items": { "type": "string", "minLength": 1 } },
234
+ "rootCause": {
235
+ "type": "object",
236
+ "additionalProperties": false,
237
+ "required": ["summary"],
238
+ "properties": {
239
+ "summary": { "type": "string", "minLength": 1 },
240
+ "chain": { "type": "array", "items": { "type": "string", "minLength": 1 } },
241
+ "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
242
+ "layer": {
243
+ "enum": ["frontend", "backend", "network", "data", "infrastructure", "third-party", "unknown"]
244
+ }
245
+ }
246
+ },
247
+ "regressionRisk": {
248
+ "type": "object",
249
+ "additionalProperties": false,
250
+ "required": ["level"],
251
+ "properties": {
252
+ "level": { "enum": ["high", "medium", "low"] },
253
+ "note": { "type": "string", "minLength": 1 },
254
+ "retest": { "type": "array", "items": { "type": "string", "minLength": 1 } }
255
+ }
256
+ },
257
+ "metrics": {
258
+ "type": "array",
259
+ "items": {
260
+ "type": "object",
261
+ "additionalProperties": false,
262
+ "required": ["label", "value"],
263
+ "properties": {
264
+ "label": { "type": "string", "minLength": 1 },
265
+ "value": { "type": "string", "minLength": 1 },
266
+ "budget": { "type": "string", "minLength": 1 },
267
+ "breached": { "type": "boolean" }
268
+ }
269
+ }
270
+ },
271
+ "evidence": {
272
+ "type": "array",
273
+ "minItems": 1,
274
+ "description": "Every entry locates its file, by artifactId when registered or by source when not.",
275
+ "items": {
276
+ "type": "object",
277
+ "additionalProperties": false,
278
+ "required": ["type"],
279
+ "properties": {
280
+ "type": {
281
+ "enum": ["screenshot", "network", "console", "dom", "har", "db", "command", "video", "trace", "performance", "accessibility", "security", "log"]
282
+ },
283
+ "source": { "type": "string", "minLength": 1 },
284
+ "artifactId": { "type": "string", "minLength": 1 },
285
+ "caption": { "type": "string", "minLength": 1 },
286
+ "excerpt": { "type": "string" }
287
+ },
288
+ "allOf": [
289
+ {
290
+ "if": { "required": ["artifactId"] },
291
+ "then": {},
292
+ "else": { "required": ["source"] }
293
+ }
294
+ ]
295
+ }
296
+ }
297
+ }
298
+ }
299
+ },
300
+
301
+ "artifacts": {
302
+ "description": "Every file the run produced, registered once. Paths are relative to this JSON.",
303
+ "type": "array",
304
+ "items": {
305
+ "type": "object",
306
+ "additionalProperties": false,
307
+ "required": ["id", "kind", "path"],
308
+ "properties": {
309
+ "id": { "type": "string", "minLength": 1, "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$" },
310
+ "kind": {
311
+ "enum": ["screenshot", "video", "trace", "har", "dom", "console", "network", "performance", "accessibility", "security", "api", "coverage", "log", "diff", "report", "file"]
312
+ },
313
+ "path": { "type": "string", "minLength": 1 },
314
+ "label": { "type": "string", "minLength": 1 },
315
+ "mimeType": { "type": "string", "minLength": 1 },
316
+ "bytes": { "type": "integer", "minimum": 0 },
317
+ "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
318
+ "capturedAt": { "type": "string", "format": "date-time" },
319
+ "width": { "type": "integer", "minimum": 1 },
320
+ "height": { "type": "integer", "minimum": 1 },
321
+ "thumbnail": { "type": "string", "minLength": 1 },
322
+ "compares": { "type": "string", "minLength": 1 },
323
+ "note": { "type": "string", "minLength": 1 }
324
+ }
325
+ }
326
+ },
327
+
328
+ "evidence": {
329
+ "description": "The run-level evidence index.",
330
+ "type": "array",
331
+ "items": {
332
+ "type": "object",
333
+ "additionalProperties": false,
334
+ "required": ["type", "description", "source"],
335
+ "properties": {
336
+ "type": {
337
+ "enum": ["screenshot", "network", "console", "dom", "har", "db", "file", "report", "command", "video", "trace", "performance", "accessibility", "security", "log"]
338
+ },
339
+ "description": { "type": "string", "minLength": 1 },
340
+ "source": { "type": "string", "minLength": 1 },
341
+ "artifactId": { "type": "string", "minLength": 1 },
342
+ "excerpt": { "type": "string" }
343
+ }
344
+ }
345
+ },
346
+
347
+ "performance": {
348
+ "type": "object",
349
+ "additionalProperties": false,
350
+ "properties": {
351
+ "lcpMs": { "type": "number", "minimum": 0 },
352
+ "fcpMs": { "type": "number", "minimum": 0 },
353
+ "ttfbMs": { "type": "number", "minimum": 0 },
354
+ "inpMs": { "type": "number", "minimum": 0 },
355
+ "cls": { "type": "number", "minimum": 0 },
356
+ "longTasks": { "type": "integer", "minimum": 0 },
357
+ "longTaskMs": { "type": "number", "minimum": 0 },
358
+ "domNodes": { "type": "integer", "minimum": 0 },
359
+ "jsBytes": { "type": "integer", "minimum": 0 },
360
+ "cssBytes": { "type": "integer", "minimum": 0 },
361
+ "imageBytes": { "type": "integer", "minimum": 0 },
362
+ "totalBytes": { "type": "integer", "minimum": 0 },
363
+ "jsHeapBytes": { "type": "integer", "minimum": 0 },
364
+ "note": { "type": "string", "minLength": 1 }
365
+ }
366
+ },
367
+
368
+ "network": {
369
+ "type": "object",
370
+ "additionalProperties": false,
371
+ "properties": {
372
+ "totalRequests": { "type": "integer", "minimum": 0 },
373
+ "failedRequests": { "type": "integer", "minimum": 0 },
374
+ "slowRequests": { "type": "integer", "minimum": 0 },
375
+ "duplicateRequests": { "type": "integer", "minimum": 0 },
376
+ "totalBytes": { "type": "integer", "minimum": 0 },
377
+ "slowThresholdMs": { "type": "integer", "minimum": 0 },
378
+ "endpoints": {
379
+ "type": "array",
380
+ "items": {
381
+ "type": "object",
382
+ "additionalProperties": false,
383
+ "required": ["method", "url"],
384
+ "properties": {
385
+ "method": { "type": "string", "minLength": 1 },
386
+ "url": { "type": "string", "minLength": 1 },
387
+ "status": { "type": "integer", "minimum": 0, "maximum": 599 },
388
+ "durationMs": { "type": "integer", "minimum": 0 },
389
+ "bytes": { "type": "integer", "minimum": 0 },
390
+ "count": { "type": "integer", "minimum": 1 },
391
+ "startedMs": { "type": "integer", "minimum": 0 },
392
+ "issue": {
393
+ "enum": ["failed", "slow", "duplicate", "large-payload", "uncached", "polling", "n-plus-one", "race", "retry"]
394
+ },
395
+ "findingId": { "type": "string", "minLength": 1 }
396
+ }
397
+ }
398
+ }
399
+ }
400
+ },
401
+
402
+ "security": {
403
+ "type": "object",
404
+ "additionalProperties": false,
405
+ "properties": {
406
+ "checked": { "type": "boolean" },
407
+ "summary": { "type": "string", "minLength": 1 },
408
+ "checks": {
409
+ "type": "array",
410
+ "items": {
411
+ "type": "object",
412
+ "additionalProperties": false,
413
+ "required": ["check", "status"],
414
+ "properties": {
415
+ "check": { "type": "string", "minLength": 1 },
416
+ "status": { "enum": ["pass", "fail", "warn", "not-checked"] },
417
+ "detail": { "type": "string", "minLength": 1 },
418
+ "findingId": { "type": "string", "minLength": 1 }
419
+ }
420
+ }
421
+ }
422
+ }
423
+ },
424
+
425
+ "accessibility": {
426
+ "type": "object",
427
+ "additionalProperties": false,
428
+ "properties": {
429
+ "checked": { "type": "boolean" },
430
+ "summary": { "type": "string", "minLength": 1 },
431
+ "violations": {
432
+ "type": "array",
433
+ "items": {
434
+ "type": "object",
435
+ "additionalProperties": false,
436
+ "required": ["rule", "impact", "count"],
437
+ "properties": {
438
+ "rule": { "type": "string", "minLength": 1 },
439
+ "impact": { "enum": ["critical", "serious", "moderate", "minor"] },
440
+ "count": { "type": "integer", "minimum": 1 },
441
+ "description": { "type": "string", "minLength": 1 },
442
+ "selector": { "type": "string", "minLength": 1 },
443
+ "findingId": { "type": "string", "minLength": 1 }
444
+ }
445
+ }
446
+ }
447
+ }
448
+ },
449
+
450
+ "console": {
451
+ "type": "object",
452
+ "additionalProperties": false,
453
+ "properties": {
454
+ "errors": { "type": "integer", "minimum": 0 },
455
+ "warnings": { "type": "integer", "minimum": 0 },
456
+ "unhandledRejections": { "type": "integer", "minimum": 0 },
457
+ "deprecations": { "type": "integer", "minimum": 0 },
458
+ "entries": {
459
+ "type": "array",
460
+ "items": {
461
+ "type": "object",
462
+ "additionalProperties": false,
463
+ "required": ["level", "message"],
464
+ "properties": {
465
+ "level": { "enum": ["error", "warning", "info", "debug"] },
466
+ "message": { "type": "string", "minLength": 1 },
467
+ "count": { "type": "integer", "minimum": 1 },
468
+ "source": { "type": "string", "minLength": 1 },
469
+ "findingId": { "type": "string", "minLength": 1 }
470
+ }
471
+ }
472
+ }
473
+ }
474
+ },
475
+
476
+ "dataValidation": {
477
+ "type": "object",
478
+ "additionalProperties": false,
479
+ "properties": {
480
+ "inScope": { "type": "boolean" },
481
+ "summary": { "type": "string" },
482
+ "comparisons": {
483
+ "type": "array",
484
+ "items": {
485
+ "type": "object",
486
+ "additionalProperties": false,
487
+ "required": ["metric", "uiValue", "sourceValue", "match"],
488
+ "properties": {
489
+ "metric": { "type": "string", "minLength": 1 },
490
+ "uiValue": { "type": "string" },
491
+ "sourceValue": { "type": "string" },
492
+ "match": { "type": "boolean" },
493
+ "capturedAt": { "type": "string" }
494
+ }
495
+ }
496
+ }
497
+ }
498
+ },
499
+
500
+ "recommendations": {
501
+ "type": "object",
502
+ "additionalProperties": false,
503
+ "properties": {
504
+ "fixOrder": { "type": "array", "items": { "type": "string", "minLength": 1 } },
505
+ "whatWorksWell": { "type": "array", "items": { "type": "string", "minLength": 1 } },
506
+ "actions": {
507
+ "type": "array",
508
+ "items": {
509
+ "type": "object",
510
+ "additionalProperties": false,
511
+ "required": ["action", "priority"],
512
+ "properties": {
513
+ "action": { "type": "string", "minLength": 1 },
514
+ "priority": { "enum": ["high", "medium", "low"] },
515
+ "rationale": { "type": "string", "minLength": 1 },
516
+ "owner": { "enum": ["frontend", "backend", "platform", "design", "qa", "product", "security"] },
517
+ "effort": { "enum": ["small", "medium", "large"] },
518
+ "findingIds": { "type": "array", "items": { "type": "string", "minLength": 1 } }
519
+ }
520
+ }
521
+ }
522
+ }
523
+ }
524
+ },
525
+ "allOf": [
526
+ {
527
+ "description": "A `ship` verdict cannot stand over a critical or high issue. The severity legend the report prints says critical blocks release and high must be fixed before it, so a run that counts one and then recommends shipping contradicts itself — invisibly to a reader who only reads the banner. `ship-with-risks` is the honest verdict for knowingly releasing over a high; there is no honest verdict for releasing over a critical.",
528
+ "if": {
529
+ "type": "object",
530
+ "required": ["summary"],
531
+ "properties": {
532
+ "summary": {
533
+ "type": "object",
534
+ "required": ["verdict"],
535
+ "properties": { "verdict": { "const": "ship" } }
536
+ }
537
+ }
538
+ },
539
+ "then": {
540
+ "properties": {
541
+ "summary": {
542
+ "properties": {
543
+ "severityCounts": {
544
+ "properties": {
545
+ "critical": { "maximum": 0 },
546
+ "high": { "maximum": 0 }
547
+ }
548
+ }
549
+ }
550
+ }
551
+ }
552
+ }
553
+ }
554
+ ]
555
+ }