ai-ccesibility 0.1.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/EXAMPLES.md ADDED
@@ -0,0 +1,595 @@
1
+ # Ejemplos de Uso - AI-ccesibility
2
+
3
+ Ejemplos concretos de inputs y outputs para cada herramienta MCP.
4
+
5
+ ## Tabla de Contenidos
6
+
7
+ - [analyze-with-axe](#analyze-with-axe)
8
+ - [analyze-with-pa11y](#analyze-with-pa11y)
9
+ - [analyze-with-eslint](#analyze-with-eslint)
10
+ - [analyze-all](#analyze-all)
11
+
12
+ ---
13
+
14
+ ## analyze-with-axe
15
+
16
+ ### Ejemplo 1: Análisis básico de URL
17
+
18
+ **Input:**
19
+ ```json
20
+ {
21
+ "url": "https://example.com"
22
+ }
23
+ ```
24
+
25
+ **Output:**
26
+ ```json
27
+ {
28
+ "success": true,
29
+ "target": "https://example.com",
30
+ "issueCount": 5,
31
+ "issues": [
32
+ {
33
+ "id": "axe-core:image-alt:a3f8b9",
34
+ "ruleId": "image-alt",
35
+ "tool": "axe-core",
36
+ "severity": "critical",
37
+ "wcag": {
38
+ "criterion": "1.1.1",
39
+ "level": "A",
40
+ "principle": "perceivable"
41
+ },
42
+ "location": {
43
+ "selector": "img:nth-child(2)",
44
+ "snippet": "<img src=\"logo.png\">"
45
+ },
46
+ "message": "Images must have alternate text",
47
+ "humanContext": "**Contenido no textual (WCAG 1.1.1 - Nivel A)**\n\nTodo contenido no textual debe tener una alternativa de texto...",
48
+ "suggestedActions": [
49
+ "Añadir atributo alt descriptivo a imágenes",
50
+ "Usar aria-label para iconos decorativos con función",
51
+ "Marcar imágenes decorativas con alt=\"\" vacío"
52
+ ],
53
+ "affectedUsers": ["screen-reader", "low-vision"],
54
+ "priority": "critical",
55
+ "remediationEffort": "low",
56
+ "confidence": 1
57
+ }
58
+ ],
59
+ "summary": {
60
+ "total": 5,
61
+ "bySeverity": {
62
+ "critical": 1,
63
+ "serious": 2,
64
+ "moderate": 2,
65
+ "minor": 0
66
+ },
67
+ "byPrinciple": {
68
+ "perceivable": 3,
69
+ "operable": 2,
70
+ "understandable": 0,
71
+ "robust": 0
72
+ }
73
+ },
74
+ "duration": 2340
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ### Ejemplo 2: Análisis de HTML raw
81
+
82
+ **Input:**
83
+ ```json
84
+ {
85
+ "html": "<html><body><form><input type=\"text\" placeholder=\"Email\"><button>Submit</button></form></body></html>",
86
+ "options": {
87
+ "wcagLevel": "AA"
88
+ }
89
+ }
90
+ ```
91
+
92
+ **Output:**
93
+ ```json
94
+ {
95
+ "success": true,
96
+ "target": "[html content]",
97
+ "issueCount": 2,
98
+ "issues": [
99
+ {
100
+ "id": "axe-core:label:f3a9b2",
101
+ "ruleId": "label",
102
+ "tool": "axe-core",
103
+ "severity": "serious",
104
+ "wcag": {
105
+ "criterion": "1.3.1",
106
+ "level": "A",
107
+ "principle": "perceivable"
108
+ },
109
+ "location": {
110
+ "selector": "input[type=\"text\"]",
111
+ "snippet": "<input type=\"text\" placeholder=\"Email\">"
112
+ },
113
+ "message": "Form elements must have labels",
114
+ "humanContext": "**Información y relaciones (WCAG 1.3.1 - Nivel A)**...",
115
+ "suggestedActions": [
116
+ "Asociar labels con inputs correctamente",
117
+ "Usar aria-label si label visible no es posible"
118
+ ],
119
+ "affectedUsers": ["screen-reader", "cognitive"],
120
+ "priority": "high",
121
+ "remediationEffort": "low"
122
+ }
123
+ ],
124
+ "duration": 180
125
+ }
126
+ ```
127
+
128
+ ---
129
+
130
+ ### Ejemplo 3: Con opciones avanzadas
131
+
132
+ **Input:**
133
+ ```json
134
+ {
135
+ "url": "https://spa-app.com",
136
+ "options": {
137
+ "wcagLevel": "AA",
138
+ "includeIncomplete": false,
139
+ "browser": {
140
+ "waitForSelector": "#app-loaded",
141
+ "waitForTimeout": 3000,
142
+ "viewport": {
143
+ "width": 1280,
144
+ "height": 720
145
+ }
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ ---
152
+
153
+ ## analyze-with-pa11y
154
+
155
+ ### Ejemplo 1: Análisis con standard específico
156
+
157
+ **Input:**
158
+ ```json
159
+ {
160
+ "url": "https://example.com",
161
+ "options": {
162
+ "standard": "WCAG21AA",
163
+ "includeWarnings": true,
164
+ "includeNotices": false
165
+ }
166
+ }
167
+ ```
168
+
169
+ **Output:**
170
+ ```json
171
+ {
172
+ "success": true,
173
+ "target": "https://example.com",
174
+ "issueCount": 3,
175
+ "issues": [
176
+ {
177
+ "id": "pa11y:WCAG2AA.Principle1.Guideline1_1.1_1_1.H37:8f3a2b",
178
+ "ruleId": "WCAG2AA.Principle1.Guideline1_1.1_1_1.H37",
179
+ "tool": "pa11y",
180
+ "severity": "serious",
181
+ "wcag": {
182
+ "criterion": "1.1.1",
183
+ "level": "AA",
184
+ "principle": "perceivable",
185
+ "version": "2.1"
186
+ },
187
+ "location": {
188
+ "selector": "html > body > img:nth-child(3)",
189
+ "snippet": "<img src=\"banner.jpg\">"
190
+ },
191
+ "message": "Img element missing an alt attribute. Use the alt attribute to specify a short text alternative.",
192
+ "humanContext": "**Contenido no textual (WCAG 1.1.1 - Nivel A)**...",
193
+ "affectedUsers": ["screen-reader", "low-vision"],
194
+ "priority": "critical",
195
+ "remediationEffort": "low",
196
+ "confidence": 1
197
+ }
198
+ ],
199
+ "summary": {
200
+ "total": 3,
201
+ "bySeverity": {
202
+ "critical": 0,
203
+ "serious": 2,
204
+ "moderate": 1,
205
+ "minor": 0
206
+ }
207
+ },
208
+ "metadata": {
209
+ "toolVersion": "9.0.1",
210
+ "pageTitle": "Example Domain"
211
+ },
212
+ "duration": 1890
213
+ }
214
+ ```
215
+
216
+ ---
217
+
218
+ ## analyze-with-eslint
219
+
220
+ ### Ejemplo 1: Análisis de archivo Vue
221
+
222
+ **Input:**
223
+ ```json
224
+ {
225
+ "files": ["src/components/LoginForm.vue"]
226
+ }
227
+ ```
228
+
229
+ **Output:**
230
+ ```json
231
+ {
232
+ "success": true,
233
+ "target": "src/components/LoginForm.vue",
234
+ "issueCount": 4,
235
+ "issues": [
236
+ {
237
+ "id": "eslint-vuejs-a11y:vuejs-accessibility/click-events-have-key-events:src/components/LoginForm.vue:42:15:f8a3c2",
238
+ "ruleId": "vuejs-accessibility/click-events-have-key-events",
239
+ "tool": "eslint-vuejs-a11y",
240
+ "severity": "serious",
241
+ "wcag": {
242
+ "criterion": "2.1.1",
243
+ "level": "A",
244
+ "principle": "operable",
245
+ "version": "2.1"
246
+ },
247
+ "location": {
248
+ "file": "src/components/LoginForm.vue",
249
+ "line": 42,
250
+ "column": 15,
251
+ "snippet": "<div @click=\"togglePassword\">"
252
+ },
253
+ "message": "Elements with click handlers must have corresponding key event handlers.",
254
+ "humanContext": "**Teclado (WCAG 2.1.1 - Nivel A)**\n\nToda funcionalidad debe ser operable mediante teclado...",
255
+ "suggestedActions": [
256
+ "Añadir manejadores de eventos de teclado (onKeyDown, onKeyPress)",
257
+ "Usar elementos interactivos nativos (button, a, input)"
258
+ ],
259
+ "affectedUsers": ["keyboard-only", "motor-impaired", "screen-reader"],
260
+ "priority": "critical",
261
+ "remediationEffort": "medium",
262
+ "confidence": 1
263
+ },
264
+ {
265
+ "id": "eslint-vuejs-a11y:vuejs-accessibility/form-control-has-label:src/components/LoginForm.vue:28:10:b9f2a1",
266
+ "ruleId": "vuejs-accessibility/form-control-has-label",
267
+ "tool": "eslint-vuejs-a11y",
268
+ "severity": "serious",
269
+ "wcag": {
270
+ "criterion": "1.3.1",
271
+ "level": "A",
272
+ "principle": "perceivable",
273
+ "version": "2.1"
274
+ },
275
+ "location": {
276
+ "file": "src/components/LoginForm.vue",
277
+ "line": 28,
278
+ "column": 10
279
+ },
280
+ "message": "A form control must have a label.",
281
+ "humanContext": "**Información y relaciones (WCAG 1.3.1 - Nivel A)**...",
282
+ "affectedUsers": ["screen-reader", "cognitive"],
283
+ "priority": "high",
284
+ "remediationEffort": "low"
285
+ }
286
+ ],
287
+ "summary": {
288
+ "total": 4,
289
+ "bySeverity": {
290
+ "critical": 0,
291
+ "serious": 4,
292
+ "moderate": 0,
293
+ "minor": 0
294
+ },
295
+ "byRule": {
296
+ "vuejs-accessibility/click-events-have-key-events": 2,
297
+ "vuejs-accessibility/form-control-has-label": 2
298
+ }
299
+ },
300
+ "duration": 450
301
+ }
302
+ ```
303
+
304
+ ---
305
+
306
+ ### Ejemplo 2: Análisis de código inline
307
+
308
+ **Input:**
309
+ ```json
310
+ {
311
+ "code": "<template>\n <div>\n <img src=\"avatar.jpg\" />\n <div @click=\"handleClick\">Click me</div>\n </div>\n</template>\n\n<script>\nexport default {\n methods: {\n handleClick() {\n console.log('clicked');\n }\n }\n};\n</script>"
312
+ }
313
+ ```
314
+
315
+ **Output:**
316
+ ```json
317
+ {
318
+ "success": true,
319
+ "target": "inline.vue",
320
+ "issueCount": 2,
321
+ "issues": [
322
+ {
323
+ "ruleId": "vuejs-accessibility/alt-text",
324
+ "message": "img elements must have an alt prop...",
325
+ "location": {
326
+ "file": "inline.vue",
327
+ "line": 3,
328
+ "column": 5
329
+ }
330
+ },
331
+ {
332
+ "ruleId": "vuejs-accessibility/no-static-element-interactions",
333
+ "message": "Static HTML elements with event handlers require a role...",
334
+ "location": {
335
+ "file": "inline.vue",
336
+ "line": 4,
337
+ "column": 5
338
+ }
339
+ }
340
+ ]
341
+ }
342
+ ```
343
+
344
+ ---
345
+
346
+ ## analyze-all
347
+
348
+ ### Ejemplo 1: Análisis combinado básico
349
+
350
+ **Input:**
351
+ ```json
352
+ {
353
+ "url": "https://example.com",
354
+ "tools": ["axe-core", "pa11y"],
355
+ "options": {
356
+ "wcagLevel": "AA",
357
+ "deduplicateResults": true
358
+ }
359
+ }
360
+ ```
361
+
362
+ **Output:**
363
+ ```json
364
+ {
365
+ "success": true,
366
+ "target": "https://example.com",
367
+ "toolsUsed": ["axe-core", "pa11y"],
368
+ "issueCount": 8,
369
+ "deduplicatedCount": 12,
370
+ "issues": [
371
+ {
372
+ "id": "axe-core:image-alt:a3f8b9",
373
+ "ruleId": "image-alt",
374
+ "tool": "axe-core",
375
+ "severity": "critical",
376
+ "message": "Images must have alternate text",
377
+ "humanContext": "**Contenido no textual (WCAG 1.1.1 - Nivel A)**...",
378
+ "affectedUsers": ["screen-reader", "low-vision"],
379
+ "priority": "critical",
380
+ "remediationEffort": "low"
381
+ }
382
+ ],
383
+ "issuesByWCAG": {
384
+ "1.1.1": [
385
+ {
386
+ "tool": "axe-core",
387
+ "ruleId": "image-alt"
388
+ },
389
+ {
390
+ "tool": "axe-core",
391
+ "ruleId": "input-image-alt"
392
+ }
393
+ ],
394
+ "1.3.1": [
395
+ {
396
+ "tool": "pa11y",
397
+ "ruleId": "WCAG2AA.Principle1.Guideline1_3..."
398
+ }
399
+ ],
400
+ "2.1.1": [
401
+ {
402
+ "tool": "axe-core",
403
+ "ruleId": "button-name"
404
+ }
405
+ ]
406
+ },
407
+ "summary": {
408
+ "total": 8,
409
+ "bySeverity": {
410
+ "critical": 2,
411
+ "serious": 4,
412
+ "moderate": 2,
413
+ "minor": 0
414
+ },
415
+ "byPrinciple": {
416
+ "perceivable": 5,
417
+ "operable": 3,
418
+ "understandable": 0,
419
+ "robust": 0
420
+ },
421
+ "byTool": {
422
+ "axe-core": 5,
423
+ "pa11y": 3
424
+ }
425
+ },
426
+ "individualResults": [
427
+ {
428
+ "tool": "axe-core",
429
+ "success": true,
430
+ "issues": [],
431
+ "duration": 2340
432
+ },
433
+ {
434
+ "tool": "pa11y",
435
+ "success": true,
436
+ "issues": [],
437
+ "duration": 1890
438
+ }
439
+ ],
440
+ "duration": 2500
441
+ }
442
+ ```
443
+
444
+ ---
445
+
446
+ ### Ejemplo 2: Sin deduplicación
447
+
448
+ **Input:**
449
+ ```json
450
+ {
451
+ "url": "https://example.com",
452
+ "tools": ["axe-core", "pa11y"],
453
+ "options": {
454
+ "deduplicateResults": false
455
+ }
456
+ }
457
+ ```
458
+
459
+ **Output:**
460
+ ```json
461
+ {
462
+ "issueCount": 12,
463
+ "deduplicatedCount": 12,
464
+ "issues": [
465
+ {
466
+ "tool": "axe-core",
467
+ "ruleId": "image-alt",
468
+ "location": { "selector": "img:nth-child(2)" }
469
+ },
470
+ {
471
+ "tool": "pa11y",
472
+ "ruleId": "WCAG2AA.Principle1.Guideline1_1.1_1_1.H37",
473
+ "location": { "selector": "html > body > img:nth-child(2)" }
474
+ }
475
+ ]
476
+ }
477
+ ```
478
+
479
+ **Nota:** Ambos detectaron la misma imagen sin `alt`, pero con diferentes selectores.
480
+
481
+ ---
482
+
483
+ ### Ejemplo 3: Con viewport móvil
484
+
485
+ **Input:**
486
+ ```json
487
+ {
488
+ "url": "https://responsive-site.com",
489
+ "tools": ["axe-core", "pa11y"],
490
+ "options": {
491
+ "wcagLevel": "AA",
492
+ "browser": {
493
+ "viewport": {
494
+ "width": 375,
495
+ "height": 667
496
+ }
497
+ }
498
+ }
499
+ }
500
+ ```
501
+
502
+ ---
503
+
504
+ ## Comparación de Outputs por Herramienta
505
+
506
+ ### Mismo Issue detectado por diferentes tools
507
+
508
+ **Axe-core:**
509
+ ```json
510
+ {
511
+ "tool": "axe-core",
512
+ "ruleId": "image-alt",
513
+ "severity": "critical",
514
+ "location": {
515
+ "selector": "img:nth-child(2)"
516
+ },
517
+ "message": "Images must have alternate text"
518
+ }
519
+ ```
520
+
521
+ **Pa11y:**
522
+ ```json
523
+ {
524
+ "tool": "pa11y",
525
+ "ruleId": "WCAG2AA.Principle1.Guideline1_1.1_1_1.H37",
526
+ "severity": "serious",
527
+ "location": {
528
+ "selector": "html > body > img:nth-child(2)"
529
+ },
530
+ "message": "Img element missing an alt attribute..."
531
+ }
532
+ ```
533
+
534
+ **ESLint (en código Vue):**
535
+ ```json
536
+ {
537
+ "tool": "eslint-vuejs-a11y",
538
+ "ruleId": "vuejs-accessibility/alt-text",
539
+ "severity": "serious",
540
+ "location": {
541
+ "file": "src/components/Avatar.vue",
542
+ "line": 12,
543
+ "column": 8
544
+ },
545
+ "message": "img elements must have an alt prop..."
546
+ }
547
+ ```
548
+
549
+ ---
550
+
551
+ ## Resumen de Diferencias
552
+
553
+ | Característica | axe-core | Pa11y | ESLint |
554
+ |----------------|----------|-------|--------|
555
+ | **Target** | URL/HTML | URL/HTML | Archivos .vue |
556
+ | **Selector** | CSS compacto | CSS completo | Línea/columna |
557
+ | **Severidades** | 4 niveles | 3 tipos | 2 niveles (warn/error) |
558
+ | **Snippet** | ✅ | ✅ | ✅ |
559
+ | **Confidence** | ✅ | ✅ | Siempre 1 |
560
+ | **Browser** | Puppeteer | Puppeteer | - |
561
+ | **Velocidad** | ~2-3s | ~2s | <1s |
562
+ | **Falsos positivos** | Pocos | Moderados | Muy pocos |
563
+
564
+ ---
565
+
566
+ ## Tips para Interpretar Resultados
567
+
568
+ ### 1. Priorizar por Matriz
569
+
570
+ ```
571
+ Critical + Low effort = Fix HOY
572
+ Critical + Medium/High effort = Planificar sprint
573
+ High + Low effort = Quick wins
574
+ Medium/Low + High effort = Backlog
575
+ ```
576
+
577
+ ### 2. Validar Duplicados
578
+
579
+ Si `deduplicatedCount` > `issueCount`, revisa `individualResults` para ver qué herramienta es más confiable para ese tipo de issue.
580
+
581
+ ### 3. Revisar `affectedUsers`
582
+
583
+ Prioriza issues que afecten a usuarios de screen readers y keyboard-only (más comunes).
584
+
585
+ ### 4. Aprovechar `humanContext`
586
+
587
+ Lee los ejemplos del mundo real para entender el impacto real en usuarios.
588
+
589
+ ---
590
+
591
+ ## Next Steps
592
+
593
+ - Ver [USAGE.md](./USAGE.md) para workflows completos
594
+ - Ver [README.md](./README.md) para configuración
595
+ - Ver `src/data/README.md` para añadir más criterios WCAG
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Diego Razquin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.