@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,457 @@
1
+ """Integration tests for Profile Guard through CLI pipeline.
2
+
3
+ Tests that guard validation works correctly when invoked through
4
+ the actual CLI commands with all three profiles.
5
+ """
6
+
7
+ import json
8
+ import tempfile
9
+ from pathlib import Path
10
+
11
+ import pytest
12
+ from click.testing import CliRunner
13
+
14
+ from a11y_assist.cli import main
15
+
16
+
17
+ @pytest.fixture
18
+ def runner() -> CliRunner:
19
+ """CLI test runner."""
20
+ return CliRunner()
21
+
22
+
23
+ @pytest.fixture
24
+ def valid_cli_error_json() -> dict:
25
+ """Valid cli.error.v0.1 JSON for testing."""
26
+ return {
27
+ "level": "ERROR",
28
+ "code": "CFG001",
29
+ "id": "TEST.CONFIG.MISSING",
30
+ "title": "Configuration file not found",
31
+ "what": "The config.yaml file is missing from the current directory.",
32
+ "why": "The tool requires a configuration file to run.",
33
+ "fix": [
34
+ "Run: config init --dry-run",
35
+ "Then: config init",
36
+ "Verify the file was created.",
37
+ ],
38
+ }
39
+
40
+
41
+ @pytest.fixture
42
+ def json_file(valid_cli_error_json: dict) -> str:
43
+ """Create a temporary JSON file with valid cli.error.v0.1 content."""
44
+ with tempfile.NamedTemporaryFile(
45
+ mode="w", suffix=".json", delete=False
46
+ ) as f:
47
+ json.dump(valid_cli_error_json, f)
48
+ return f.name
49
+
50
+
51
+ # Integration: explain command with guard
52
+
53
+
54
+ def test_explain_lowvision_passes_guard(runner: CliRunner, json_file: str):
55
+ """Explain command with lowvision profile should pass guard."""
56
+ result = runner.invoke(main, ["explain", "--json", json_file, "--profile", "lowvision"])
57
+
58
+ # Should succeed
59
+ assert result.exit_code == 0
60
+ assert "ASSIST (Low Vision)" in result.output
61
+ assert "TEST.CONFIG.MISSING" in result.output
62
+
63
+
64
+ def test_explain_cognitive_load_passes_guard(runner: CliRunner, json_file: str):
65
+ """Explain command with cognitive-load profile should pass guard."""
66
+ result = runner.invoke(main, ["explain", "--json", json_file, "--profile", "cognitive-load"])
67
+
68
+ # Should succeed
69
+ assert result.exit_code == 0
70
+ assert "ASSIST (Cognitive Load)" in result.output
71
+ assert "TEST.CONFIG.MISSING" in result.output
72
+
73
+
74
+ def test_explain_screen_reader_passes_guard(runner: CliRunner, json_file: str):
75
+ """Explain command with screen-reader profile should pass guard."""
76
+ result = runner.invoke(main, ["explain", "--json", json_file, "--profile", "screen-reader"])
77
+
78
+ # Should succeed
79
+ assert result.exit_code == 0
80
+ assert "ASSIST. Profile: Screen reader." in result.output
81
+ assert "TEST.CONFIG.MISSING" in result.output
82
+
83
+
84
+ # Integration: triage command with guard
85
+
86
+
87
+ def test_triage_lowvision_passes_guard(runner: CliRunner):
88
+ """Triage command with lowvision profile should pass guard."""
89
+ input_text = """[ERROR] (ID: TOOL.PARSE.FAIL)
90
+ What: Failed to parse input file.
91
+ Why: Invalid syntax on line 42.
92
+ Fix:
93
+ Check line 42 for typos.
94
+ Run: tool validate --dry-run
95
+ """
96
+ result = runner.invoke(
97
+ main, ["triage", "--stdin", "--profile", "lowvision"], input=input_text
98
+ )
99
+
100
+ # Should succeed
101
+ assert result.exit_code == 0
102
+ assert "ASSIST (Low Vision)" in result.output
103
+ assert "TOOL.PARSE.FAIL" in result.output
104
+
105
+
106
+ def test_triage_cognitive_load_passes_guard(runner: CliRunner):
107
+ """Triage command with cognitive-load profile should pass guard."""
108
+ input_text = """[ERROR] (ID: TOOL.PARSE.FAIL)
109
+ What: Failed to parse input file.
110
+ Why: Invalid syntax on line 42.
111
+ Fix:
112
+ Check line 42 for typos.
113
+ Run: tool validate --dry-run
114
+ """
115
+ result = runner.invoke(
116
+ main, ["triage", "--stdin", "--profile", "cognitive-load"], input=input_text
117
+ )
118
+
119
+ # Should succeed
120
+ assert result.exit_code == 0
121
+ assert "ASSIST (Cognitive Load)" in result.output
122
+
123
+
124
+ def test_triage_screen_reader_passes_guard(runner: CliRunner):
125
+ """Triage command with screen-reader profile should pass guard."""
126
+ input_text = """[ERROR] (ID: TOOL.PARSE.FAIL)
127
+ What: Failed to parse input file.
128
+ Why: Invalid syntax on line 42.
129
+ Fix:
130
+ Check line 42 for typos.
131
+ Run: tool validate --dry-run
132
+ """
133
+ result = runner.invoke(
134
+ main, ["triage", "--stdin", "--profile", "screen-reader"], input=input_text
135
+ )
136
+
137
+ # Should succeed
138
+ assert result.exit_code == 0
139
+ assert "ASSIST. Profile: Screen reader." in result.output
140
+
141
+
142
+ # Integration: triage with no ID (Low confidence)
143
+
144
+
145
+ def test_triage_low_confidence_lowvision(runner: CliRunner):
146
+ """Triage with no ID should produce Low confidence output."""
147
+ input_text = """Error: Something went wrong.
148
+ Please check the configuration and try again.
149
+ """
150
+ result = runner.invoke(
151
+ main, ["triage", "--stdin", "--profile", "lowvision"], input=input_text
152
+ )
153
+
154
+ # Should succeed with Low confidence
155
+ assert result.exit_code == 0
156
+ assert "Confidence: Low" in result.output
157
+ assert "No (ID: ...) found" in result.output
158
+
159
+
160
+ def test_triage_low_confidence_cognitive_load(runner: CliRunner):
161
+ """Triage with no ID should produce Low confidence output for cognitive-load."""
162
+ input_text = """Error: Something went wrong.
163
+ Please check the configuration and try again.
164
+ """
165
+ result = runner.invoke(
166
+ main, ["triage", "--stdin", "--profile", "cognitive-load"], input=input_text
167
+ )
168
+
169
+ # Should succeed with Low confidence
170
+ assert result.exit_code == 0
171
+ assert "Confidence: Low" in result.output
172
+
173
+
174
+ def test_triage_low_confidence_screen_reader(runner: CliRunner):
175
+ """Triage with no ID should produce Low confidence output for screen-reader."""
176
+ input_text = """Error: Something went wrong.
177
+ Please check the configuration and try again.
178
+ """
179
+ result = runner.invoke(
180
+ main, ["triage", "--stdin", "--profile", "screen-reader"], input=input_text
181
+ )
182
+
183
+ # Should succeed with Low confidence
184
+ assert result.exit_code == 0
185
+ assert "Confidence: Low" in result.output
186
+
187
+
188
+ # Integration: explain with invalid JSON
189
+
190
+
191
+ def test_explain_invalid_json_lowvision(runner: CliRunner):
192
+ """Explain with invalid JSON should produce Low confidence validation error."""
193
+ # Create invalid JSON (missing required fields)
194
+ invalid_json = {"level": "ERROR", "code": "TST001"} # Missing what, why, fix
195
+
196
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
197
+ json.dump(invalid_json, f)
198
+ json_path = f.name
199
+
200
+ result = runner.invoke(main, ["explain", "--json", json_path, "--profile", "lowvision"])
201
+
202
+ # Should exit with code 2 (validation error)
203
+ assert result.exit_code == 2
204
+ assert "Confidence: Low" in result.output
205
+
206
+
207
+ def test_explain_invalid_json_cognitive_load(runner: CliRunner):
208
+ """Explain with invalid JSON should work with cognitive-load profile."""
209
+ invalid_json = {"level": "ERROR", "code": "TST001"} # Missing what, why, fix
210
+
211
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
212
+ json.dump(invalid_json, f)
213
+ json_path = f.name
214
+
215
+ result = runner.invoke(main, ["explain", "--json", json_path, "--profile", "cognitive-load"])
216
+
217
+ # Should exit with code 2 (validation error)
218
+ assert result.exit_code == 2
219
+ assert "Confidence: Low" in result.output
220
+
221
+
222
+ def test_explain_invalid_json_screen_reader(runner: CliRunner):
223
+ """Explain with invalid JSON should work with screen-reader profile."""
224
+ invalid_json = {"level": "ERROR", "code": "TST001"} # Missing what, why, fix
225
+
226
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
227
+ json.dump(invalid_json, f)
228
+ json_path = f.name
229
+
230
+ result = runner.invoke(main, ["explain", "--json", json_path, "--profile", "screen-reader"])
231
+
232
+ # Should exit with code 2 (validation error)
233
+ assert result.exit_code == 2
234
+ assert "Confidence: Low" in result.output
235
+
236
+
237
+ # Integration: Verify guard catches profile bugs (simulated)
238
+
239
+
240
+ def test_guard_error_format_on_violation():
241
+ """Guard violations should produce structured error output.
242
+
243
+ This test documents the expected error format. In practice,
244
+ guard violations indicate bugs in profile transforms, not
245
+ user errors. This test ensures the error format is correct
246
+ if such a bug were to occur.
247
+ """
248
+ # The guard error format should include:
249
+ # - [ERROR] A11Y.ASSIST.ENGINE.GUARD.FAIL
250
+ # - What: description
251
+ # - Why: explanation
252
+ # - Fix: instructions
253
+ # - Guard codes: list of violations
254
+
255
+ # Since we can't easily trigger a guard violation without
256
+ # introducing a bug, we just document the expected format
257
+ # in this test. Actual guard violation tests are in test_guard.py.
258
+ pass
259
+
260
+
261
+ # Integration: Verify max steps enforced per profile
262
+
263
+
264
+ def test_cognitive_load_max_3_steps_integration(runner: CliRunner):
265
+ """Cognitive-load profile should enforce max 3 steps."""
266
+ # Create JSON with many fix steps
267
+ json_data = {
268
+ "level": "ERROR",
269
+ "code": "TST001",
270
+ "id": "TEST.MANY.STEPS",
271
+ "title": "Error with many steps",
272
+ "what": "Something went wrong.",
273
+ "why": "Multiple issues need fixing.",
274
+ "fix": [
275
+ "Step 1: Do first thing.",
276
+ "Step 2: Do second thing.",
277
+ "Step 3: Do third thing.",
278
+ "Step 4: Do fourth thing.",
279
+ "Step 5: Do fifth thing.",
280
+ ],
281
+ }
282
+
283
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
284
+ json.dump(json_data, f)
285
+ json_path = f.name
286
+
287
+ result = runner.invoke(main, ["explain", "--json", json_path, "--profile", "cognitive-load"])
288
+
289
+ # Should succeed - profile should truncate to 3 steps
290
+ assert result.exit_code == 0
291
+ # Count "First:", "Next:", "Last:" labels (max 3)
292
+ assert result.output.count("First:") <= 1
293
+ assert result.output.count("Last:") <= 1
294
+
295
+
296
+ def test_lowvision_within_5_steps_succeeds(runner: CliRunner):
297
+ """Lowvision profile should succeed when steps are within limit."""
298
+ json_data = {
299
+ "level": "ERROR",
300
+ "code": "TST002",
301
+ "id": "TEST.FEW.STEPS",
302
+ "title": "Error with few steps",
303
+ "what": "Something went wrong.",
304
+ "why": "A few issues need fixing.",
305
+ "fix": [
306
+ "Step 1",
307
+ "Step 2",
308
+ "Step 3",
309
+ "Step 4",
310
+ "Step 5",
311
+ ],
312
+ }
313
+
314
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
315
+ json.dump(json_data, f)
316
+ json_path = f.name
317
+
318
+ result = runner.invoke(main, ["explain", "--json", json_path, "--profile", "lowvision"])
319
+
320
+ # Should succeed - 5 steps is within limit
321
+ assert result.exit_code == 0
322
+ assert "ASSIST (Low Vision)" in result.output
323
+
324
+
325
+ # Integration: Screen-reader abbreviation expansion
326
+
327
+
328
+ def test_screen_reader_expands_cli_abbreviation(runner: CliRunner):
329
+ """Screen-reader profile should expand CLI abbreviation."""
330
+ json_data = {
331
+ "level": "ERROR",
332
+ "code": "CLI001",
333
+ "id": "TEST.CLI.ERROR",
334
+ "title": "CLI tool failed",
335
+ "what": "The CLI command failed to execute.",
336
+ "why": "Invalid CLI arguments provided.",
337
+ "fix": ["Check CLI documentation.", "Run: cli --help"],
338
+ }
339
+
340
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
341
+ json.dump(json_data, f)
342
+ json_path = f.name
343
+
344
+ result = runner.invoke(main, ["explain", "--json", json_path, "--profile", "screen-reader"])
345
+
346
+ assert result.exit_code == 0
347
+ # CLI should be expanded to "command line" in output
348
+ assert "command line" in result.output.lower() or "C L I" in result.output
349
+
350
+
351
+ def test_screen_reader_expands_json_abbreviation(runner: CliRunner):
352
+ """Screen-reader profile should expand JSON abbreviation."""
353
+ json_data = {
354
+ "level": "ERROR",
355
+ "code": "JSN001",
356
+ "id": "TEST.JSON.ERROR",
357
+ "title": "JSON parse error",
358
+ "what": "The JSON file is malformed.",
359
+ "why": "Invalid JSON syntax.",
360
+ "fix": ["Validate your JSON file.", "Check for missing commas."],
361
+ }
362
+
363
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
364
+ json.dump(json_data, f)
365
+ json_path = f.name
366
+
367
+ result = runner.invoke(main, ["explain", "--json", json_path, "--profile", "screen-reader"])
368
+
369
+ assert result.exit_code == 0
370
+ # JSON should be expanded to "J S O N" in output
371
+ assert "J S O N" in result.output
372
+
373
+
374
+ # Integration: Verify SAFE commands pass through correctly
375
+
376
+
377
+ def test_safe_commands_preserved_lowvision(runner: CliRunner):
378
+ """SAFE commands should be preserved in lowvision output."""
379
+ json_data = {
380
+ "level": "ERROR",
381
+ "code": "TST003",
382
+ "id": "TEST.SAFE.CMD",
383
+ "title": "Error with safe command",
384
+ "what": "Operation failed.",
385
+ "why": "Need to retry.",
386
+ "fix": ["Run: tool fix --dry-run"],
387
+ }
388
+
389
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
390
+ json.dump(json_data, f)
391
+ json_path = f.name
392
+
393
+ result = runner.invoke(main, ["explain", "--json", json_path, "--profile", "lowvision"])
394
+
395
+ assert result.exit_code == 0
396
+ assert "tool fix --dry-run" in result.output
397
+ assert "Next (SAFE):" in result.output
398
+
399
+
400
+ def test_safe_commands_preserved_cognitive_load(runner: CliRunner):
401
+ """SAFE commands should be preserved in cognitive-load output (max 1)."""
402
+ json_data = {
403
+ "level": "ERROR",
404
+ "code": "TST004",
405
+ "id": "TEST.SAFE.CMD",
406
+ "title": "Error with safe command",
407
+ "what": "Operation failed.",
408
+ "why": "Need to retry.",
409
+ "fix": [
410
+ "Run: tool fix --dry-run",
411
+ "Run: tool check --dry-run",
412
+ ],
413
+ }
414
+
415
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
416
+ json.dump(json_data, f)
417
+ json_path = f.name
418
+
419
+ result = runner.invoke(main, ["explain", "--json", json_path, "--profile", "cognitive-load"])
420
+
421
+ assert result.exit_code == 0
422
+ # Should have at most one SAFE command
423
+ assert "Next (SAFE):" in result.output
424
+
425
+
426
+ def test_safe_commands_preserved_screen_reader(runner: CliRunner):
427
+ """SAFE commands should be preserved in screen-reader output."""
428
+ json_data = {
429
+ "level": "ERROR",
430
+ "code": "TST005",
431
+ "id": "TEST.SAFE.CMD",
432
+ "title": "Error with safe command",
433
+ "what": "Operation failed.",
434
+ "why": "Need to retry.",
435
+ "fix": ["Run: tool fix --dry-run"],
436
+ }
437
+
438
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
439
+ json.dump(json_data, f)
440
+ json_path = f.name
441
+
442
+ result = runner.invoke(main, ["explain", "--json", json_path, "--profile", "screen-reader"])
443
+
444
+ assert result.exit_code == 0
445
+ assert "tool fix --dry-run" in result.output
446
+ assert "Next safe command" in result.output
447
+
448
+
449
+ # Integration: Default profile is lowvision
450
+
451
+
452
+ def test_default_profile_is_lowvision(runner: CliRunner, json_file: str):
453
+ """Default profile should be lowvision when --profile not specified."""
454
+ result = runner.invoke(main, ["explain", "--json", json_file])
455
+
456
+ assert result.exit_code == 0
457
+ assert "ASSIST (Low Vision)" in result.output