@mcptoolshop/accessibility-suite 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.
Files changed (241) hide show
  1. package/.github/workflows/ci.yml +63 -0
  2. package/LICENSE +21 -0
  3. package/README.md +37 -0
  4. package/docs/prov-spec/.github/workflows/ci.yml +68 -0
  5. package/docs/prov-spec/CHANGELOG.md +69 -0
  6. package/docs/prov-spec/CODE_OF_CONDUCT.md +129 -0
  7. package/docs/prov-spec/CONFORMANCE_LEVELS.md +223 -0
  8. package/docs/prov-spec/CONTRIBUTING.md +145 -0
  9. package/docs/prov-spec/IMPLEMENTER_CHECKLIST.md +137 -0
  10. package/docs/prov-spec/LICENSE +21 -0
  11. package/docs/prov-spec/PRESS_RELEASE.md +74 -0
  12. package/docs/prov-spec/README.md +182 -0
  13. package/docs/prov-spec/SETUP.md +135 -0
  14. package/docs/prov-spec/WHY.md +86 -0
  15. package/docs/prov-spec/examples/artifact.example.json +14 -0
  16. package/docs/prov-spec/examples/artifact.ref.example.json +9 -0
  17. package/docs/prov-spec/examples/evidence.example.json +6 -0
  18. package/docs/prov-spec/examples/mcp.envelope.example.json +97 -0
  19. package/docs/prov-spec/examples/mcp.request.example.json +28 -0
  20. package/docs/prov-spec/examples/prov.record.example.json +35 -0
  21. package/docs/prov-spec/interop/PROOF_NODE_ENGINE.md +114 -0
  22. package/docs/prov-spec/spec/MCP_COMPATIBILITY.md +241 -0
  23. package/docs/prov-spec/spec/PROV_METHODS_CATALOG.md +142 -0
  24. package/docs/prov-spec/spec/PROV_METHODS_SPEC.md +397 -0
  25. package/docs/prov-spec/spec/methods.json +213 -0
  26. package/docs/prov-spec/spec/schemas/artifact.ref.schema.v0.1.json +58 -0
  27. package/docs/prov-spec/spec/schemas/artifact.schema.v0.1.json +61 -0
  28. package/docs/prov-spec/spec/schemas/assist.request.schema.v0.1.json +52 -0
  29. package/docs/prov-spec/spec/schemas/assist.response.schema.v0.1.json +70 -0
  30. package/docs/prov-spec/spec/schemas/cli.error.schema.v0.1.json +78 -0
  31. package/docs/prov-spec/spec/schemas/evidence.schema.v0.1.json +37 -0
  32. package/docs/prov-spec/spec/schemas/mcp.envelope.schema.v0.1.json +141 -0
  33. package/docs/prov-spec/spec/schemas/mcp.request.schema.v0.1.json +79 -0
  34. package/docs/prov-spec/spec/schemas/methods.schema.json +93 -0
  35. package/docs/prov-spec/spec/schemas/prov-capabilities.schema.json +122 -0
  36. package/docs/prov-spec/spec/schemas/prov.record.schema.v0.1.json +133 -0
  37. package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/expected.json +4 -0
  38. package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/input.json +1 -0
  39. package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/negative/double_wrapped.json +14 -0
  40. package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/negative/wrong_schema_version.json +11 -0
  41. package/docs/prov-spec/spec/vectors/engine.extract.evidence.json_pointer/expected.json +24 -0
  42. package/docs/prov-spec/spec/vectors/engine.extract.evidence.json_pointer/input.json +8 -0
  43. package/docs/prov-spec/spec/vectors/integrity.digest.sha256/expected.json +7 -0
  44. package/docs/prov-spec/spec/vectors/integrity.digest.sha256/input.json +1 -0
  45. package/docs/prov-spec/spec/vectors/integrity.digest.sha256/negative/non_hex_chars.json +16 -0
  46. package/docs/prov-spec/spec/vectors/integrity.digest.sha256/negative/uppercase_hex.json +16 -0
  47. package/docs/prov-spec/spec/vectors/integrity.digest.sha256/negative/wrong_length.json +16 -0
  48. package/docs/prov-spec/spec/vectors/method_id_syntax/negative/hyphen_separator.json +8 -0
  49. package/docs/prov-spec/spec/vectors/method_id_syntax/negative/reserved_namespace.json +8 -0
  50. package/docs/prov-spec/spec/vectors/method_id_syntax/negative/starts_with_digit.json +8 -0
  51. package/docs/prov-spec/spec/vectors/method_id_syntax/negative/uppercase.json +8 -0
  52. package/docs/prov-spec/spec/vectors/method_id_syntax/positive/valid_ids.json +18 -0
  53. package/docs/prov-spec/tools/python/prov_validator.py +428 -0
  54. package/examples/a11y-demo-site/.github/workflows/a11y-artifacts.yml +81 -0
  55. package/examples/a11y-demo-site/.github/workflows/a11y.yml +34 -0
  56. package/examples/a11y-demo-site/CODE_OF_CONDUCT.md +129 -0
  57. package/examples/a11y-demo-site/CONTRIBUTING.md +83 -0
  58. package/examples/a11y-demo-site/LICENSE +21 -0
  59. package/examples/a11y-demo-site/README.md +155 -0
  60. package/examples/a11y-demo-site/html/contact.html +15 -0
  61. package/examples/a11y-demo-site/html/index.html +20 -0
  62. package/examples/a11y-demo-site/scripts/a11y.sh +20 -0
  63. package/package.json +26 -0
  64. package/src/a11y-assist/.github/workflows/publish.yml +52 -0
  65. package/src/a11y-assist/.github/workflows/test.yml +30 -0
  66. package/src/a11y-assist/A11Y_ASSIST_TEST_COVERAGE_REQUIREMENTS.md +104 -0
  67. package/src/a11y-assist/CODE_OF_CONDUCT.md +129 -0
  68. package/src/a11y-assist/CONTRIBUTING.md +98 -0
  69. package/src/a11y-assist/ENGINE.md +363 -0
  70. package/src/a11y-assist/LICENSE +21 -0
  71. package/src/a11y-assist/PRESS_RELEASE.md +71 -0
  72. package/src/a11y-assist/QUICKSTART.md +101 -0
  73. package/src/a11y-assist/README.md +192 -0
  74. package/src/a11y-assist/RELEASE_NOTES.md +319 -0
  75. package/src/a11y-assist/a11y_assist/__init__.py +3 -0
  76. package/src/a11y-assist/a11y_assist/cli.py +599 -0
  77. package/src/a11y-assist/a11y_assist/from_cli_error.py +149 -0
  78. package/src/a11y-assist/a11y_assist/guard.py +444 -0
  79. package/src/a11y-assist/a11y_assist/ingest.py +407 -0
  80. package/src/a11y-assist/a11y_assist/methods.py +137 -0
  81. package/src/a11y-assist/a11y_assist/parse_raw.py +71 -0
  82. package/src/a11y-assist/a11y_assist/profiles/__init__.py +29 -0
  83. package/src/a11y-assist/a11y_assist/profiles/cognitive_load.py +245 -0
  84. package/src/a11y-assist/a11y_assist/profiles/cognitive_load_render.py +86 -0
  85. package/src/a11y-assist/a11y_assist/profiles/dyslexia.py +144 -0
  86. package/src/a11y-assist/a11y_assist/profiles/dyslexia_render.py +77 -0
  87. package/src/a11y-assist/a11y_assist/profiles/plain_language.py +119 -0
  88. package/src/a11y-assist/a11y_assist/profiles/plain_language_render.py +66 -0
  89. package/src/a11y-assist/a11y_assist/profiles/screen_reader.py +348 -0
  90. package/src/a11y-assist/a11y_assist/profiles/screen_reader_render.py +89 -0
  91. package/src/a11y-assist/a11y_assist/render.py +95 -0
  92. package/src/a11y-assist/a11y_assist/schemas/assist.request.schema.v0.1.json +52 -0
  93. package/src/a11y-assist/a11y_assist/schemas/assist.response.schema.v0.1.json +70 -0
  94. package/src/a11y-assist/a11y_assist/schemas/cli.error.schema.v0.1.json +78 -0
  95. package/src/a11y-assist/a11y_assist/storage.py +31 -0
  96. package/src/a11y-assist/pyproject.toml +60 -0
  97. package/src/a11y-assist/tests/__init__.py +1 -0
  98. package/src/a11y-assist/tests/fixtures/base_inputs/cli_error_high.json +18 -0
  99. package/src/a11y-assist/tests/fixtures/base_inputs/cli_error_medium.json +16 -0
  100. package/src/a11y-assist/tests/fixtures/base_inputs/raw_text_low.txt +3 -0
  101. package/src/a11y-assist/tests/fixtures/cli_error_good.json +9 -0
  102. package/src/a11y-assist/tests/fixtures/cli_error_missing_id.json +7 -0
  103. package/src/a11y-assist/tests/fixtures/cli_error_string_format.json +7 -0
  104. package/src/a11y-assist/tests/fixtures/expected/cognitive_load_high.txt +20 -0
  105. package/src/a11y-assist/tests/fixtures/expected/dyslexia_high.txt +20 -0
  106. package/src/a11y-assist/tests/fixtures/expected/lowvision_high.txt +18 -0
  107. package/src/a11y-assist/tests/fixtures/expected/plain_language_high.txt +14 -0
  108. package/src/a11y-assist/tests/fixtures/expected/screen_reader_high.txt +19 -0
  109. package/src/a11y-assist/tests/fixtures/golden_screen_reader_cli_error.txt +16 -0
  110. package/src/a11y-assist/tests/fixtures/golden_screen_reader_raw_no_id.txt +14 -0
  111. package/src/a11y-assist/tests/fixtures/golden_screen_reader_raw_with_id.txt +14 -0
  112. package/src/a11y-assist/tests/fixtures/raw_good.txt +11 -0
  113. package/src/a11y-assist/tests/fixtures/raw_no_id.txt +2 -0
  114. package/src/a11y-assist/tests/test_cognitive_load.py +469 -0
  115. package/src/a11y-assist/tests/test_dyslexia.py +337 -0
  116. package/src/a11y-assist/tests/test_explain.py +74 -0
  117. package/src/a11y-assist/tests/test_golden.py +127 -0
  118. package/src/a11y-assist/tests/test_guard.py +819 -0
  119. package/src/a11y-assist/tests/test_guard_integration.py +457 -0
  120. package/src/a11y-assist/tests/test_ingest.py +311 -0
  121. package/src/a11y-assist/tests/test_methods_metadata.py +236 -0
  122. package/src/a11y-assist/tests/test_plain_language.py +348 -0
  123. package/src/a11y-assist/tests/test_render.py +117 -0
  124. package/src/a11y-assist/tests/test_screen_reader.py +703 -0
  125. package/src/a11y-assist/tests/test_storage_last.py +61 -0
  126. package/src/a11y-assist/tests/test_triage.py +86 -0
  127. package/src/a11y-ci/.github/workflows/ci.yml +43 -0
  128. package/src/a11y-ci/.github/workflows/test.yml +30 -0
  129. package/src/a11y-ci/A11Y_CI_TEST_COVERAGE_REQUIREMENTS.md +94 -0
  130. package/src/a11y-ci/CODE_OF_CONDUCT.md +129 -0
  131. package/src/a11y-ci/CONTRIBUTING.md +142 -0
  132. package/src/a11y-ci/LICENSE +21 -0
  133. package/src/a11y-ci/README.md +105 -0
  134. package/src/a11y-ci/a11y_ci/__init__.py +3 -0
  135. package/src/a11y-ci/a11y_ci/allowlist.py +83 -0
  136. package/src/a11y-ci/a11y_ci/cli.py +145 -0
  137. package/src/a11y-ci/a11y_ci/gate.py +131 -0
  138. package/src/a11y-ci/a11y_ci/render.py +48 -0
  139. package/src/a11y-ci/a11y_ci/schemas/allowlist.schema.json +24 -0
  140. package/src/a11y-ci/a11y_ci/scorecard.py +99 -0
  141. package/src/a11y-ci/npm/package.json +35 -0
  142. package/src/a11y-ci/pyproject.toml +64 -0
  143. package/src/a11y-ci/tests/__init__.py +1 -0
  144. package/src/a11y-ci/tests/fixtures/allowlist_expired.json +10 -0
  145. package/src/a11y-ci/tests/fixtures/allowlist_ok.json +10 -0
  146. package/src/a11y-ci/tests/fixtures/baseline_ok.json +7 -0
  147. package/src/a11y-ci/tests/fixtures/current_fail.json +6 -0
  148. package/src/a11y-ci/tests/fixtures/current_ok.json +6 -0
  149. package/src/a11y-ci/tests/fixtures/current_regresses.json +7 -0
  150. package/src/a11y-ci/tests/test_gate.py +134 -0
  151. package/src/a11y-evidence-engine/.github/workflows/ci.yml +53 -0
  152. package/src/a11y-evidence-engine/CODE_OF_CONDUCT.md +129 -0
  153. package/src/a11y-evidence-engine/CONTRIBUTING.md +128 -0
  154. package/src/a11y-evidence-engine/LICENSE +21 -0
  155. package/src/a11y-evidence-engine/README.md +71 -0
  156. package/src/a11y-evidence-engine/bin/a11y-engine.js +11 -0
  157. package/src/a11y-evidence-engine/fixtures/bad/button-no-name.html +30 -0
  158. package/src/a11y-evidence-engine/fixtures/bad/img-missing-alt.html +19 -0
  159. package/src/a11y-evidence-engine/fixtures/bad/input-missing-label.html +26 -0
  160. package/src/a11y-evidence-engine/fixtures/bad/missing-lang.html +11 -0
  161. package/src/a11y-evidence-engine/fixtures/good/index.html +29 -0
  162. package/src/a11y-evidence-engine/package-lock.json +109 -0
  163. package/src/a11y-evidence-engine/package.json +45 -0
  164. package/src/a11y-evidence-engine/src/cli.js +74 -0
  165. package/src/a11y-evidence-engine/src/evidence/canonicalize.js +52 -0
  166. package/src/a11y-evidence-engine/src/evidence/json_pointer.js +34 -0
  167. package/src/a11y-evidence-engine/src/evidence/prov_emit.js +153 -0
  168. package/src/a11y-evidence-engine/src/fswalk.js +56 -0
  169. package/src/a11y-evidence-engine/src/html_parse.js +117 -0
  170. package/src/a11y-evidence-engine/src/ids.js +53 -0
  171. package/src/a11y-evidence-engine/src/rules/document_missing_lang.js +50 -0
  172. package/src/a11y-evidence-engine/src/rules/form_control_missing_label.js +105 -0
  173. package/src/a11y-evidence-engine/src/rules/img_missing_alt.js +77 -0
  174. package/src/a11y-evidence-engine/src/rules/index.js +37 -0
  175. package/src/a11y-evidence-engine/src/rules/interactive_missing_name.js +129 -0
  176. package/src/a11y-evidence-engine/src/scan.js +128 -0
  177. package/src/a11y-evidence-engine/test/scan.test.js +149 -0
  178. package/src/a11y-evidence-engine/test/vectors.test.js +200 -0
  179. package/src/a11y-lint/.github/workflows/ci.yml +46 -0
  180. package/src/a11y-lint/.github/workflows/test.yml +34 -0
  181. package/src/a11y-lint/CODE_OF_CONDUCT.md +129 -0
  182. package/src/a11y-lint/CONTRIBUTING.md +70 -0
  183. package/src/a11y-lint/GOVERNANCE.md +57 -0
  184. package/src/a11y-lint/LICENSE +21 -0
  185. package/src/a11y-lint/PRESS_RELEASE.md +50 -0
  186. package/src/a11y-lint/README.md +276 -0
  187. package/src/a11y-lint/RELEASE_NOTES.md +57 -0
  188. package/src/a11y-lint/RELEASING.md +57 -0
  189. package/src/a11y-lint/a11y_lint/__init__.py +64 -0
  190. package/src/a11y-lint/a11y_lint/cli.py +319 -0
  191. package/src/a11y-lint/a11y_lint/errors.py +252 -0
  192. package/src/a11y-lint/a11y_lint/render.py +293 -0
  193. package/src/a11y-lint/a11y_lint/report_md.py +289 -0
  194. package/src/a11y-lint/a11y_lint/scan_cli_text.py +434 -0
  195. package/src/a11y-lint/a11y_lint/schemas/cli.error.schema.v0.1.json +83 -0
  196. package/src/a11y-lint/a11y_lint/scorecard.py +244 -0
  197. package/src/a11y-lint/a11y_lint/validate.py +225 -0
  198. package/src/a11y-lint/pyproject.toml +75 -0
  199. package/src/a11y-lint/tests/__init__.py +1 -0
  200. package/src/a11y-lint/tests/test_cli.py +200 -0
  201. package/src/a11y-lint/tests/test_errors.py +188 -0
  202. package/src/a11y-lint/tests/test_render.py +202 -0
  203. package/src/a11y-lint/tests/test_report_md.py +188 -0
  204. package/src/a11y-lint/tests/test_scan_cli_text.py +290 -0
  205. package/src/a11y-lint/tests/test_scorecard.py +195 -0
  206. package/src/a11y-lint/tests/test_validate.py +257 -0
  207. package/src/a11y-mcp-tools/.github/workflows/ci.yml +53 -0
  208. package/src/a11y-mcp-tools/CODE_OF_CONDUCT.md +129 -0
  209. package/src/a11y-mcp-tools/CONTRIBUTING.md +136 -0
  210. package/src/a11y-mcp-tools/LICENSE +21 -0
  211. package/src/a11y-mcp-tools/PROV_METHODS_CATALOG.md +104 -0
  212. package/src/a11y-mcp-tools/README.md +168 -0
  213. package/src/a11y-mcp-tools/bin/cli.js +452 -0
  214. package/src/a11y-mcp-tools/bin/server.js +244 -0
  215. package/src/a11y-mcp-tools/fixtures/requests/a11y.diagnose.ok.json +27 -0
  216. package/src/a11y-mcp-tools/fixtures/requests/a11y.evidence.ok.json +25 -0
  217. package/src/a11y-mcp-tools/fixtures/responses/a11y.diagnose.ok.json +139 -0
  218. package/src/a11y-mcp-tools/fixtures/responses/a11y.diagnose.provenance_fail.json +13 -0
  219. package/src/a11y-mcp-tools/fixtures/responses/a11y.evidence.ok.json +88 -0
  220. package/src/a11y-mcp-tools/package-lock.json +189 -0
  221. package/src/a11y-mcp-tools/package.json +49 -0
  222. package/src/a11y-mcp-tools/src/envelope.js +197 -0
  223. package/src/a11y-mcp-tools/src/index.js +9 -0
  224. package/src/a11y-mcp-tools/src/schemas/artifact.js +85 -0
  225. package/src/a11y-mcp-tools/src/schemas/diagnosis.schema.v0.1.json +137 -0
  226. package/src/a11y-mcp-tools/src/schemas/envelope.schema.v0.1.json +108 -0
  227. package/src/a11y-mcp-tools/src/schemas/evidence.bundle.schema.v0.1.json +129 -0
  228. package/src/a11y-mcp-tools/src/schemas/evidence.js +97 -0
  229. package/src/a11y-mcp-tools/src/schemas/index.js +11 -0
  230. package/src/a11y-mcp-tools/src/schemas/provenance.js +140 -0
  231. package/src/a11y-mcp-tools/src/schemas/tools/a11y.diagnose.request.schema.v0.1.json +77 -0
  232. package/src/a11y-mcp-tools/src/schemas/tools/a11y.diagnose.response.schema.v0.1.json +50 -0
  233. package/src/a11y-mcp-tools/src/schemas/tools/a11y.evidence.request.schema.v0.1.json +120 -0
  234. package/src/a11y-mcp-tools/src/schemas/tools/a11y.evidence.response.schema.v0.1.json +50 -0
  235. package/src/a11y-mcp-tools/src/tools/diagnose.js +597 -0
  236. package/src/a11y-mcp-tools/src/tools/evidence.js +481 -0
  237. package/src/a11y-mcp-tools/src/tools/index.js +10 -0
  238. package/src/a11y-mcp-tools/test/contract.test.mjs +154 -0
  239. package/src/a11y-mcp-tools/test/diagnose.test.js +485 -0
  240. package/src/a11y-mcp-tools/test/evidence.test.js +183 -0
  241. package/src/a11y-mcp-tools/test/schema.test.js +327 -0
@@ -0,0 +1,168 @@
1
+ # a11y-mcp-tools
2
+
3
+ MCP (Model Context Protocol) tools for accessibility evidence capture and diagnosis.
4
+
5
+ ## Tools
6
+
7
+ ### `a11y.evidence`
8
+
9
+ Capture tamper-evident evidence bundles from HTML files, CLI logs, or other inputs.
10
+
11
+ **Capabilities:**
12
+ - Canonical HTML normalization
13
+ - DOM snapshot extraction
14
+ - SHA-256 integrity digests
15
+ - prov-spec provenance records
16
+
17
+ ### `a11y.diagnose`
18
+
19
+ Run deterministic accessibility checks over evidence bundles.
20
+
21
+ **Capabilities:**
22
+ - WCAG 2.2 AA rule checking
23
+ - Evidence-anchored findings (JSON Pointer, CSS selector, line spans)
24
+ - SAFE-only fix guidance (intent patches, not direct writes)
25
+ - Provenance verification
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ npm install -g a11y-mcp-tools
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ### CLI (Recommended)
36
+
37
+ ```bash
38
+ # Capture evidence from HTML file
39
+ a11y evidence --target index.html --dom-snapshot --out evidence.json
40
+
41
+ # Diagnose captured evidence
42
+ a11y diagnose --bundle evidence.json --fix
43
+
44
+ # With provenance verification
45
+ a11y diagnose --bundle evidence.json --verify-provenance --fix
46
+
47
+ # Output with MCP envelope
48
+ a11y evidence --target page.html --dom-snapshot --envelope
49
+
50
+ # One-liner capture and diagnose
51
+ a11y evidence --target page.html --dom-snapshot | a11y diagnose --fix
52
+ ```
53
+
54
+ **Exit Codes (CI-native):**
55
+ - `0` - Success (no findings at/above `--fail-on`)
56
+ - `2` - Findings exist (tool succeeded, but issues found)
57
+ - `3` - Capture/validation failure (bad input, schema error)
58
+ - `4` - Provenance verification failed (digest mismatch)
59
+
60
+ ### As MCP Server
61
+
62
+ ```bash
63
+ a11y-mcp
64
+ ```
65
+
66
+ ### MCP Envelope Format (v0.1)
67
+
68
+ Requests and responses use a standard envelope:
69
+
70
+ **Request:**
71
+ ```json
72
+ {
73
+ "mcp": {
74
+ "envelope": "mcp.envelope_v0_1",
75
+ "request_id": "req_01HR9Y6GQ7V8WQ0K8N9K",
76
+ "tool": "a11y.evidence",
77
+ "client": { "name": "a11y-cli", "version": "0.2.0" }
78
+ },
79
+ "input": {
80
+ "targets": [{ "kind": "file", "path": "html/index.html" }],
81
+ "capture": { "html": { "canonicalize": true }, "dom": { "snapshot": true } }
82
+ }
83
+ }
84
+ ```
85
+
86
+ **Response:**
87
+ ```json
88
+ {
89
+ "mcp": {
90
+ "envelope": "mcp.envelope_v0_1",
91
+ "request_id": "req_01HR9Y6GQ7V8WQ0K8N9K",
92
+ "tool": "a11y.evidence",
93
+ "ok": true
94
+ },
95
+ "result": {
96
+ "bundle": { ... }
97
+ }
98
+ }
99
+ ```
100
+
101
+ **Error:**
102
+ ```json
103
+ {
104
+ "mcp": {
105
+ "envelope": "mcp.envelope_v0_1",
106
+ "request_id": "req_01HR9Y6GQ7V8WQ0K8N9K",
107
+ "tool": "a11y.diagnose",
108
+ "ok": false
109
+ },
110
+ "error": {
111
+ "code": "PROVENANCE_VERIFICATION_FAILED",
112
+ "message": "Evidence digest mismatch for artifact:dom:index.",
113
+ "fix": "Re-run a11y.evidence to recapture evidence."
114
+ }
115
+ }
116
+ ```
117
+
118
+ ## Schemas
119
+
120
+ JSON Schemas are provided for validation:
121
+
122
+ - [`envelope.schema.v0.1.json`](src/schemas/envelope.schema.v0.1.json) - MCP envelope format
123
+ - [`evidence.bundle.schema.v0.1.json`](src/schemas/evidence.bundle.schema.v0.1.json) - Evidence bundle format
124
+ - [`diagnosis.schema.v0.1.json`](src/schemas/diagnosis.schema.v0.1.json) - Diagnosis output format
125
+
126
+ ## Method ID Catalog (v0.1)
127
+
128
+ Stable method IDs for provenance tracking. See [PROV_METHODS_CATALOG.md](PROV_METHODS_CATALOG.md) for full documentation.
129
+
130
+ | Method ID | Description |
131
+ |-----------|-------------|
132
+ | `adapter.wrap.envelope_v0_1` | Wrap in MCP envelope |
133
+ | `adapter.provenance.record_v0_1` | Provenance record creation |
134
+ | `adapter.integrity.sha256_v0_1` | SHA-256 integrity verification |
135
+ | `engine.capture.html_canonicalize_v0_1` | HTML capture with canonicalization |
136
+ | `engine.capture.dom_snapshot_v0_1` | DOM snapshot extraction |
137
+ | `engine.diagnose.wcag_rules_v0_1` | WCAG rule evaluation |
138
+ | `engine.extract.evidence.json_pointer_v0_1` | JSON Pointer evidence extraction |
139
+ | `engine.extract.evidence.selector_v0_1` | CSS selector evidence extraction |
140
+
141
+ ## Shared Artifact Model
142
+
143
+ Both tools work with a shared artifact/provenance model:
144
+
145
+ - **Artifacts**: Captured content with digests and metadata
146
+ - **Evidence Anchors**: Pointers back to artifact locations (JSON Pointer, selector, line span)
147
+ - **Provenance**: prov-spec records documenting capture and analysis
148
+
149
+ ## WCAG Rules (v0.1)
150
+
151
+ | Rule | Finding ID | WCAG | Description |
152
+ |------|-----------|------|-------------|
153
+ | `lang` | `a11y.lang.missing` | 3.1.1 | Missing lang attribute on html element |
154
+ | `alt` | `a11y.img.missing_alt` | 1.1.1 | Missing alt attribute on img element |
155
+ | `button-name` | `a11y.button.missing_name` | 4.1.2 | Button without accessible name |
156
+ | `link-name` | `a11y.link.missing_name` | 4.1.2 | Link without accessible name |
157
+ | `label` | `a11y.input.missing_label` | 1.3.1 | Form input without label |
158
+
159
+ ## Related
160
+
161
+ - [prov-spec](https://github.com/mcp-tool-shop-org/prov-spec) - Provenance specification
162
+ - [a11y-evidence-engine](https://github.com/mcp-tool-shop-org/a11y-evidence-engine) - CLI scanner
163
+ - [a11y-assist](https://github.com/mcp-tool-shop-org/a11y-assist) - Fix advisor
164
+ - [a11y-demo-site](https://github.com/mcp-tool-shop-org/a11y-demo-site) - Demo with CI workflows
165
+
166
+ ## License
167
+
168
+ MIT
@@ -0,0 +1,452 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * a11y CLI - Command-line interface for a11y MCP tools.
6
+ *
7
+ * Maps directly to MCP tool calls with standard exit codes:
8
+ * 0 = success, no findings at/above --fail-on
9
+ * 2 = success, findings exist (but not a tool failure)
10
+ * 3 = capture/validation failure (bad input, schema fail, etc.)
11
+ * 4 = provenance verification failed (digest mismatch)
12
+ */
13
+
14
+ const fs = require("fs");
15
+ const path = require("path");
16
+ const { evidence, diagnose } = require("../src/tools/index.js");
17
+ const {
18
+ createRequestEnvelope,
19
+ createResponseEnvelope,
20
+ createErrorEnvelope,
21
+ ERROR_CODES,
22
+ } = require("../src/envelope.js");
23
+
24
+ const VERSION = "0.2.0";
25
+
26
+ // Exit codes (CI-native)
27
+ const EXIT_OK = 0; // Success, no findings
28
+ const EXIT_FINDINGS = 2; // Success, findings exist
29
+ const EXIT_VALIDATION = 3; // Capture/validation failure
30
+ const EXIT_PROVENANCE = 4; // Provenance verification failed
31
+
32
+ /**
33
+ * Parse command-line arguments.
34
+ */
35
+ function parseArgs(args) {
36
+ const result = {
37
+ command: null,
38
+ flags: {},
39
+ positional: [],
40
+ };
41
+
42
+ let i = 0;
43
+ while (i < args.length) {
44
+ const arg = args[i];
45
+
46
+ if (!result.command && !arg.startsWith("-")) {
47
+ result.command = arg;
48
+ i++;
49
+ continue;
50
+ }
51
+
52
+ if (arg.startsWith("--")) {
53
+ const key = arg.slice(2);
54
+ const nextArg = args[i + 1];
55
+
56
+ // Boolean flags vs value flags
57
+ if (
58
+ !nextArg ||
59
+ nextArg.startsWith("-") ||
60
+ key === "dom-snapshot" ||
61
+ key === "canonicalize" ||
62
+ key === "strip-dynamic" ||
63
+ key === "fix" ||
64
+ key === "json" ||
65
+ key === "envelope" ||
66
+ key === "verify-provenance" ||
67
+ key === "help" ||
68
+ key === "version"
69
+ ) {
70
+ result.flags[key] = true;
71
+ i++;
72
+ } else {
73
+ result.flags[key] = nextArg;
74
+ i += 2;
75
+ }
76
+ } else if (arg.startsWith("-")) {
77
+ // Short flags
78
+ const key = arg.slice(1);
79
+ result.flags[key] = true;
80
+ i++;
81
+ } else {
82
+ result.positional.push(arg);
83
+ i++;
84
+ }
85
+ }
86
+
87
+ return result;
88
+ }
89
+
90
+ /**
91
+ * Print help message.
92
+ */
93
+ function printHelp() {
94
+ console.log(`
95
+ a11y - Accessibility evidence capture and diagnosis
96
+
97
+ USAGE:
98
+ a11y <command> [options]
99
+
100
+ COMMANDS:
101
+ evidence Capture tamper-evident evidence bundles
102
+ diagnose Run accessibility diagnosis on evidence
103
+
104
+ EVIDENCE OPTIONS:
105
+ --target <path> File to capture (can be repeated)
106
+ --dom-snapshot Include DOM snapshot artifact
107
+ --canonicalize Canonicalize HTML content
108
+ --strip-dynamic Strip dynamic attributes (React, Vue, Angular)
109
+ --label <label> Add label to artifacts (can be repeated)
110
+ --out <path> Output bundle to file (default: stdout)
111
+ --envelope Wrap output in MCP envelope
112
+
113
+ DIAGNOSE OPTIONS:
114
+ --bundle <path> Path to evidence bundle JSON
115
+ --bundle-id <id> Bundle ID (when using MCP server)
116
+ --profile <profile> WCAG profile (wcag-2.0-a, wcag-2.0-aa, wcag-2.1-a,
117
+ wcag-2.1-aa, wcag-2.2-a, wcag-2.2-aa) (default: wcag-2.2-aa)
118
+ --rules <rules> Comma-separated rule names to run
119
+ --exclude <rules> Comma-separated rules to exclude
120
+ --fix Include fix guidance in findings
121
+ --verify-provenance Verify artifact digests before diagnosis
122
+ --fail-on <severity> Exit 2 if findings at/above severity (default: low)
123
+ --out <path> Output diagnosis to file (default: stdout)
124
+ --envelope Wrap output in MCP envelope
125
+
126
+ GLOBAL OPTIONS:
127
+ --json Output as JSON (default)
128
+ --envelope Wrap output in MCP envelope
129
+ --help, -h Show this help message
130
+ --version, -v Show version
131
+
132
+ EXIT CODES:
133
+ 0 Success (no findings at/above --fail-on)
134
+ 2 Findings exist (tool succeeded, but issues found)
135
+ 3 Capture/validation failure (bad input, schema error)
136
+ 4 Provenance verification failed (digest mismatch)
137
+
138
+ EXAMPLES:
139
+ # Capture evidence from HTML file
140
+ a11y evidence --target index.html --dom-snapshot --out evidence.json
141
+
142
+ # Diagnose captured evidence
143
+ a11y diagnose --bundle evidence.json --fix
144
+
145
+ # Diagnose with specific WCAG profile
146
+ a11y diagnose --bundle evidence.json --profile wcag-2.1-aa --fix
147
+
148
+ # With provenance verification
149
+ a11y diagnose --bundle evidence.json --verify-provenance --fix
150
+
151
+ # Output with MCP envelope
152
+ a11y evidence --target page.html --envelope
153
+
154
+ # One-liner capture and diagnose
155
+ a11y evidence --target page.html --dom-snapshot | a11y diagnose --fix
156
+ `);
157
+ }
158
+
159
+ /**
160
+ * Evidence command handler.
161
+ */
162
+ async function handleEvidence(flags, positional) {
163
+ // Collect targets
164
+ const targets = [];
165
+
166
+ // From --target flags (may be repeated)
167
+ if (flags.target) {
168
+ const targetPaths = Array.isArray(flags.target)
169
+ ? flags.target
170
+ : [flags.target];
171
+ for (const p of targetPaths) {
172
+ targets.push({ kind: "file", path: p });
173
+ }
174
+ }
175
+
176
+ // From positional args
177
+ for (const p of positional) {
178
+ targets.push({ kind: "file", path: p });
179
+ }
180
+
181
+ if (targets.length === 0) {
182
+ console.error("Error: No targets specified. Use --target <path>");
183
+ process.exit(EXIT_VALIDATION);
184
+ }
185
+
186
+ // Build capture options
187
+ const capture = {
188
+ html: {},
189
+ dom: {},
190
+ environment: { include: ["os", "node", "tool_versions"] },
191
+ };
192
+
193
+ if (flags["dom-snapshot"]) {
194
+ capture.dom.snapshot = true;
195
+ capture.dom.include_css_selectors = true;
196
+ }
197
+
198
+ if (flags.canonicalize) {
199
+ capture.html.canonicalize = true;
200
+ }
201
+
202
+ if (flags["strip-dynamic"]) {
203
+ capture.html.strip_dynamic = true;
204
+ }
205
+
206
+ // Collect labels
207
+ const labels = [];
208
+ if (flags.label) {
209
+ const labelList = Array.isArray(flags.label) ? flags.label : [flags.label];
210
+ labels.push(...labelList);
211
+ }
212
+
213
+ // Build input
214
+ const input = { targets, capture, labels };
215
+
216
+ // Execute
217
+ const result = await evidence.execute(input);
218
+
219
+ if (!result.ok) {
220
+ if (flags.envelope) {
221
+ const errorEnvelope = createErrorEnvelope(
222
+ `req_cli_${Date.now()}`,
223
+ "a11y.evidence",
224
+ result.error?.code || ERROR_CODES.CAPTURE_FAILED,
225
+ result.error?.message || "Evidence capture failed"
226
+ );
227
+ console.log(JSON.stringify(errorEnvelope, null, 2));
228
+ } else {
229
+ console.error(`Error: ${result.error.message}`);
230
+ }
231
+ process.exit(EXIT_VALIDATION);
232
+ }
233
+
234
+ // Output
235
+ let output;
236
+ if (flags.envelope) {
237
+ const envelope = createResponseEnvelope(
238
+ `req_cli_${Date.now()}`,
239
+ "a11y.evidence",
240
+ { bundle: result.bundle }
241
+ );
242
+ output = JSON.stringify(envelope, null, 2);
243
+ } else {
244
+ output = JSON.stringify(result.bundle, null, 2);
245
+ }
246
+
247
+ if (flags.out) {
248
+ fs.writeFileSync(flags.out, output + "\n");
249
+ console.error(`Bundle written to: ${flags.out}`);
250
+ } else {
251
+ console.log(output);
252
+ }
253
+
254
+ process.exit(EXIT_OK);
255
+ }
256
+
257
+ /**
258
+ * Diagnose command handler.
259
+ */
260
+ async function handleDiagnose(flags, positional) {
261
+ let bundle = null;
262
+
263
+ // Load bundle from file
264
+ if (flags.bundle) {
265
+ const bundlePath = path.resolve(flags.bundle);
266
+ if (!fs.existsSync(bundlePath)) {
267
+ console.error(`Error: Bundle file not found: ${bundlePath}`);
268
+ process.exit(EXIT_VALIDATION);
269
+ }
270
+ try {
271
+ bundle = JSON.parse(fs.readFileSync(bundlePath, "utf8"));
272
+ } catch (err) {
273
+ console.error(`Error: Failed to parse bundle: ${err.message}`);
274
+ process.exit(EXIT_VALIDATION);
275
+ }
276
+ } else if (positional.length > 0) {
277
+ // First positional as bundle path
278
+ const bundlePath = path.resolve(positional[0]);
279
+ if (!fs.existsSync(bundlePath)) {
280
+ console.error(`Error: Bundle file not found: ${bundlePath}`);
281
+ process.exit(EXIT_VALIDATION);
282
+ }
283
+ try {
284
+ bundle = JSON.parse(fs.readFileSync(bundlePath, "utf8"));
285
+ } catch (err) {
286
+ console.error(`Error: Failed to parse bundle: ${err.message}`);
287
+ process.exit(EXIT_VALIDATION);
288
+ }
289
+ } else {
290
+ // Read from stdin
291
+ const stdin = fs.readFileSync(0, "utf8");
292
+ try {
293
+ bundle = JSON.parse(stdin);
294
+ } catch (err) {
295
+ console.error("Error: Failed to parse bundle from stdin");
296
+ process.exit(EXIT_VALIDATION);
297
+ }
298
+ }
299
+
300
+ // Build rules filter
301
+ const rules = {};
302
+ if (flags.rules) {
303
+ rules.include = flags.rules.split(",").map((r) => r.trim());
304
+ }
305
+ if (flags.exclude) {
306
+ rules.exclude = flags.exclude.split(",").map((r) => r.trim());
307
+ }
308
+
309
+ // Build output options
310
+ const outputOptions = {};
311
+ if (flags.fix) {
312
+ outputOptions.include_fix_guidance = true;
313
+ }
314
+
315
+ // Build integrity options
316
+ const integrity = {};
317
+ if (flags["verify-provenance"]) {
318
+ integrity.verify_provenance = true;
319
+ }
320
+
321
+ // Get profile (default wcag-2.2-aa)
322
+ const profile = flags.profile || "wcag-2.2-aa";
323
+ const validProfiles = [
324
+ "wcag-2.0-a",
325
+ "wcag-2.0-aa",
326
+ "wcag-2.1-a",
327
+ "wcag-2.1-aa",
328
+ "wcag-2.2-a",
329
+ "wcag-2.2-aa",
330
+ ];
331
+ if (!validProfiles.includes(profile)) {
332
+ console.error(`Error: Invalid profile '${profile}'. Valid profiles: ${validProfiles.join(", ")}`);
333
+ process.exit(EXIT_VALIDATION);
334
+ }
335
+
336
+ // Execute
337
+ const input = { bundle, rules, output: outputOptions, integrity, profile };
338
+ const result = await diagnose.execute(input);
339
+
340
+ if (!result.ok) {
341
+ // Check for provenance verification failure
342
+ const isProvenanceError =
343
+ result.error?.code === ERROR_CODES.PROVENANCE_VERIFICATION_FAILED ||
344
+ result.error?.code === ERROR_CODES.DIGEST_MISMATCH;
345
+
346
+ if (flags.envelope) {
347
+ const errorEnvelope = createErrorEnvelope(
348
+ `req_cli_${Date.now()}`,
349
+ "a11y.diagnose",
350
+ result.error?.code || ERROR_CODES.INTERNAL_ERROR,
351
+ result.error?.message || "Diagnosis failed",
352
+ result.error?.fix
353
+ );
354
+ console.log(JSON.stringify(errorEnvelope, null, 2));
355
+ } else {
356
+ console.error(`Error: ${result.error.message}`);
357
+ }
358
+
359
+ process.exit(isProvenanceError ? EXIT_PROVENANCE : EXIT_VALIDATION);
360
+ }
361
+
362
+ // Output
363
+ let output;
364
+ if (flags.envelope) {
365
+ const envelope = createResponseEnvelope(
366
+ `req_cli_${Date.now()}`,
367
+ "a11y.diagnose",
368
+ { diagnosis: result.diagnosis }
369
+ );
370
+ output = JSON.stringify(envelope, null, 2);
371
+ } else {
372
+ output = JSON.stringify(result.diagnosis, null, 2);
373
+ }
374
+
375
+ if (flags.out) {
376
+ fs.writeFileSync(flags.out, output + "\n");
377
+ console.error(`Diagnosis written to: ${flags.out}`);
378
+ } else {
379
+ console.log(output);
380
+ }
381
+
382
+ // Exit based on findings
383
+ const findingsCount = result.diagnosis.summary.findings_total;
384
+ if (findingsCount > 0) {
385
+ // Check --fail-on threshold
386
+ const failOn = flags["fail-on"] || "low";
387
+ const severityOrder = ["low", "medium", "high", "critical"];
388
+ const failIndex = severityOrder.indexOf(failOn);
389
+
390
+ // Count findings at or above threshold
391
+ const counts = result.diagnosis.summary.severity_counts;
392
+ let failingCount = 0;
393
+ for (let i = failIndex; i < severityOrder.length; i++) {
394
+ failingCount += counts[severityOrder[i]] || 0;
395
+ }
396
+
397
+ if (failingCount > 0) {
398
+ console.error(`Found ${failingCount} issue(s) at/above '${failOn}' severity`);
399
+ process.exit(EXIT_FINDINGS);
400
+ }
401
+ }
402
+
403
+ process.exit(EXIT_OK);
404
+ }
405
+
406
+ /**
407
+ * Main entry point.
408
+ */
409
+ async function main() {
410
+ const args = process.argv.slice(2);
411
+
412
+ if (args.length === 0) {
413
+ printHelp();
414
+ process.exit(EXIT_OK);
415
+ }
416
+
417
+ const { command, flags, positional } = parseArgs(args);
418
+
419
+ // Global flags
420
+ if (flags.help || flags.h) {
421
+ printHelp();
422
+ process.exit(EXIT_OK);
423
+ }
424
+
425
+ if (flags.version || flags.v) {
426
+ console.log(`a11y v${VERSION}`);
427
+ process.exit(EXIT_OK);
428
+ }
429
+
430
+ // Route to command handler
431
+ switch (command) {
432
+ case "evidence":
433
+ await handleEvidence(flags, positional);
434
+ break;
435
+
436
+ case "diagnose":
437
+ await handleDiagnose(flags, positional);
438
+ break;
439
+
440
+ default:
441
+ if (command) {
442
+ console.error(`Unknown command: ${command}`);
443
+ }
444
+ printHelp();
445
+ process.exit(EXIT_VALIDATION);
446
+ }
447
+ }
448
+
449
+ main().catch((err) => {
450
+ console.error(`Fatal error: ${err.message}`);
451
+ process.exit(EXIT_VALIDATION);
452
+ });