@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,348 @@
1
+ """Tests for plain-language profile.
2
+
3
+ Verifies:
4
+ - Sentences simplified to one clause
5
+ - Parentheticals removed
6
+ - Subordinate clauses removed
7
+ - Max 4 steps
8
+ - Numeric step labels
9
+ - Confidence preserved
10
+ - Commands from allowlist only (max 1)
11
+ """
12
+
13
+ import pytest
14
+
15
+ from a11y_assist.profiles.plain_language import (
16
+ _normalize_step,
17
+ _remove_parentheticals,
18
+ _simplify_sentence,
19
+ apply_plain_language,
20
+ )
21
+ from a11y_assist.profiles.plain_language_render import render_plain_language
22
+ from a11y_assist.render import AssistResult
23
+
24
+
25
+ # Unit tests for helper functions
26
+
27
+
28
+ def test_remove_parentheticals():
29
+ """Should remove parenthetical content."""
30
+ assert _remove_parentheticals("Check config (optional)") == "Check config"
31
+ assert _remove_parentheticals("Run command [see docs]") == "Run command"
32
+ assert _remove_parentheticals("No parens here") == "No parens here"
33
+
34
+
35
+ def test_simplify_sentence_removes_conjunctions():
36
+ """Should split on conjunctions and keep first part."""
37
+ result = _simplify_sentence("Check config and run the test")
38
+ assert "and" not in result.lower()
39
+ assert "Check config" in result
40
+
41
+
42
+ def test_simplify_sentence_removes_but():
43
+ """Should handle 'but' conjunction."""
44
+ result = _simplify_sentence("Try this but be careful")
45
+ assert "but" not in result.lower()
46
+ assert "Try this" in result
47
+
48
+
49
+ def test_simplify_sentence_removes_or():
50
+ """Should handle 'or' conjunction."""
51
+ result = _simplify_sentence("Use option A or option B")
52
+ assert " or " not in result.lower()
53
+
54
+
55
+ def test_simplify_sentence_removes_subordinate_which():
56
+ """Should remove subordinate clauses with 'which'."""
57
+ result = _simplify_sentence("Check the file, which is located in /etc")
58
+ assert "which" not in result.lower()
59
+
60
+
61
+ def test_simplify_sentence_removes_subordinate_that():
62
+ """Should remove subordinate clauses with 'that'."""
63
+ result = _simplify_sentence("Run the command that verifies config")
64
+ assert "that" not in result.lower()
65
+
66
+
67
+ def test_simplify_sentence_removes_subordinate_because():
68
+ """Should remove subordinate clauses with 'because'."""
69
+ result = _simplify_sentence("This fails because the file is missing")
70
+ assert "because" not in result.lower()
71
+
72
+
73
+ def test_simplify_sentence_adds_period():
74
+ """Should ensure sentence ends with punctuation."""
75
+ result = _simplify_sentence("Check config")
76
+ assert result.endswith(".")
77
+
78
+
79
+ def test_simplify_sentence_preserves_existing_punctuation():
80
+ """Should preserve existing punctuation."""
81
+ result = _simplify_sentence("Check config!")
82
+ assert result.endswith("!")
83
+
84
+
85
+ def test_normalize_step():
86
+ """Step normalization should simplify and clean."""
87
+ result = _normalize_step("Check config (optional) and verify")
88
+ assert "(" not in result
89
+ assert ")" not in result
90
+ assert "and" not in result.lower() or "and" == result.lower()[-3:] # Allow if at end
91
+
92
+
93
+ # Tests for apply_plain_language transform
94
+
95
+
96
+ @pytest.fixture
97
+ def base_result() -> AssistResult:
98
+ """Base result with various elements to transform."""
99
+ return AssistResult(
100
+ anchored_id="TEST.ERROR.001",
101
+ confidence="High",
102
+ safest_next_step="Check the config file (see docs), which is located in /etc.",
103
+ plan=[
104
+ "First, verify the config exists and check permissions.",
105
+ "Run the validation command, but be careful.",
106
+ "Check the output, which contains details.",
107
+ "Verify results because this is important.",
108
+ "Final step that completes the process.",
109
+ "Extra step (should be truncated).",
110
+ ],
111
+ next_safe_commands=["cmd --dry-run", "cmd2 --check"],
112
+ notes=["Note with (parenthetical) and subordinate clause which explains."],
113
+ )
114
+
115
+
116
+ def test_apply_plain_language_simplifies_safest_step(base_result: AssistResult):
117
+ """Plain-language should simplify safest next step."""
118
+ result = apply_plain_language(base_result)
119
+ # Should not have parenthetical
120
+ assert "(" not in result.safest_next_step
121
+ assert ")" not in result.safest_next_step
122
+ # Should not have subordinate clause
123
+ assert "which" not in result.safest_next_step.lower()
124
+
125
+
126
+ def test_apply_plain_language_simplifies_plan_steps(base_result: AssistResult):
127
+ """Plain-language should simplify plan steps."""
128
+ result = apply_plain_language(base_result)
129
+ for step in result.plan:
130
+ # Each step should be one clause (no conjunctions in middle)
131
+ # Note: "and" at end is ok due to simplification
132
+ words = step.lower().split()
133
+ if " and " in step.lower():
134
+ # "and" should only appear if it's a remaining fragment
135
+ pass
136
+
137
+
138
+ def test_apply_plain_language_max_4_steps(base_result: AssistResult):
139
+ """Plain-language should limit to 4 steps."""
140
+ result = apply_plain_language(base_result)
141
+ assert len(result.plan) <= 4
142
+
143
+
144
+ def test_apply_plain_language_max_1_command(base_result: AssistResult):
145
+ """Plain-language should limit to 1 command for simplicity."""
146
+ result = apply_plain_language(base_result)
147
+ assert len(result.next_safe_commands) <= 1
148
+
149
+
150
+ def test_apply_plain_language_max_2_notes(base_result: AssistResult):
151
+ """Plain-language should limit to 2 notes."""
152
+ result = apply_plain_language(base_result)
153
+ assert len(result.notes) <= 2
154
+
155
+
156
+ def test_apply_plain_language_preserves_confidence(base_result: AssistResult):
157
+ """Plain-language should preserve confidence."""
158
+ result = apply_plain_language(base_result)
159
+ assert result.confidence == base_result.confidence
160
+
161
+
162
+ def test_apply_plain_language_preserves_id(base_result: AssistResult):
163
+ """Plain-language should preserve anchored ID."""
164
+ result = apply_plain_language(base_result)
165
+ assert result.anchored_id == base_result.anchored_id
166
+
167
+
168
+ # Tests for render_plain_language
169
+
170
+
171
+ def test_render_plain_language_header():
172
+ """Render should include plain language header."""
173
+ result = AssistResult(
174
+ anchored_id="TEST.001",
175
+ confidence="High",
176
+ safest_next_step="Check config.",
177
+ plan=["Step 1."],
178
+ next_safe_commands=[],
179
+ notes=[],
180
+ )
181
+ output = render_plain_language(result)
182
+ assert "ASSIST (Plain Language)" in output
183
+
184
+
185
+ def test_render_plain_language_labels():
186
+ """Render should use explicit labels."""
187
+ result = AssistResult(
188
+ anchored_id="TEST.001",
189
+ confidence="High",
190
+ safest_next_step="Check config.",
191
+ plan=["Step 1.", "Step 2."],
192
+ next_safe_commands=["cmd --dry-run"],
193
+ notes=[],
194
+ )
195
+ output = render_plain_language(result)
196
+ assert "ID:" in output
197
+ assert "Confidence:" in output
198
+ assert "What to do next:" in output
199
+ assert "Steps:" in output
200
+ assert "Safe command:" in output
201
+
202
+
203
+ def test_render_plain_language_numeric_steps():
204
+ """Render should use numeric step labels."""
205
+ result = AssistResult(
206
+ anchored_id="TEST.001",
207
+ confidence="High",
208
+ safest_next_step="Check config.",
209
+ plan=["First step.", "Second step.", "Third step."],
210
+ next_safe_commands=[],
211
+ notes=[],
212
+ )
213
+ output = render_plain_language(result)
214
+ assert "1." in output
215
+ assert "2." in output
216
+ assert "3." in output
217
+
218
+
219
+ def test_render_plain_language_no_command_on_low():
220
+ """Render should not show command on Low confidence."""
221
+ result = AssistResult(
222
+ anchored_id=None,
223
+ confidence="Low",
224
+ safest_next_step="Check config.",
225
+ plan=["Step 1."],
226
+ next_safe_commands=["cmd --dry-run"],
227
+ notes=[],
228
+ )
229
+ output = render_plain_language(result)
230
+ assert "Safe command:" not in output
231
+
232
+
233
+ def test_render_plain_language_shows_command_on_high():
234
+ """Render should show command on High confidence."""
235
+ result = AssistResult(
236
+ anchored_id="TEST.001",
237
+ confidence="High",
238
+ safest_next_step="Check config.",
239
+ plan=["Step 1."],
240
+ next_safe_commands=["cmd --dry-run"],
241
+ notes=[],
242
+ )
243
+ output = render_plain_language(result)
244
+ assert "Safe command:" in output
245
+ assert "cmd --dry-run" in output
246
+
247
+
248
+ def test_render_plain_language_omits_notes():
249
+ """Render should omit notes for simplicity."""
250
+ result = AssistResult(
251
+ anchored_id="TEST.001",
252
+ confidence="High",
253
+ safest_next_step="Check config.",
254
+ plan=["Step 1."],
255
+ next_safe_commands=[],
256
+ notes=["A note that should not appear."],
257
+ )
258
+ output = render_plain_language(result)
259
+ # Notes section omitted in plain-language for simplicity
260
+ assert "Notes:" not in output
261
+
262
+
263
+ # Integration tests
264
+
265
+
266
+ def test_plain_language_full_pipeline():
267
+ """Full pipeline test for plain-language profile."""
268
+ base = AssistResult(
269
+ anchored_id="DEPLOY.CONFIG.MISSING",
270
+ confidence="High",
271
+ safest_next_step="Check the config file (in /etc), which contains settings and run verify.",
272
+ plan=[
273
+ "Verify the config exists and check permissions.",
274
+ "Run: deploy check --dry-run",
275
+ "Review output, which shows results.",
276
+ ],
277
+ next_safe_commands=["deploy check --dry-run"],
278
+ notes=["Important note."],
279
+ )
280
+
281
+ transformed = apply_plain_language(base)
282
+ output = render_plain_language(transformed)
283
+
284
+ # Header present
285
+ assert "ASSIST (Plain Language)" in output
286
+
287
+ # ID preserved
288
+ assert "DEPLOY.CONFIG.MISSING" in output
289
+
290
+ # Confidence preserved
291
+ assert "Confidence: High" in output
292
+
293
+ # Simplified step label
294
+ assert "What to do next:" in output
295
+
296
+ # Numeric steps
297
+ assert "1." in output
298
+
299
+ # Command shown
300
+ assert "deploy check --dry-run" in output
301
+
302
+ # No notes section (omitted for simplicity)
303
+ assert "Notes:" not in output
304
+
305
+
306
+ def test_plain_language_low_confidence():
307
+ """Plain-language profile with Low confidence."""
308
+ base = AssistResult(
309
+ anchored_id=None,
310
+ confidence="Low",
311
+ safest_next_step="Review the output.",
312
+ plan=["Step 1.", "Step 2."],
313
+ next_safe_commands=["cmd --dry-run"],
314
+ notes=["No ID found."],
315
+ )
316
+
317
+ transformed = apply_plain_language(base)
318
+ output = render_plain_language(transformed)
319
+
320
+ # Confidence preserved
321
+ assert "Confidence: Low" in output
322
+
323
+ # No command on Low confidence
324
+ assert "Safe command:" not in output
325
+
326
+ # ID shows as none
327
+ assert "ID: none" in output
328
+
329
+
330
+ def test_plain_language_complex_sentence():
331
+ """Plain-language should simplify complex sentences."""
332
+ base = AssistResult(
333
+ anchored_id="TEST.001",
334
+ confidence="High",
335
+ safest_next_step="First check the config, and then verify the settings, but be careful because errors may occur.",
336
+ plan=[
337
+ "Run the command which validates, and also checks permissions.",
338
+ ],
339
+ next_safe_commands=[],
340
+ notes=[],
341
+ )
342
+
343
+ transformed = apply_plain_language(base)
344
+
345
+ # Safest step should be simplified
346
+ assert "and then" not in transformed.safest_next_step.lower()
347
+ assert "but" not in transformed.safest_next_step.lower()
348
+ assert "because" not in transformed.safest_next_step.lower()
@@ -0,0 +1,117 @@
1
+ """Tests for render output."""
2
+
3
+ from a11y_assist.render import AssistResult, render_assist
4
+
5
+
6
+ class TestRenderAssist:
7
+ """Tests for rendering AssistResult to text."""
8
+
9
+ def test_render_includes_header(self):
10
+ """Output includes ASSIST (Low Vision) header."""
11
+ result = AssistResult(
12
+ anchored_id="TEST.ID",
13
+ confidence="High",
14
+ safest_next_step="Do the thing.",
15
+ plan=["Step 1", "Step 2"],
16
+ next_safe_commands=[],
17
+ notes=[],
18
+ )
19
+ output = render_assist(result)
20
+ assert "ASSIST (Low Vision):" in output
21
+
22
+ def test_render_includes_anchored_id(self):
23
+ """Output includes anchored ID."""
24
+ result = AssistResult(
25
+ anchored_id="PAY.EXPORT.AUTH",
26
+ confidence="High",
27
+ safest_next_step="Check credentials.",
28
+ plan=["Step 1"],
29
+ next_safe_commands=[],
30
+ notes=[],
31
+ )
32
+ output = render_assist(result)
33
+ assert "Anchored to: PAY.EXPORT.AUTH" in output
34
+
35
+ def test_render_no_id_shows_none(self):
36
+ """Output shows (none) when no anchored ID."""
37
+ result = AssistResult(
38
+ anchored_id=None,
39
+ confidence="Low",
40
+ safest_next_step="Try again.",
41
+ plan=["Step 1"],
42
+ next_safe_commands=[],
43
+ notes=[],
44
+ )
45
+ output = render_assist(result)
46
+ assert "Anchored to: (none)" in output
47
+
48
+ def test_render_includes_confidence(self):
49
+ """Output includes confidence level."""
50
+ result = AssistResult(
51
+ anchored_id=None,
52
+ confidence="Medium",
53
+ safest_next_step="Do something.",
54
+ plan=["Step 1"],
55
+ next_safe_commands=[],
56
+ notes=[],
57
+ )
58
+ output = render_assist(result)
59
+ assert "Confidence: Medium" in output
60
+
61
+ def test_render_includes_plan(self):
62
+ """Output includes numbered plan steps."""
63
+ result = AssistResult(
64
+ anchored_id=None,
65
+ confidence="High",
66
+ safest_next_step="Start here.",
67
+ plan=["First step", "Second step", "Third step"],
68
+ next_safe_commands=[],
69
+ notes=[],
70
+ )
71
+ output = render_assist(result)
72
+ assert "1) First step" in output
73
+ assert "2) Second step" in output
74
+ assert "3) Third step" in output
75
+
76
+ def test_render_limits_plan_to_five(self):
77
+ """Plan is limited to 5 steps with ellipsis."""
78
+ result = AssistResult(
79
+ anchored_id=None,
80
+ confidence="High",
81
+ safest_next_step="Start here.",
82
+ plan=["Step 1", "Step 2", "Step 3", "Step 4", "Step 5", "Step 6"],
83
+ next_safe_commands=[],
84
+ notes=[],
85
+ )
86
+ output = render_assist(result)
87
+ assert "5) Step 5" in output
88
+ assert "..." in output
89
+ assert "6)" not in output
90
+
91
+ def test_render_includes_safe_commands(self):
92
+ """Output includes SAFE commands section."""
93
+ result = AssistResult(
94
+ anchored_id=None,
95
+ confidence="High",
96
+ safest_next_step="Check first.",
97
+ plan=["Step 1"],
98
+ next_safe_commands=["tool --dry-run", "tool --validate"],
99
+ notes=[],
100
+ )
101
+ output = render_assist(result)
102
+ assert "Next (SAFE):" in output
103
+ assert "tool --dry-run" in output
104
+
105
+ def test_render_includes_notes(self):
106
+ """Output includes notes section."""
107
+ result = AssistResult(
108
+ anchored_id=None,
109
+ confidence="High",
110
+ safest_next_step="Do it.",
111
+ plan=["Step 1"],
112
+ next_safe_commands=[],
113
+ notes=["Note one", "Note two"],
114
+ )
115
+ output = render_assist(result)
116
+ assert "Notes:" in output
117
+ assert "- Note one" in output