@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,245 @@
1
+ """Cognitive-load profile transformation.
2
+
3
+ Transforms AssistResult for users who benefit from reduced cognitive load:
4
+ - ADHD / executive dysfunction
5
+ - Autism / sensory overload
6
+ - Anxiety under incident conditions
7
+ - Novices under stress
8
+
9
+ Invariants (non-negotiable):
10
+ 1. No invented facts - only rephrases existing content
11
+ 2. No invented commands - SAFE commands must be verbatim from input
12
+ 3. SAFE-only remains absolute
13
+ 4. Additive behavior - doesn't rewrite original output
14
+ 5. Deterministic - no randomness, no network calls
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import re
20
+ from typing import List, Optional
21
+
22
+ from ..render import AssistResult, Confidence
23
+
24
+ # Boilerplate prefixes to strip
25
+ BOILERPLATE_PREFIXES = re.compile(
26
+ r"^(re-?run:\s*|run:\s*|try:\s*|\$\s*|>\s*)", re.IGNORECASE
27
+ )
28
+
29
+ # Phrases to rewrite to imperative form
30
+ IMPERATIVE_REWRITES = [
31
+ (re.compile(r"^you should\s+", re.IGNORECASE), "Do "),
32
+ (re.compile(r"^please\s+", re.IGNORECASE), "Do "),
33
+ (re.compile(r"^consider\s+", re.IGNORECASE), "Try "),
34
+ (re.compile(r"^it may help to\s+", re.IGNORECASE), "Try "),
35
+ ]
36
+
37
+ # Conjunction replacements
38
+ CONJUNCTION_REPLACEMENTS = [
39
+ (" and then ", ". Then "),
40
+ (" and ", ". "),
41
+ (" but ", ". "),
42
+ ]
43
+
44
+ # Parenthetical patterns
45
+ PARENTHETICAL_RE = re.compile(r"\s*[\(\[][^\)\]]*[\)\]]\s*")
46
+
47
+ # Max step length
48
+ MAX_STEP_LENGTH = 90
49
+
50
+
51
+ def _strip_boilerplate(s: str) -> str:
52
+ """Strip boilerplate prefixes from a string."""
53
+ return BOILERPLATE_PREFIXES.sub("", s).strip()
54
+
55
+
56
+ def _remove_parentheticals(s: str) -> str:
57
+ """Remove parenthetical content (...) and [...] from string."""
58
+ result = PARENTHETICAL_RE.sub(" ", s).strip()
59
+ # If removal empties the string, revert
60
+ if not result:
61
+ return s
62
+ # Clean up double spaces
63
+ return re.sub(r"\s+", " ", result)
64
+
65
+
66
+ def _to_imperative(s: str) -> str:
67
+ """Convert phrases to imperative form."""
68
+ for pattern, replacement in IMPERATIVE_REWRITES:
69
+ s = pattern.sub(replacement, s)
70
+ return s
71
+
72
+
73
+ def _reduce_conjunctions(s: str) -> str:
74
+ """Replace conjunctions, keep only first sentence."""
75
+ for old, new in CONJUNCTION_REPLACEMENTS:
76
+ s = s.replace(old, new)
77
+
78
+ # Keep only first sentence
79
+ sentences = s.split(". ")
80
+ if sentences:
81
+ first = sentences[0].strip()
82
+ if first and not first.endswith("."):
83
+ first += "."
84
+ return first
85
+ return s
86
+
87
+
88
+ def _cap_length(s: str, max_len: int = MAX_STEP_LENGTH) -> str:
89
+ """Cap string length, truncating with ellipsis if needed."""
90
+ if len(s) <= max_len:
91
+ return s
92
+ return s[: max_len - 1] + "…"
93
+
94
+
95
+ def normalize_step(step: str) -> str:
96
+ """Normalize a single step according to cognitive-load rules.
97
+
98
+ Order of operations:
99
+ 1. Strip boilerplate prefixes
100
+ 2. Remove parentheticals
101
+ 3. Convert to imperative form
102
+ 4. Reduce conjunctions (keep first sentence)
103
+ 5. Cap length at 90 chars
104
+ """
105
+ s = step.strip()
106
+ if not s:
107
+ return s
108
+
109
+ # 1. Strip boilerplate
110
+ s = _strip_boilerplate(s)
111
+
112
+ # 2. Remove parentheticals
113
+ s = _remove_parentheticals(s)
114
+
115
+ # 3. Convert to imperative
116
+ s = _to_imperative(s)
117
+
118
+ # 4. Reduce conjunctions
119
+ s = _reduce_conjunctions(s)
120
+
121
+ # 5. Cap length
122
+ s = _cap_length(s)
123
+
124
+ return s
125
+
126
+
127
+ def normalize_safest_step(s: str) -> str:
128
+ """Normalize safest_next_step for cognitive-load.
129
+
130
+ - Remove parentheticals
131
+ - Remove subordinate clauses (split on ; or ,)
132
+ - Ensure ends with period
133
+ - One sentence max
134
+ """
135
+ if not s:
136
+ return "Follow the Fix steps."
137
+
138
+ # Remove parentheticals
139
+ s = _remove_parentheticals(s)
140
+
141
+ # Split on semicolon or comma, keep first segment
142
+ for sep in [";", ","]:
143
+ if sep in s:
144
+ s = s.split(sep)[0].strip()
145
+ break
146
+
147
+ # Reduce conjunctions to get one sentence
148
+ s = _reduce_conjunctions(s)
149
+
150
+ # Ensure ends with period
151
+ s = s.strip()
152
+ if s and not s.endswith("."):
153
+ s += "."
154
+
155
+ return _cap_length(s, MAX_STEP_LENGTH)
156
+
157
+
158
+ def reduce_plan(plan: List[str], max_steps: int = 3) -> List[str]:
159
+ """Reduce plan to max_steps normalized items.
160
+
161
+ No merging, no summarization beyond normalization.
162
+ """
163
+ if not plan:
164
+ return ["Follow the tool's Fix steps in order."]
165
+
166
+ normalized = [normalize_step(s) for s in plan if s.strip()]
167
+ # Filter out empty results
168
+ normalized = [s for s in normalized if s]
169
+
170
+ if not normalized:
171
+ return ["Follow the tool's Fix steps in order."]
172
+
173
+ return normalized[:max_steps]
174
+
175
+
176
+ def select_safe_command(
177
+ commands: List[str], confidence: Confidence
178
+ ) -> Optional[str]:
179
+ """Select at most one SAFE command for cognitive-load.
180
+
181
+ Rules:
182
+ - Only include if confidence is High or Medium
183
+ - Return first command only
184
+ - No command synthesis
185
+ """
186
+ if confidence == "Low":
187
+ return None
188
+
189
+ if not commands:
190
+ return None
191
+
192
+ # Return first command, no modification (must be verbatim)
193
+ return commands[0]
194
+
195
+
196
+ def reduce_notes(notes: List[str], max_notes: int = 2) -> List[str]:
197
+ """Reduce notes to max_notes, remove parentheticals, cap length."""
198
+ if not notes:
199
+ return []
200
+
201
+ reduced = []
202
+ for note in notes[:max_notes]:
203
+ n = _remove_parentheticals(note)
204
+ n = _cap_length(n, 100)
205
+ if n:
206
+ reduced.append(n)
207
+
208
+ return reduced
209
+
210
+
211
+ def apply_cognitive_load(result: AssistResult) -> AssistResult:
212
+ """Transform AssistResult for cognitive-load profile.
213
+
214
+ This transformation:
215
+ 1. Reduces plan to exactly 3 steps max
216
+ 2. Normalizes step language (no conjunctions, parentheticals)
217
+ 3. Selects at most 1 SAFE command (none if Low confidence)
218
+ 4. Reduces notes to 2 max
219
+
220
+ Invariants enforced:
221
+ - No invented facts (only rephrases existing content)
222
+ - No invented commands (SAFE commands verbatim from input)
223
+ - Deterministic output
224
+ """
225
+ # Reduce and normalize plan
226
+ reduced_plan = reduce_plan(result.plan)
227
+
228
+ # Normalize safest next step
229
+ normalized_safest = normalize_safest_step(result.safest_next_step)
230
+
231
+ # Select single SAFE command (or None)
232
+ safe_cmd = select_safe_command(result.next_safe_commands, result.confidence)
233
+ safe_commands = [safe_cmd] if safe_cmd else []
234
+
235
+ # Reduce notes
236
+ reduced_notes = reduce_notes(result.notes)
237
+
238
+ return AssistResult(
239
+ anchored_id=result.anchored_id,
240
+ confidence=result.confidence,
241
+ safest_next_step=normalized_safest,
242
+ plan=reduced_plan,
243
+ next_safe_commands=safe_commands,
244
+ notes=reduced_notes,
245
+ )
@@ -0,0 +1,86 @@
1
+ """Cognitive-load profile renderer.
2
+
3
+ Renders AssistResult with cognitive-load specific formatting:
4
+ - Goal line at top
5
+ - First/Next/Last labels instead of numbers
6
+ - Maximum clarity and brevity
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import List
12
+
13
+ from ..render import AssistResult
14
+
15
+ # Step labels for cognitive-load profile
16
+ STEP_LABELS = ["First", "Next", "Last"]
17
+
18
+
19
+ def render_cognitive_load(result: AssistResult) -> str:
20
+ """Render an AssistResult in cognitive-load format.
21
+
22
+ Format:
23
+ ASSIST (Cognitive Load):
24
+ - Anchored to: ID or (none)
25
+ - Confidence: High/Medium/Low
26
+
27
+ Goal: Get back to a known-good state.
28
+
29
+ Safest next step:
30
+ <step>
31
+
32
+ Plan:
33
+ First: <step>
34
+ Next: <step>
35
+ Last: <step>
36
+
37
+ Next (SAFE):
38
+ <command> (only if confidence is High or Medium)
39
+
40
+ Notes:
41
+ - note
42
+ """
43
+ lines: List[str] = []
44
+
45
+ # Header
46
+ lines.append("ASSIST (Cognitive Load):")
47
+
48
+ if result.anchored_id:
49
+ lines.append(f"- Anchored to: {result.anchored_id}")
50
+ else:
51
+ lines.append("- Anchored to: (none)")
52
+
53
+ lines.append(f"- Confidence: {result.confidence}")
54
+ lines.append("")
55
+
56
+ # Goal (fixed text)
57
+ lines.append("Goal: Get back to a known-good state.")
58
+ lines.append("")
59
+
60
+ # Safest next step
61
+ lines.append("Safest next step:")
62
+ lines.append(f" {result.safest_next_step}")
63
+ lines.append("")
64
+
65
+ # Plan with First/Next/Last labels
66
+ lines.append("Plan:")
67
+ for i, step in enumerate(result.plan[:3]):
68
+ label = STEP_LABELS[i] if i < len(STEP_LABELS) else f"Step {i + 1}"
69
+ lines.append(f" {label}: {step}")
70
+
71
+ # Next (SAFE) - only show if commands exist
72
+ # Note: cognitive-load transform already filters for confidence
73
+ if result.next_safe_commands:
74
+ lines.append("")
75
+ lines.append("Next (SAFE):")
76
+ # Only show first command for cognitive-load
77
+ lines.append(f" {result.next_safe_commands[0]}")
78
+
79
+ # Notes (max 2, already reduced by transform)
80
+ if result.notes:
81
+ lines.append("")
82
+ lines.append("Notes:")
83
+ for note in result.notes[:2]:
84
+ lines.append(f" - {note}")
85
+
86
+ return "\n".join(lines) + "\n"
@@ -0,0 +1,144 @@
1
+ """Dyslexia profile transform.
2
+
3
+ Reduces reading friction without reducing information:
4
+ - Extra vertical spacing between sections
5
+ - One idea per line
6
+ - No dense paragraphs
7
+ - No italics/all-caps emphasis
8
+ - Labels always explicit
9
+ - No parentheticals
10
+ - No symbolic emphasis
11
+ - Abbreviations expanded once
12
+
13
+ Max 5 steps. Confidence preserved or downgraded.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import re
19
+ from typing import List
20
+
21
+ from ..render import AssistResult
22
+
23
+ # Abbreviation expansions (letter-spelled for clarity)
24
+ ABBREVIATIONS = {
25
+ r"\bCLI\b": "command line",
26
+ r"\bID\b": "I D",
27
+ r"\bJSON\b": "J S O N",
28
+ r"\bAPI\b": "A P I",
29
+ r"\bSFTP\b": "S F T P",
30
+ r"\bSSH\b": "S S H",
31
+ r"\bURL\b": "U R L",
32
+ r"\bHTTP\b": "H T T P",
33
+ r"\bHTTPS\b": "H T T P S",
34
+ }
35
+
36
+ # Parenthetical pattern
37
+ PARENTHETICAL = re.compile(r"\s*[\(\[][^\)\]]*[\)\]]\s*")
38
+
39
+ # Visual reference pattern
40
+ VISUAL_REF = re.compile(r"\b(see\s+)?(above|below|left|right|arrow)\b", re.IGNORECASE)
41
+
42
+ # Symbolic emphasis pattern (*, _, →, emojis)
43
+ SYMBOLIC_EMPHASIS = re.compile(r"[*_→←↑↓]|[\U0001F300-\U0001F9FF]")
44
+
45
+
46
+ def _expand_abbreviations(text: str) -> str:
47
+ """Expand abbreviations once for readability."""
48
+ result = text
49
+ for pattern, expansion in ABBREVIATIONS.items():
50
+ result = re.sub(pattern, expansion, result, count=1)
51
+ return result
52
+
53
+
54
+ def _remove_parentheticals(text: str) -> str:
55
+ """Remove parenthetical content."""
56
+ return PARENTHETICAL.sub(" ", text).strip()
57
+
58
+
59
+ def _remove_visual_refs(text: str) -> str:
60
+ """Remove visual navigation references."""
61
+ return VISUAL_REF.sub("", text).strip()
62
+
63
+
64
+ def _remove_symbolic_emphasis(text: str) -> str:
65
+ """Remove symbolic emphasis characters."""
66
+ return SYMBOLIC_EMPHASIS.sub("", text).strip()
67
+
68
+
69
+ def _normalize_step(step: str) -> str:
70
+ """Normalize a step for dyslexia profile.
71
+
72
+ - Remove parentheticals
73
+ - Remove visual references
74
+ - Remove symbolic emphasis
75
+ - Expand abbreviations
76
+ - Truncate to 110 chars
77
+ """
78
+ result = step
79
+ result = _remove_parentheticals(result)
80
+ result = _remove_visual_refs(result)
81
+ result = _remove_symbolic_emphasis(result)
82
+ result = _expand_abbreviations(result)
83
+
84
+ # Clean up multiple spaces
85
+ result = re.sub(r"\s+", " ", result).strip()
86
+
87
+ # Truncate if too long
88
+ if len(result) > 110:
89
+ result = result[:107] + "..."
90
+
91
+ return result
92
+
93
+
94
+ def _normalize_safest_step(text: str) -> str:
95
+ """Normalize safest next step."""
96
+ result = _remove_parentheticals(text)
97
+ result = _remove_visual_refs(result)
98
+ result = _remove_symbolic_emphasis(result)
99
+ result = _expand_abbreviations(result)
100
+ result = re.sub(r"\s+", " ", result).strip()
101
+ return result
102
+
103
+
104
+ def apply_dyslexia(result: AssistResult) -> AssistResult:
105
+ """Apply dyslexia profile transformation.
106
+
107
+ - Expand abbreviations
108
+ - Remove parentheticals
109
+ - Remove visual references
110
+ - Remove symbolic emphasis
111
+ - Max 5 steps
112
+ - Preserve or downgrade confidence
113
+ """
114
+ # Normalize safest next step
115
+ safest = _normalize_safest_step(result.safest_next_step)
116
+
117
+ # Normalize and limit plan steps
118
+ plan: List[str] = []
119
+ for step in result.plan[:5]: # Max 5 steps
120
+ normalized = _normalize_step(step)
121
+ if normalized:
122
+ plan.append(normalized)
123
+
124
+ # Normalize notes (max 2)
125
+ notes: List[str] = []
126
+ for note in result.notes[:2]: # Max 2 notes
127
+ normalized = _remove_parentheticals(note)
128
+ normalized = _remove_symbolic_emphasis(normalized)
129
+ normalized = _expand_abbreviations(normalized)
130
+ normalized = re.sub(r"\s+", " ", normalized).strip()
131
+ if normalized:
132
+ notes.append(normalized)
133
+
134
+ # Commands: preserve only safe commands, max 3
135
+ commands = result.next_safe_commands[:3]
136
+
137
+ return AssistResult(
138
+ anchored_id=result.anchored_id,
139
+ confidence=result.confidence, # Preserved (guard enforces no increase)
140
+ safest_next_step=safest,
141
+ plan=plan,
142
+ next_safe_commands=commands,
143
+ notes=notes,
144
+ )
@@ -0,0 +1,77 @@
1
+ """Dyslexia profile renderer.
2
+
3
+ Output format optimized for reduced reading friction:
4
+ - Extra vertical spacing between sections
5
+ - One idea per line
6
+ - Explicit labels
7
+ - Numbered steps with "Step N:" prefix
8
+ - No dense paragraphs
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from ..render import AssistResult
14
+
15
+
16
+ def render_dyslexia(result: AssistResult) -> str:
17
+ """Render AssistResult in dyslexia-friendly format.
18
+
19
+ Format:
20
+ ASSIST (Dyslexia):
21
+ Anchored ID: <ID or none>
22
+ Confidence: High | Medium | Low
23
+
24
+ Summary:
25
+ <derived from context>
26
+
27
+ Safest next step:
28
+ <one sentence>
29
+
30
+ Plan:
31
+ - Step 1: <sentence>
32
+ - Step 2: <sentence>
33
+ ...
34
+
35
+ Next safe command:
36
+ <command>
37
+
38
+ Notes:
39
+ - <sentence>
40
+ """
41
+ lines = []
42
+
43
+ # Header - each piece on its own line
44
+ lines.append("ASSIST (Dyslexia):")
45
+ lines.append("")
46
+ lines.append(f"Anchored ID: {result.anchored_id or 'none'}")
47
+ lines.append("")
48
+ lines.append(f"Confidence: {result.confidence}")
49
+ lines.append("")
50
+
51
+ # Safest next step - explicit label
52
+ lines.append("Safest next step:")
53
+ lines.append(f" {result.safest_next_step}")
54
+ lines.append("")
55
+
56
+ # Plan - numbered with "Step N:" prefix
57
+ if result.plan:
58
+ lines.append("Plan:")
59
+ for i, step in enumerate(result.plan, 1):
60
+ lines.append(f" - Step {i}: {step}")
61
+ lines.append("")
62
+
63
+ # Next safe command - only if confidence is not Low
64
+ if result.next_safe_commands and result.confidence != "Low":
65
+ lines.append("Next safe command:")
66
+ # Show first command only for simplicity
67
+ lines.append(f" {result.next_safe_commands[0]}")
68
+ lines.append("")
69
+
70
+ # Notes - max 2, each on its own line
71
+ if result.notes:
72
+ lines.append("Notes:")
73
+ for note in result.notes[:2]:
74
+ lines.append(f" - {note}")
75
+ lines.append("")
76
+
77
+ return "\n".join(lines)
@@ -0,0 +1,119 @@
1
+ """Plain-language profile transform.
2
+
3
+ Maximizes understandability through:
4
+ - Active voice
5
+ - Present tense
6
+ - One clause per sentence
7
+ - No idioms or metaphors
8
+ - No jargon unless already present
9
+ - Short, clear sentences
10
+
11
+ Max 4 steps. Confidence preserved or downgraded.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import re
17
+ from typing import List
18
+
19
+ from ..render import AssistResult
20
+
21
+ # Parenthetical pattern
22
+ PARENTHETICAL = re.compile(r"\s*[\(\[][^\)\]]*[\)\]]\s*")
23
+
24
+ # Conjunction pattern for splitting
25
+ CONJUNCTIONS = re.compile(r"\s*(?:,\s*and\s+|,\s*but\s+|,\s*or\s+|\s+and\s+|\s+but\s+|\s+or\s+)")
26
+
27
+ # Subordinate clause starters
28
+ SUBORDINATE = re.compile(
29
+ r"\s*(?:,\s*)?(?:which|that|who|whom|whose|when|where|while|although|because|if|unless|until|after|before|since)\s+.*$",
30
+ re.IGNORECASE,
31
+ )
32
+
33
+
34
+ def _remove_parentheticals(text: str) -> str:
35
+ """Remove parenthetical content."""
36
+ return PARENTHETICAL.sub(" ", text).strip()
37
+
38
+
39
+ def _simplify_sentence(text: str) -> str:
40
+ """Simplify a sentence to one clause.
41
+
42
+ - Remove parentheticals
43
+ - Split on conjunctions, keep first clause
44
+ - Remove subordinate clauses
45
+ """
46
+ result = _remove_parentheticals(text)
47
+
48
+ # Split on conjunctions, keep first part
49
+ parts = CONJUNCTIONS.split(result, maxsplit=1)
50
+ if parts:
51
+ result = parts[0].strip()
52
+
53
+ # Remove subordinate clauses
54
+ result = SUBORDINATE.sub("", result).strip()
55
+
56
+ # Clean up trailing punctuation issues
57
+ result = re.sub(r"\s+", " ", result).strip()
58
+
59
+ # Ensure ends with period if it doesn't have punctuation
60
+ if result and not result[-1] in ".!?:":
61
+ result = result + "."
62
+
63
+ return result
64
+
65
+
66
+ def _normalize_step(step: str) -> str:
67
+ """Normalize a step for plain-language profile.
68
+
69
+ - Simplify to one clause
70
+ - Remove complex structures
71
+ """
72
+ result = _simplify_sentence(step)
73
+
74
+ # Remove any remaining parentheticals
75
+ result = _remove_parentheticals(result)
76
+
77
+ # Clean up
78
+ result = re.sub(r"\s+", " ", result).strip()
79
+
80
+ return result
81
+
82
+
83
+ def apply_plain_language(result: AssistResult) -> AssistResult:
84
+ """Apply plain-language profile transformation.
85
+
86
+ - Simplify sentences to one clause
87
+ - Remove parentheticals
88
+ - Remove subordinate clauses
89
+ - Max 4 steps
90
+ - Preserve or downgrade confidence
91
+ """
92
+ # Simplify safest next step
93
+ safest = _simplify_sentence(result.safest_next_step)
94
+
95
+ # Normalize and limit plan steps
96
+ plan: List[str] = []
97
+ for step in result.plan[:4]: # Max 4 steps
98
+ normalized = _normalize_step(step)
99
+ if normalized and len(normalized) > 2: # Skip trivially empty
100
+ plan.append(normalized)
101
+
102
+ # Simplify notes (max 2)
103
+ notes: List[str] = []
104
+ for note in result.notes[:2]:
105
+ simplified = _simplify_sentence(note)
106
+ if simplified and len(simplified) > 2:
107
+ notes.append(simplified)
108
+
109
+ # Commands: preserve only safe commands, max 1 for simplicity
110
+ commands = result.next_safe_commands[:1]
111
+
112
+ return AssistResult(
113
+ anchored_id=result.anchored_id,
114
+ confidence=result.confidence, # Preserved (guard enforces no increase)
115
+ safest_next_step=safest,
116
+ plan=plan,
117
+ next_safe_commands=commands,
118
+ notes=notes,
119
+ )