@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,30 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Buttons Without Names</title>
6
+ </head>
7
+ <body>
8
+ <h1>Interactive Elements</h1>
9
+
10
+ <!-- Empty button -->
11
+ <button type="button"></button>
12
+
13
+ <!-- Icon button without label -->
14
+ <button type="button" class="icon-btn">
15
+ <span class="icon"></span>
16
+ </button>
17
+
18
+ <!-- Empty link -->
19
+ <a href="/somewhere"></a>
20
+
21
+ <!-- This is fine -->
22
+ <button type="submit">Submit Form</button>
23
+
24
+ <!-- This is fine (has aria-label) -->
25
+ <button type="button" aria-label="Close dialog"></button>
26
+
27
+ <!-- This is fine (has title) -->
28
+ <a href="/help" title="Get Help"></a>
29
+ </body>
30
+ </html>
@@ -0,0 +1,19 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Missing Alt Text</title>
6
+ </head>
7
+ <body>
8
+ <h1>Images Without Alt</h1>
9
+
10
+ <!-- This image is missing alt text -->
11
+ <img src="photo.jpg">
12
+
13
+ <!-- This one too -->
14
+ <img src="banner.png" class="hero">
15
+
16
+ <!-- This is fine (decorative) -->
17
+ <img src="divider.gif" role="presentation">
18
+ </body>
19
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Missing Labels</title>
6
+ </head>
7
+ <body>
8
+ <h1>Form Without Labels</h1>
9
+
10
+ <form>
11
+ <!-- Missing label -->
12
+ <input type="text" name="username" placeholder="Username">
13
+
14
+ <!-- Missing label -->
15
+ <input type="password" name="password">
16
+
17
+ <!-- This is fine (has aria-label) -->
18
+ <input type="search" aria-label="Search the site">
19
+
20
+ <!-- This is fine (hidden) -->
21
+ <input type="hidden" name="token" value="abc">
22
+
23
+ <button type="submit">Login</button>
24
+ </form>
25
+ </body>
26
+ </html>
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Missing Language</title>
6
+ </head>
7
+ <body>
8
+ <h1>Page Without Language</h1>
9
+ <p>This page is missing the lang attribute on the html element.</p>
10
+ </body>
11
+ </html>
@@ -0,0 +1,29 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Accessible Page</title>
6
+ </head>
7
+ <body>
8
+ <h1>Welcome</h1>
9
+
10
+ <img src="logo.png" alt="Company Logo">
11
+ <img src="decorative.png" role="presentation">
12
+
13
+ <form>
14
+ <label for="name">Name:</label>
15
+ <input type="text" id="name" name="name">
16
+
17
+ <label for="email">Email:</label>
18
+ <input type="email" id="email" name="email">
19
+
20
+ <input type="hidden" name="csrf" value="token">
21
+ <button type="submit">Submit</button>
22
+ </form>
23
+
24
+ <nav>
25
+ <a href="/home">Home</a>
26
+ <a href="/about">About Us</a>
27
+ </nav>
28
+ </body>
29
+ </html>
@@ -0,0 +1,109 @@
1
+ {
2
+ "name": "a11y-evidence-engine",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "a11y-evidence-engine",
9
+ "version": "0.1.0",
10
+ "license": "MIT",
11
+ "dependencies": {
12
+ "htmlparser2": "^9.1.0"
13
+ },
14
+ "bin": {
15
+ "a11y-engine": "bin/a11y-engine.js"
16
+ },
17
+ "devDependencies": {},
18
+ "engines": {
19
+ "node": ">=18.0.0"
20
+ }
21
+ },
22
+ "node_modules/dom-serializer": {
23
+ "version": "2.0.0",
24
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
25
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "domelementtype": "^2.3.0",
29
+ "domhandler": "^5.0.2",
30
+ "entities": "^4.2.0"
31
+ },
32
+ "funding": {
33
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
34
+ }
35
+ },
36
+ "node_modules/domelementtype": {
37
+ "version": "2.3.0",
38
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
39
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
40
+ "funding": [
41
+ {
42
+ "type": "github",
43
+ "url": "https://github.com/sponsors/fb55"
44
+ }
45
+ ],
46
+ "license": "BSD-2-Clause"
47
+ },
48
+ "node_modules/domhandler": {
49
+ "version": "5.0.3",
50
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
51
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
52
+ "license": "BSD-2-Clause",
53
+ "dependencies": {
54
+ "domelementtype": "^2.3.0"
55
+ },
56
+ "engines": {
57
+ "node": ">= 4"
58
+ },
59
+ "funding": {
60
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
61
+ }
62
+ },
63
+ "node_modules/domutils": {
64
+ "version": "3.2.2",
65
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
66
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
67
+ "license": "BSD-2-Clause",
68
+ "dependencies": {
69
+ "dom-serializer": "^2.0.0",
70
+ "domelementtype": "^2.3.0",
71
+ "domhandler": "^5.0.3"
72
+ },
73
+ "funding": {
74
+ "url": "https://github.com/fb55/domutils?sponsor=1"
75
+ }
76
+ },
77
+ "node_modules/entities": {
78
+ "version": "4.5.0",
79
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
80
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
81
+ "license": "BSD-2-Clause",
82
+ "engines": {
83
+ "node": ">=0.12"
84
+ },
85
+ "funding": {
86
+ "url": "https://github.com/fb55/entities?sponsor=1"
87
+ }
88
+ },
89
+ "node_modules/htmlparser2": {
90
+ "version": "9.1.0",
91
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
92
+ "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
93
+ "funding": [
94
+ "https://github.com/fb55/htmlparser2?sponsor=1",
95
+ {
96
+ "type": "github",
97
+ "url": "https://github.com/sponsors/fb55"
98
+ }
99
+ ],
100
+ "license": "MIT",
101
+ "dependencies": {
102
+ "domelementtype": "^2.3.0",
103
+ "domhandler": "^5.0.3",
104
+ "domutils": "^3.1.0",
105
+ "entities": "^4.5.0"
106
+ }
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@mcptoolshop/a11y-evidence-engine",
3
+ "version": "0.2.1",
4
+ "description": "Headless accessibility evidence engine with prov-spec provenance",
5
+ "main": "src/cli.js",
6
+ "bin": {
7
+ "a11y-engine": "./bin/a11y-engine.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node --test test/*.test.js",
11
+ "scan": "node bin/a11y-engine.js scan"
12
+ },
13
+ "keywords": [
14
+ "accessibility",
15
+ "a11y",
16
+ "wcag",
17
+ "provenance",
18
+ "prov-spec",
19
+ "evidence",
20
+ "testing",
21
+ "headless"
22
+ ],
23
+ "author": "mcp-tool-shop <64996768+mcp-tool-shop@users.noreply.github.com>",
24
+ "license": "MIT",
25
+ "homepage": "https://github.com/mcp-tool-shop-org/accessibility-suite#readme",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/mcp-tool-shop-org/accessibility-suite.git",
29
+ "directory": "src/a11y-evidence-engine"
30
+ },
31
+ "bugs": {
32
+ "url": "https://github.com/mcp-tool-shop-org/accessibility-suite/issues"
33
+ },
34
+ "engines": {
35
+ "node": ">=18.0.0"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public",
39
+ "registry": "https://registry.npmjs.org"
40
+ },
41
+ "dependencies": {
42
+ "htmlparser2": "^9.1.0"
43
+ },
44
+ "devDependencies": {}
45
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+
3
+ const { scan } = require("./scan.js");
4
+ const path = require("path");
5
+
6
+ const HELP = `
7
+ a11y-engine - Headless accessibility evidence engine
8
+
9
+ USAGE:
10
+ a11y-engine scan <path> --out <dir> Scan HTML files and emit findings
11
+ a11y-engine --help Show this help
12
+
13
+ OPTIONS:
14
+ --out <dir> Output directory for findings.json and provenance (default: ./out)
15
+
16
+ EXIT CODES:
17
+ 0 No findings with severity 'error'
18
+ 2 At least one 'error' finding
19
+ 3 Internal engine failure / invalid input
20
+ `;
21
+
22
+ async function run(args) {
23
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
24
+ console.log(HELP);
25
+ return 0;
26
+ }
27
+
28
+ const command = args[0];
29
+
30
+ if (command === "scan") {
31
+ return runScan(args.slice(1));
32
+ }
33
+
34
+ console.error(`Unknown command: ${command}`);
35
+ console.log(HELP);
36
+ return 3;
37
+ }
38
+
39
+ async function runScan(args) {
40
+ // Parse arguments
41
+ let targetPath = null;
42
+ let outDir = "./out";
43
+
44
+ for (let i = 0; i < args.length; i++) {
45
+ if (args[i] === "--out" && args[i + 1]) {
46
+ outDir = args[++i];
47
+ } else if (!args[i].startsWith("-")) {
48
+ targetPath = args[i];
49
+ }
50
+ }
51
+
52
+ if (!targetPath) {
53
+ console.error("Error: No target path specified");
54
+ return 3;
55
+ }
56
+
57
+ try {
58
+ const result = await scan(targetPath, outDir);
59
+
60
+ console.log(`Scanned ${result.summary.files_scanned} file(s)`);
61
+ console.log(` Errors: ${result.summary.errors}`);
62
+ console.log(` Warnings: ${result.summary.warnings}`);
63
+ console.log(` Info: ${result.summary.info}`);
64
+ console.log(`\nOutput: ${path.resolve(outDir)}/findings.json`);
65
+
66
+ // Exit code based on error count
67
+ return result.summary.errors > 0 ? 2 : 0;
68
+ } catch (err) {
69
+ console.error(`Scan failed: ${err.message}`);
70
+ return 3;
71
+ }
72
+ }
73
+
74
+ module.exports = { run };
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Canonicalize a JSON value per prov-spec requirements.
5
+ * - Sorted keys
6
+ * - No whitespace
7
+ * - UTF-8 (handled by Node.js strings)
8
+ *
9
+ * This is a JCS-subset per RFC 8785.
10
+ *
11
+ * @param {any} value - JSON-compatible value
12
+ * @returns {string} Canonical JSON string
13
+ */
14
+ function canonicalize(value) {
15
+ if (value === null) {
16
+ return "null";
17
+ }
18
+
19
+ const type = typeof value;
20
+
21
+ if (type === "boolean") {
22
+ return value ? "true" : "false";
23
+ }
24
+
25
+ if (type === "string") {
26
+ return JSON.stringify(value);
27
+ }
28
+
29
+ if (type === "number") {
30
+ if (!Number.isFinite(value)) {
31
+ throw new Error("Non-finite numbers not allowed in canonical JSON");
32
+ }
33
+ return JSON.stringify(value);
34
+ }
35
+
36
+ if (Array.isArray(value)) {
37
+ const items = value.map((item) => canonicalize(item));
38
+ return "[" + items.join(",") + "]";
39
+ }
40
+
41
+ if (type === "object") {
42
+ const keys = Object.keys(value).sort();
43
+ const pairs = keys.map(
44
+ (key) => JSON.stringify(key) + ":" + canonicalize(value[key])
45
+ );
46
+ return "{" + pairs.join(",") + "}";
47
+ }
48
+
49
+ throw new Error(`Non-JSON value type: ${type}`);
50
+ }
51
+
52
+ module.exports = { canonicalize };
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * JSON Pointer utilities per RFC 6901.
5
+ */
6
+
7
+ /**
8
+ * Build a JSON pointer from nodes index.
9
+ * @param {number} index - Node index
10
+ * @returns {string} JSON pointer
11
+ */
12
+ function nodePointer(index) {
13
+ return `/nodes/${index}`;
14
+ }
15
+
16
+ /**
17
+ * Escape a string for use in a JSON pointer segment.
18
+ * @param {string} str - Raw string
19
+ * @returns {string} Escaped string
20
+ */
21
+ function escapePointer(str) {
22
+ return str.replace(/~/g, "~0").replace(/\//g, "~1");
23
+ }
24
+
25
+ /**
26
+ * Unescape a JSON pointer segment.
27
+ * @param {string} str - Escaped string
28
+ * @returns {string} Raw string
29
+ */
30
+ function unescapePointer(str) {
31
+ return str.replace(/~1/g, "/").replace(/~0/g, "~");
32
+ }
33
+
34
+ module.exports = { nodePointer, escapePointer, unescapePointer };
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+
3
+ const crypto = require("crypto");
4
+ const { canonicalize } = require("./canonicalize.js");
5
+
6
+ /**
7
+ * Generate prov-spec provenance records for a finding.
8
+ *
9
+ * Emits three records:
10
+ * 1. record.json - engine.extract.evidence.json_pointer
11
+ * 2. digest.json - integrity.digest.sha256
12
+ * 3. envelope.json - adapter.wrap.envelope_v0_1
13
+ *
14
+ * @param {Object} finding - Finding with evidence
15
+ * @param {Object} options - { engineVersion }
16
+ * @returns {{ record: Object, digest: Object, envelope: Object }}
17
+ */
18
+ function emitProvenance(finding, options = {}) {
19
+ const engineVersion = options.engineVersion || "0.1.0";
20
+ const timestamp = options.timestamp || new Date().toISOString();
21
+
22
+ // 1. Evidence extraction record
23
+ const record = {
24
+ "prov.record.v0.1": {
25
+ method_id: "engine.extract.evidence.json_pointer",
26
+ timestamp,
27
+ inputs: [
28
+ {
29
+ "artifact.v0.1": {
30
+ name: "source_document",
31
+ uri: `file://${finding.location.file}`,
32
+ },
33
+ },
34
+ ],
35
+ outputs: [
36
+ {
37
+ "artifact.v0.1": {
38
+ name: "evidence",
39
+ content: {
40
+ document_ref: finding.location.file,
41
+ pointer: finding.location.json_pointer,
42
+ evidence: finding.evidence,
43
+ },
44
+ },
45
+ },
46
+ ],
47
+ agent: {
48
+ name: "a11y-evidence-engine",
49
+ version: engineVersion,
50
+ },
51
+ },
52
+ };
53
+
54
+ // 2. Integrity digest of canonical evidence
55
+ const evidenceContent = record["prov.record.v0.1"].outputs[0]["artifact.v0.1"].content;
56
+ const canonicalEvidence = canonicalize(evidenceContent);
57
+ const evidenceDigest = crypto
58
+ .createHash("sha256")
59
+ .update(canonicalEvidence, "utf8")
60
+ .digest("hex");
61
+
62
+ const digest = {
63
+ "prov.record.v0.1": {
64
+ method_id: "integrity.digest.sha256",
65
+ timestamp,
66
+ inputs: [
67
+ {
68
+ "artifact.v0.1": {
69
+ name: "evidence",
70
+ content: evidenceContent,
71
+ },
72
+ },
73
+ ],
74
+ outputs: [
75
+ {
76
+ "artifact.v0.1": {
77
+ name: "digest",
78
+ digest: {
79
+ algorithm: "sha256",
80
+ value: evidenceDigest,
81
+ },
82
+ },
83
+ },
84
+ ],
85
+ agent: {
86
+ name: "a11y-evidence-engine",
87
+ version: engineVersion,
88
+ },
89
+ },
90
+ };
91
+
92
+ // 3. Envelope wrapping the evidence record
93
+ const envelope = {
94
+ "mcp.envelope.v0.1": {
95
+ result: {
96
+ finding_id: finding.finding_id,
97
+ rule_id: finding.rule_id,
98
+ severity: finding.severity,
99
+ message: finding.message,
100
+ location: finding.location,
101
+ },
102
+ provenance: {
103
+ "prov.record.v0.1": {
104
+ method_id: "adapter.wrap.envelope_v0_1",
105
+ timestamp,
106
+ inputs: [
107
+ {
108
+ "artifact.v0.1": {
109
+ name: "evidence_record",
110
+ digest: {
111
+ algorithm: "sha256",
112
+ value: evidenceDigest,
113
+ },
114
+ },
115
+ },
116
+ ],
117
+ outputs: [
118
+ {
119
+ "artifact.v0.1": {
120
+ name: "envelope",
121
+ content_type: "mcp.envelope.v0.1",
122
+ },
123
+ },
124
+ ],
125
+ agent: {
126
+ name: "a11y-evidence-engine",
127
+ version: engineVersion,
128
+ },
129
+ },
130
+ },
131
+ },
132
+ };
133
+
134
+ return { record, digest, envelope };
135
+ }
136
+
137
+ /**
138
+ * Verify a digest matches the canonical evidence.
139
+ *
140
+ * @param {Object} evidence - Evidence object
141
+ * @param {string} expectedDigest - Expected SHA-256 hex digest
142
+ * @returns {boolean}
143
+ */
144
+ function verifyDigest(evidence, expectedDigest) {
145
+ const canonical = canonicalize(evidence);
146
+ const actualDigest = crypto
147
+ .createHash("sha256")
148
+ .update(canonical, "utf8")
149
+ .digest("hex");
150
+ return actualDigest === expectedDigest;
151
+ }
152
+
153
+ module.exports = { emitProvenance, verifyDigest, canonicalize };
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ /**
7
+ * Gather all .html files from a path.
8
+ * If path is a file, returns [path].
9
+ * If path is a directory, recursively finds all .html files.
10
+ *
11
+ * @param {string} targetPath - File or directory path
12
+ * @returns {string[]} Array of absolute file paths, sorted for determinism
13
+ */
14
+ function walkHtmlFiles(targetPath) {
15
+ const resolved = path.resolve(targetPath);
16
+
17
+ if (!fs.existsSync(resolved)) {
18
+ throw new Error(`Path does not exist: ${resolved}`);
19
+ }
20
+
21
+ const stat = fs.statSync(resolved);
22
+
23
+ if (stat.isFile()) {
24
+ if (resolved.endsWith(".html") || resolved.endsWith(".htm")) {
25
+ return [resolved];
26
+ }
27
+ throw new Error(`Not an HTML file: ${resolved}`);
28
+ }
29
+
30
+ if (stat.isDirectory()) {
31
+ const files = [];
32
+ walkDir(resolved, files);
33
+ // Sort for deterministic ordering
34
+ return files.sort();
35
+ }
36
+
37
+ throw new Error(`Invalid path type: ${resolved}`);
38
+ }
39
+
40
+ function walkDir(dir, files) {
41
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
42
+
43
+ for (const entry of entries) {
44
+ const fullPath = path.join(dir, entry.name);
45
+
46
+ if (entry.isDirectory()) {
47
+ walkDir(fullPath, files);
48
+ } else if (entry.isFile()) {
49
+ if (entry.name.endsWith(".html") || entry.name.endsWith(".htm")) {
50
+ files.push(fullPath);
51
+ }
52
+ }
53
+ }
54
+ }
55
+
56
+ module.exports = { walkHtmlFiles };