@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.
- package/.github/workflows/ci.yml +63 -0
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/docs/prov-spec/.github/workflows/ci.yml +68 -0
- package/docs/prov-spec/CHANGELOG.md +69 -0
- package/docs/prov-spec/CODE_OF_CONDUCT.md +129 -0
- package/docs/prov-spec/CONFORMANCE_LEVELS.md +223 -0
- package/docs/prov-spec/CONTRIBUTING.md +145 -0
- package/docs/prov-spec/IMPLEMENTER_CHECKLIST.md +137 -0
- package/docs/prov-spec/LICENSE +21 -0
- package/docs/prov-spec/PRESS_RELEASE.md +74 -0
- package/docs/prov-spec/README.md +182 -0
- package/docs/prov-spec/SETUP.md +135 -0
- package/docs/prov-spec/WHY.md +86 -0
- package/docs/prov-spec/examples/artifact.example.json +14 -0
- package/docs/prov-spec/examples/artifact.ref.example.json +9 -0
- package/docs/prov-spec/examples/evidence.example.json +6 -0
- package/docs/prov-spec/examples/mcp.envelope.example.json +97 -0
- package/docs/prov-spec/examples/mcp.request.example.json +28 -0
- package/docs/prov-spec/examples/prov.record.example.json +35 -0
- package/docs/prov-spec/interop/PROOF_NODE_ENGINE.md +114 -0
- package/docs/prov-spec/spec/MCP_COMPATIBILITY.md +241 -0
- package/docs/prov-spec/spec/PROV_METHODS_CATALOG.md +142 -0
- package/docs/prov-spec/spec/PROV_METHODS_SPEC.md +397 -0
- package/docs/prov-spec/spec/methods.json +213 -0
- package/docs/prov-spec/spec/schemas/artifact.ref.schema.v0.1.json +58 -0
- package/docs/prov-spec/spec/schemas/artifact.schema.v0.1.json +61 -0
- package/docs/prov-spec/spec/schemas/assist.request.schema.v0.1.json +52 -0
- package/docs/prov-spec/spec/schemas/assist.response.schema.v0.1.json +70 -0
- package/docs/prov-spec/spec/schemas/cli.error.schema.v0.1.json +78 -0
- package/docs/prov-spec/spec/schemas/evidence.schema.v0.1.json +37 -0
- package/docs/prov-spec/spec/schemas/mcp.envelope.schema.v0.1.json +141 -0
- package/docs/prov-spec/spec/schemas/mcp.request.schema.v0.1.json +79 -0
- package/docs/prov-spec/spec/schemas/methods.schema.json +93 -0
- package/docs/prov-spec/spec/schemas/prov-capabilities.schema.json +122 -0
- package/docs/prov-spec/spec/schemas/prov.record.schema.v0.1.json +133 -0
- package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/expected.json +4 -0
- package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/input.json +1 -0
- package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/negative/double_wrapped.json +14 -0
- package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/negative/wrong_schema_version.json +11 -0
- package/docs/prov-spec/spec/vectors/engine.extract.evidence.json_pointer/expected.json +24 -0
- package/docs/prov-spec/spec/vectors/engine.extract.evidence.json_pointer/input.json +8 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/expected.json +7 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/input.json +1 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/negative/non_hex_chars.json +16 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/negative/uppercase_hex.json +16 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/negative/wrong_length.json +16 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/negative/hyphen_separator.json +8 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/negative/reserved_namespace.json +8 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/negative/starts_with_digit.json +8 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/negative/uppercase.json +8 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/positive/valid_ids.json +18 -0
- package/docs/prov-spec/tools/python/prov_validator.py +428 -0
- package/examples/a11y-demo-site/.github/workflows/a11y-artifacts.yml +81 -0
- package/examples/a11y-demo-site/.github/workflows/a11y.yml +34 -0
- package/examples/a11y-demo-site/CODE_OF_CONDUCT.md +129 -0
- package/examples/a11y-demo-site/CONTRIBUTING.md +83 -0
- package/examples/a11y-demo-site/LICENSE +21 -0
- package/examples/a11y-demo-site/README.md +155 -0
- package/examples/a11y-demo-site/html/contact.html +15 -0
- package/examples/a11y-demo-site/html/index.html +20 -0
- package/examples/a11y-demo-site/scripts/a11y.sh +20 -0
- package/package.json +26 -0
- package/src/a11y-assist/.github/workflows/publish.yml +52 -0
- package/src/a11y-assist/.github/workflows/test.yml +30 -0
- package/src/a11y-assist/A11Y_ASSIST_TEST_COVERAGE_REQUIREMENTS.md +104 -0
- package/src/a11y-assist/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-assist/CONTRIBUTING.md +98 -0
- package/src/a11y-assist/ENGINE.md +363 -0
- package/src/a11y-assist/LICENSE +21 -0
- package/src/a11y-assist/PRESS_RELEASE.md +71 -0
- package/src/a11y-assist/QUICKSTART.md +101 -0
- package/src/a11y-assist/README.md +192 -0
- package/src/a11y-assist/RELEASE_NOTES.md +319 -0
- package/src/a11y-assist/a11y_assist/__init__.py +3 -0
- package/src/a11y-assist/a11y_assist/cli.py +599 -0
- package/src/a11y-assist/a11y_assist/from_cli_error.py +149 -0
- package/src/a11y-assist/a11y_assist/guard.py +444 -0
- package/src/a11y-assist/a11y_assist/ingest.py +407 -0
- package/src/a11y-assist/a11y_assist/methods.py +137 -0
- package/src/a11y-assist/a11y_assist/parse_raw.py +71 -0
- package/src/a11y-assist/a11y_assist/profiles/__init__.py +29 -0
- package/src/a11y-assist/a11y_assist/profiles/cognitive_load.py +245 -0
- package/src/a11y-assist/a11y_assist/profiles/cognitive_load_render.py +86 -0
- package/src/a11y-assist/a11y_assist/profiles/dyslexia.py +144 -0
- package/src/a11y-assist/a11y_assist/profiles/dyslexia_render.py +77 -0
- package/src/a11y-assist/a11y_assist/profiles/plain_language.py +119 -0
- package/src/a11y-assist/a11y_assist/profiles/plain_language_render.py +66 -0
- package/src/a11y-assist/a11y_assist/profiles/screen_reader.py +348 -0
- package/src/a11y-assist/a11y_assist/profiles/screen_reader_render.py +89 -0
- package/src/a11y-assist/a11y_assist/render.py +95 -0
- package/src/a11y-assist/a11y_assist/schemas/assist.request.schema.v0.1.json +52 -0
- package/src/a11y-assist/a11y_assist/schemas/assist.response.schema.v0.1.json +70 -0
- package/src/a11y-assist/a11y_assist/schemas/cli.error.schema.v0.1.json +78 -0
- package/src/a11y-assist/a11y_assist/storage.py +31 -0
- package/src/a11y-assist/pyproject.toml +60 -0
- package/src/a11y-assist/tests/__init__.py +1 -0
- package/src/a11y-assist/tests/fixtures/base_inputs/cli_error_high.json +18 -0
- package/src/a11y-assist/tests/fixtures/base_inputs/cli_error_medium.json +16 -0
- package/src/a11y-assist/tests/fixtures/base_inputs/raw_text_low.txt +3 -0
- package/src/a11y-assist/tests/fixtures/cli_error_good.json +9 -0
- package/src/a11y-assist/tests/fixtures/cli_error_missing_id.json +7 -0
- package/src/a11y-assist/tests/fixtures/cli_error_string_format.json +7 -0
- package/src/a11y-assist/tests/fixtures/expected/cognitive_load_high.txt +20 -0
- package/src/a11y-assist/tests/fixtures/expected/dyslexia_high.txt +20 -0
- package/src/a11y-assist/tests/fixtures/expected/lowvision_high.txt +18 -0
- package/src/a11y-assist/tests/fixtures/expected/plain_language_high.txt +14 -0
- package/src/a11y-assist/tests/fixtures/expected/screen_reader_high.txt +19 -0
- package/src/a11y-assist/tests/fixtures/golden_screen_reader_cli_error.txt +16 -0
- package/src/a11y-assist/tests/fixtures/golden_screen_reader_raw_no_id.txt +14 -0
- package/src/a11y-assist/tests/fixtures/golden_screen_reader_raw_with_id.txt +14 -0
- package/src/a11y-assist/tests/fixtures/raw_good.txt +11 -0
- package/src/a11y-assist/tests/fixtures/raw_no_id.txt +2 -0
- package/src/a11y-assist/tests/test_cognitive_load.py +469 -0
- package/src/a11y-assist/tests/test_dyslexia.py +337 -0
- package/src/a11y-assist/tests/test_explain.py +74 -0
- package/src/a11y-assist/tests/test_golden.py +127 -0
- package/src/a11y-assist/tests/test_guard.py +819 -0
- package/src/a11y-assist/tests/test_guard_integration.py +457 -0
- package/src/a11y-assist/tests/test_ingest.py +311 -0
- package/src/a11y-assist/tests/test_methods_metadata.py +236 -0
- package/src/a11y-assist/tests/test_plain_language.py +348 -0
- package/src/a11y-assist/tests/test_render.py +117 -0
- package/src/a11y-assist/tests/test_screen_reader.py +703 -0
- package/src/a11y-assist/tests/test_storage_last.py +61 -0
- package/src/a11y-assist/tests/test_triage.py +86 -0
- package/src/a11y-ci/.github/workflows/ci.yml +43 -0
- package/src/a11y-ci/.github/workflows/test.yml +30 -0
- package/src/a11y-ci/A11Y_CI_TEST_COVERAGE_REQUIREMENTS.md +94 -0
- package/src/a11y-ci/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-ci/CONTRIBUTING.md +142 -0
- package/src/a11y-ci/LICENSE +21 -0
- package/src/a11y-ci/README.md +105 -0
- package/src/a11y-ci/a11y_ci/__init__.py +3 -0
- package/src/a11y-ci/a11y_ci/allowlist.py +83 -0
- package/src/a11y-ci/a11y_ci/cli.py +145 -0
- package/src/a11y-ci/a11y_ci/gate.py +131 -0
- package/src/a11y-ci/a11y_ci/render.py +48 -0
- package/src/a11y-ci/a11y_ci/schemas/allowlist.schema.json +24 -0
- package/src/a11y-ci/a11y_ci/scorecard.py +99 -0
- package/src/a11y-ci/npm/package.json +35 -0
- package/src/a11y-ci/pyproject.toml +64 -0
- package/src/a11y-ci/tests/__init__.py +1 -0
- package/src/a11y-ci/tests/fixtures/allowlist_expired.json +10 -0
- package/src/a11y-ci/tests/fixtures/allowlist_ok.json +10 -0
- package/src/a11y-ci/tests/fixtures/baseline_ok.json +7 -0
- package/src/a11y-ci/tests/fixtures/current_fail.json +6 -0
- package/src/a11y-ci/tests/fixtures/current_ok.json +6 -0
- package/src/a11y-ci/tests/fixtures/current_regresses.json +7 -0
- package/src/a11y-ci/tests/test_gate.py +134 -0
- package/src/a11y-evidence-engine/.github/workflows/ci.yml +53 -0
- package/src/a11y-evidence-engine/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-evidence-engine/CONTRIBUTING.md +128 -0
- package/src/a11y-evidence-engine/LICENSE +21 -0
- package/src/a11y-evidence-engine/README.md +71 -0
- package/src/a11y-evidence-engine/bin/a11y-engine.js +11 -0
- package/src/a11y-evidence-engine/fixtures/bad/button-no-name.html +30 -0
- package/src/a11y-evidence-engine/fixtures/bad/img-missing-alt.html +19 -0
- package/src/a11y-evidence-engine/fixtures/bad/input-missing-label.html +26 -0
- package/src/a11y-evidence-engine/fixtures/bad/missing-lang.html +11 -0
- package/src/a11y-evidence-engine/fixtures/good/index.html +29 -0
- package/src/a11y-evidence-engine/package-lock.json +109 -0
- package/src/a11y-evidence-engine/package.json +45 -0
- package/src/a11y-evidence-engine/src/cli.js +74 -0
- package/src/a11y-evidence-engine/src/evidence/canonicalize.js +52 -0
- package/src/a11y-evidence-engine/src/evidence/json_pointer.js +34 -0
- package/src/a11y-evidence-engine/src/evidence/prov_emit.js +153 -0
- package/src/a11y-evidence-engine/src/fswalk.js +56 -0
- package/src/a11y-evidence-engine/src/html_parse.js +117 -0
- package/src/a11y-evidence-engine/src/ids.js +53 -0
- package/src/a11y-evidence-engine/src/rules/document_missing_lang.js +50 -0
- package/src/a11y-evidence-engine/src/rules/form_control_missing_label.js +105 -0
- package/src/a11y-evidence-engine/src/rules/img_missing_alt.js +77 -0
- package/src/a11y-evidence-engine/src/rules/index.js +37 -0
- package/src/a11y-evidence-engine/src/rules/interactive_missing_name.js +129 -0
- package/src/a11y-evidence-engine/src/scan.js +128 -0
- package/src/a11y-evidence-engine/test/scan.test.js +149 -0
- package/src/a11y-evidence-engine/test/vectors.test.js +200 -0
- package/src/a11y-lint/.github/workflows/ci.yml +46 -0
- package/src/a11y-lint/.github/workflows/test.yml +34 -0
- package/src/a11y-lint/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-lint/CONTRIBUTING.md +70 -0
- package/src/a11y-lint/GOVERNANCE.md +57 -0
- package/src/a11y-lint/LICENSE +21 -0
- package/src/a11y-lint/PRESS_RELEASE.md +50 -0
- package/src/a11y-lint/README.md +276 -0
- package/src/a11y-lint/RELEASE_NOTES.md +57 -0
- package/src/a11y-lint/RELEASING.md +57 -0
- package/src/a11y-lint/a11y_lint/__init__.py +64 -0
- package/src/a11y-lint/a11y_lint/cli.py +319 -0
- package/src/a11y-lint/a11y_lint/errors.py +252 -0
- package/src/a11y-lint/a11y_lint/render.py +293 -0
- package/src/a11y-lint/a11y_lint/report_md.py +289 -0
- package/src/a11y-lint/a11y_lint/scan_cli_text.py +434 -0
- package/src/a11y-lint/a11y_lint/schemas/cli.error.schema.v0.1.json +83 -0
- package/src/a11y-lint/a11y_lint/scorecard.py +244 -0
- package/src/a11y-lint/a11y_lint/validate.py +225 -0
- package/src/a11y-lint/pyproject.toml +75 -0
- package/src/a11y-lint/tests/__init__.py +1 -0
- package/src/a11y-lint/tests/test_cli.py +200 -0
- package/src/a11y-lint/tests/test_errors.py +188 -0
- package/src/a11y-lint/tests/test_render.py +202 -0
- package/src/a11y-lint/tests/test_report_md.py +188 -0
- package/src/a11y-lint/tests/test_scan_cli_text.py +290 -0
- package/src/a11y-lint/tests/test_scorecard.py +195 -0
- package/src/a11y-lint/tests/test_validate.py +257 -0
- package/src/a11y-mcp-tools/.github/workflows/ci.yml +53 -0
- package/src/a11y-mcp-tools/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-mcp-tools/CONTRIBUTING.md +136 -0
- package/src/a11y-mcp-tools/LICENSE +21 -0
- package/src/a11y-mcp-tools/PROV_METHODS_CATALOG.md +104 -0
- package/src/a11y-mcp-tools/README.md +168 -0
- package/src/a11y-mcp-tools/bin/cli.js +452 -0
- package/src/a11y-mcp-tools/bin/server.js +244 -0
- package/src/a11y-mcp-tools/fixtures/requests/a11y.diagnose.ok.json +27 -0
- package/src/a11y-mcp-tools/fixtures/requests/a11y.evidence.ok.json +25 -0
- package/src/a11y-mcp-tools/fixtures/responses/a11y.diagnose.ok.json +139 -0
- package/src/a11y-mcp-tools/fixtures/responses/a11y.diagnose.provenance_fail.json +13 -0
- package/src/a11y-mcp-tools/fixtures/responses/a11y.evidence.ok.json +88 -0
- package/src/a11y-mcp-tools/package-lock.json +189 -0
- package/src/a11y-mcp-tools/package.json +49 -0
- package/src/a11y-mcp-tools/src/envelope.js +197 -0
- package/src/a11y-mcp-tools/src/index.js +9 -0
- package/src/a11y-mcp-tools/src/schemas/artifact.js +85 -0
- package/src/a11y-mcp-tools/src/schemas/diagnosis.schema.v0.1.json +137 -0
- package/src/a11y-mcp-tools/src/schemas/envelope.schema.v0.1.json +108 -0
- package/src/a11y-mcp-tools/src/schemas/evidence.bundle.schema.v0.1.json +129 -0
- package/src/a11y-mcp-tools/src/schemas/evidence.js +97 -0
- package/src/a11y-mcp-tools/src/schemas/index.js +11 -0
- package/src/a11y-mcp-tools/src/schemas/provenance.js +140 -0
- package/src/a11y-mcp-tools/src/schemas/tools/a11y.diagnose.request.schema.v0.1.json +77 -0
- package/src/a11y-mcp-tools/src/schemas/tools/a11y.diagnose.response.schema.v0.1.json +50 -0
- package/src/a11y-mcp-tools/src/schemas/tools/a11y.evidence.request.schema.v0.1.json +120 -0
- package/src/a11y-mcp-tools/src/schemas/tools/a11y.evidence.response.schema.v0.1.json +50 -0
- package/src/a11y-mcp-tools/src/tools/diagnose.js +597 -0
- package/src/a11y-mcp-tools/src/tools/evidence.js +481 -0
- package/src/a11y-mcp-tools/src/tools/index.js +10 -0
- package/src/a11y-mcp-tools/test/contract.test.mjs +154 -0
- package/src/a11y-mcp-tools/test/diagnose.test.js +485 -0
- package/src/a11y-mcp-tools/test/evidence.test.js +183 -0
- package/src/a11y-mcp-tools/test/schema.test.js +327 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""Tests for dyslexia profile.
|
|
2
|
+
|
|
3
|
+
Verifies:
|
|
4
|
+
- Parentheticals removed
|
|
5
|
+
- Visual references removed
|
|
6
|
+
- Symbolic emphasis removed
|
|
7
|
+
- Abbreviations expanded
|
|
8
|
+
- Max 5 steps
|
|
9
|
+
- Step numbering with "Step N:" prefix
|
|
10
|
+
- Confidence preserved
|
|
11
|
+
- Commands from allowlist only
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from a11y_assist.profiles.dyslexia import (
|
|
17
|
+
_expand_abbreviations,
|
|
18
|
+
_normalize_step,
|
|
19
|
+
_remove_parentheticals,
|
|
20
|
+
_remove_symbolic_emphasis,
|
|
21
|
+
_remove_visual_refs,
|
|
22
|
+
apply_dyslexia,
|
|
23
|
+
)
|
|
24
|
+
from a11y_assist.profiles.dyslexia_render import render_dyslexia
|
|
25
|
+
from a11y_assist.render import AssistResult
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Unit tests for helper functions
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_remove_parentheticals():
|
|
32
|
+
"""Should remove parenthetical content."""
|
|
33
|
+
assert _remove_parentheticals("Check config (optional)") == "Check config"
|
|
34
|
+
assert _remove_parentheticals("Run command [see docs]") == "Run command"
|
|
35
|
+
assert _remove_parentheticals("No parens here") == "No parens here"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_remove_visual_refs():
|
|
39
|
+
"""Should remove visual navigation references."""
|
|
40
|
+
assert _remove_visual_refs("See above for details") == "for details"
|
|
41
|
+
assert _remove_visual_refs("Check below") == "Check"
|
|
42
|
+
assert _remove_visual_refs("Click the left arrow") == "Click the"
|
|
43
|
+
assert _remove_visual_refs("No visual refs") == "No visual refs"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_remove_symbolic_emphasis():
|
|
47
|
+
"""Should remove symbolic emphasis characters."""
|
|
48
|
+
assert _remove_symbolic_emphasis("*important*") == "important"
|
|
49
|
+
assert _remove_symbolic_emphasis("_underlined_") == "underlined"
|
|
50
|
+
assert _remove_symbolic_emphasis("Step → Next") == "Step Next"
|
|
51
|
+
assert _remove_symbolic_emphasis("No symbols") == "No symbols"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_expand_abbreviations():
|
|
55
|
+
"""Should expand abbreviations once."""
|
|
56
|
+
assert "command line" in _expand_abbreviations("Use the CLI tool")
|
|
57
|
+
assert "I D" in _expand_abbreviations("Check the ID field")
|
|
58
|
+
assert "J S O N" in _expand_abbreviations("Parse the JSON file")
|
|
59
|
+
assert "A P I" in _expand_abbreviations("Call the API")
|
|
60
|
+
assert "S F T P" in _expand_abbreviations("Upload via SFTP")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_normalize_step_removes_parentheticals():
|
|
64
|
+
"""Step normalization should remove parentheticals."""
|
|
65
|
+
result = _normalize_step("Run the check (optional)")
|
|
66
|
+
assert "(" not in result
|
|
67
|
+
assert ")" not in result
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_normalize_step_removes_visual_refs():
|
|
71
|
+
"""Step normalization should remove visual references."""
|
|
72
|
+
result = _normalize_step("See above for more info")
|
|
73
|
+
assert "above" not in result
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_normalize_step_truncates_long_text():
|
|
77
|
+
"""Step normalization should truncate to 110 chars."""
|
|
78
|
+
long_text = "A" * 150
|
|
79
|
+
result = _normalize_step(long_text)
|
|
80
|
+
assert len(result) <= 110
|
|
81
|
+
assert result.endswith("...")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Tests for apply_dyslexia transform
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.fixture
|
|
88
|
+
def base_result() -> AssistResult:
|
|
89
|
+
"""Base result with various elements to transform."""
|
|
90
|
+
return AssistResult(
|
|
91
|
+
anchored_id="TEST.ERROR.001",
|
|
92
|
+
confidence="High",
|
|
93
|
+
safest_next_step="Check the CLI config (see docs above).",
|
|
94
|
+
plan=[
|
|
95
|
+
"Step 1: Verify the JSON file (optional).",
|
|
96
|
+
"Step 2: Run the API check → next step.",
|
|
97
|
+
"Step 3: See below for SFTP details.",
|
|
98
|
+
"Step 4: Final check.",
|
|
99
|
+
"Step 5: Done.",
|
|
100
|
+
"Step 6: Extra step (should be truncated).",
|
|
101
|
+
],
|
|
102
|
+
next_safe_commands=["cmd --dry-run", "cmd2 --check"],
|
|
103
|
+
notes=["Note with *emphasis* and (parenthetical).", "Second note."],
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_apply_dyslexia_removes_parentheticals(base_result: AssistResult):
|
|
108
|
+
"""Dyslexia transform should remove parentheticals."""
|
|
109
|
+
result = apply_dyslexia(base_result)
|
|
110
|
+
assert "(" not in result.safest_next_step
|
|
111
|
+
assert ")" not in result.safest_next_step
|
|
112
|
+
for step in result.plan:
|
|
113
|
+
assert "(" not in step
|
|
114
|
+
assert ")" not in step
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_apply_dyslexia_removes_visual_refs(base_result: AssistResult):
|
|
118
|
+
"""Dyslexia transform should remove visual references."""
|
|
119
|
+
result = apply_dyslexia(base_result)
|
|
120
|
+
assert "above" not in result.safest_next_step.lower()
|
|
121
|
+
for step in result.plan:
|
|
122
|
+
assert "below" not in step.lower()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_apply_dyslexia_removes_symbolic_emphasis(base_result: AssistResult):
|
|
126
|
+
"""Dyslexia transform should remove symbolic emphasis."""
|
|
127
|
+
result = apply_dyslexia(base_result)
|
|
128
|
+
for note in result.notes:
|
|
129
|
+
assert "*" not in note
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_apply_dyslexia_expands_abbreviations(base_result: AssistResult):
|
|
133
|
+
"""Dyslexia transform should expand abbreviations."""
|
|
134
|
+
result = apply_dyslexia(base_result)
|
|
135
|
+
# CLI expanded in safest_next_step
|
|
136
|
+
assert "command line" in result.safest_next_step.lower()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_apply_dyslexia_max_5_steps(base_result: AssistResult):
|
|
140
|
+
"""Dyslexia transform should limit to 5 steps."""
|
|
141
|
+
result = apply_dyslexia(base_result)
|
|
142
|
+
assert len(result.plan) <= 5
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_apply_dyslexia_max_2_notes(base_result: AssistResult):
|
|
146
|
+
"""Dyslexia transform should limit to 2 notes."""
|
|
147
|
+
result = apply_dyslexia(base_result)
|
|
148
|
+
assert len(result.notes) <= 2
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_apply_dyslexia_preserves_confidence(base_result: AssistResult):
|
|
152
|
+
"""Dyslexia transform should preserve confidence."""
|
|
153
|
+
result = apply_dyslexia(base_result)
|
|
154
|
+
assert result.confidence == base_result.confidence
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_apply_dyslexia_preserves_id(base_result: AssistResult):
|
|
158
|
+
"""Dyslexia transform should preserve anchored ID."""
|
|
159
|
+
result = apply_dyslexia(base_result)
|
|
160
|
+
assert result.anchored_id == base_result.anchored_id
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_apply_dyslexia_preserves_commands(base_result: AssistResult):
|
|
164
|
+
"""Dyslexia transform should preserve safe commands."""
|
|
165
|
+
result = apply_dyslexia(base_result)
|
|
166
|
+
assert result.next_safe_commands == base_result.next_safe_commands[:3]
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# Tests for render_dyslexia
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_render_dyslexia_header():
|
|
173
|
+
"""Render should include dyslexia header."""
|
|
174
|
+
result = AssistResult(
|
|
175
|
+
anchored_id="TEST.001",
|
|
176
|
+
confidence="High",
|
|
177
|
+
safest_next_step="Check config.",
|
|
178
|
+
plan=["Step 1."],
|
|
179
|
+
next_safe_commands=[],
|
|
180
|
+
notes=[],
|
|
181
|
+
)
|
|
182
|
+
output = render_dyslexia(result)
|
|
183
|
+
assert "ASSIST (Dyslexia):" in output
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_render_dyslexia_explicit_labels():
|
|
187
|
+
"""Render should use explicit labels."""
|
|
188
|
+
result = AssistResult(
|
|
189
|
+
anchored_id="TEST.001",
|
|
190
|
+
confidence="High",
|
|
191
|
+
safest_next_step="Check config.",
|
|
192
|
+
plan=["Step 1.", "Step 2."],
|
|
193
|
+
next_safe_commands=["cmd --dry-run"],
|
|
194
|
+
notes=["A note."],
|
|
195
|
+
)
|
|
196
|
+
output = render_dyslexia(result)
|
|
197
|
+
assert "Anchored ID:" in output
|
|
198
|
+
assert "Confidence:" in output
|
|
199
|
+
assert "Safest next step:" in output
|
|
200
|
+
assert "Plan:" in output
|
|
201
|
+
assert "Next safe command:" in output
|
|
202
|
+
assert "Notes:" in output
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_render_dyslexia_step_numbering():
|
|
206
|
+
"""Render should use 'Step N:' prefix."""
|
|
207
|
+
result = AssistResult(
|
|
208
|
+
anchored_id="TEST.001",
|
|
209
|
+
confidence="High",
|
|
210
|
+
safest_next_step="Check config.",
|
|
211
|
+
plan=["First step.", "Second step.", "Third step."],
|
|
212
|
+
next_safe_commands=[],
|
|
213
|
+
notes=[],
|
|
214
|
+
)
|
|
215
|
+
output = render_dyslexia(result)
|
|
216
|
+
assert "- Step 1:" in output
|
|
217
|
+
assert "- Step 2:" in output
|
|
218
|
+
assert "- Step 3:" in output
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_render_dyslexia_no_command_on_low():
|
|
222
|
+
"""Render should not show command on Low confidence."""
|
|
223
|
+
result = AssistResult(
|
|
224
|
+
anchored_id=None,
|
|
225
|
+
confidence="Low",
|
|
226
|
+
safest_next_step="Check config.",
|
|
227
|
+
plan=["Step 1."],
|
|
228
|
+
next_safe_commands=["cmd --dry-run"],
|
|
229
|
+
notes=[],
|
|
230
|
+
)
|
|
231
|
+
output = render_dyslexia(result)
|
|
232
|
+
assert "Next safe command:" not in output
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def test_render_dyslexia_shows_command_on_high():
|
|
236
|
+
"""Render should show command on High confidence."""
|
|
237
|
+
result = AssistResult(
|
|
238
|
+
anchored_id="TEST.001",
|
|
239
|
+
confidence="High",
|
|
240
|
+
safest_next_step="Check config.",
|
|
241
|
+
plan=["Step 1."],
|
|
242
|
+
next_safe_commands=["cmd --dry-run"],
|
|
243
|
+
notes=[],
|
|
244
|
+
)
|
|
245
|
+
output = render_dyslexia(result)
|
|
246
|
+
assert "Next safe command:" in output
|
|
247
|
+
assert "cmd --dry-run" in output
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_render_dyslexia_vertical_spacing():
|
|
251
|
+
"""Render should have blank lines between sections."""
|
|
252
|
+
result = AssistResult(
|
|
253
|
+
anchored_id="TEST.001",
|
|
254
|
+
confidence="High",
|
|
255
|
+
safest_next_step="Check config.",
|
|
256
|
+
plan=["Step 1."],
|
|
257
|
+
next_safe_commands=[],
|
|
258
|
+
notes=[],
|
|
259
|
+
)
|
|
260
|
+
output = render_dyslexia(result)
|
|
261
|
+
# Should have multiple blank lines for visual separation
|
|
262
|
+
assert "\n\n" in output
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# Integration tests
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def test_dyslexia_full_pipeline():
|
|
269
|
+
"""Full pipeline test for dyslexia profile."""
|
|
270
|
+
base = AssistResult(
|
|
271
|
+
anchored_id="DEPLOY.CONFIG.MISSING",
|
|
272
|
+
confidence="High",
|
|
273
|
+
safest_next_step="Check the CLI configuration file (located in /etc/app).",
|
|
274
|
+
plan=[
|
|
275
|
+
"Verify the JSON config exists (optional step).",
|
|
276
|
+
"Run: deploy check --dry-run",
|
|
277
|
+
"See above for API endpoint details.",
|
|
278
|
+
],
|
|
279
|
+
next_safe_commands=["deploy check --dry-run"],
|
|
280
|
+
notes=["*Important*: This is critical."],
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
transformed = apply_dyslexia(base)
|
|
284
|
+
output = render_dyslexia(transformed)
|
|
285
|
+
|
|
286
|
+
# Header present
|
|
287
|
+
assert "ASSIST (Dyslexia):" in output
|
|
288
|
+
|
|
289
|
+
# ID preserved
|
|
290
|
+
assert "DEPLOY.CONFIG.MISSING" in output
|
|
291
|
+
|
|
292
|
+
# Confidence preserved
|
|
293
|
+
assert "Confidence: High" in output
|
|
294
|
+
|
|
295
|
+
# No parentheticals in content (header "(Dyslexia)" is ok)
|
|
296
|
+
# Check that plan steps and notes don't have parentheticals
|
|
297
|
+
for step in transformed.plan:
|
|
298
|
+
assert "(" not in step
|
|
299
|
+
assert ")" not in step
|
|
300
|
+
assert "(" not in transformed.safest_next_step
|
|
301
|
+
assert ")" not in transformed.safest_next_step
|
|
302
|
+
|
|
303
|
+
# No visual refs
|
|
304
|
+
assert "above" not in output.lower()
|
|
305
|
+
|
|
306
|
+
# No symbolic emphasis
|
|
307
|
+
assert "*" not in output
|
|
308
|
+
|
|
309
|
+
# Step numbering
|
|
310
|
+
assert "- Step 1:" in output
|
|
311
|
+
|
|
312
|
+
# Command shown
|
|
313
|
+
assert "deploy check --dry-run" in output
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def test_dyslexia_low_confidence():
|
|
317
|
+
"""Dyslexia profile with Low confidence."""
|
|
318
|
+
base = AssistResult(
|
|
319
|
+
anchored_id=None,
|
|
320
|
+
confidence="Low",
|
|
321
|
+
safest_next_step="Review the output.",
|
|
322
|
+
plan=["Step 1.", "Step 2."],
|
|
323
|
+
next_safe_commands=["cmd --dry-run"],
|
|
324
|
+
notes=["No ID found."],
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
transformed = apply_dyslexia(base)
|
|
328
|
+
output = render_dyslexia(transformed)
|
|
329
|
+
|
|
330
|
+
# Confidence preserved
|
|
331
|
+
assert "Confidence: Low" in output
|
|
332
|
+
|
|
333
|
+
# No command on Low confidence
|
|
334
|
+
assert "Next safe command:" not in output
|
|
335
|
+
|
|
336
|
+
# ID shows as none
|
|
337
|
+
assert "Anchored ID: none" in output
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Tests for the explain command (cli.error.v0.1 JSON)."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from a11y_assist.from_cli_error import (
|
|
8
|
+
CliErrorValidationError,
|
|
9
|
+
assist_from_cli_error,
|
|
10
|
+
load_cli_error,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
FIX = Path(__file__).parent / "fixtures"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestLoadCliError:
|
|
17
|
+
"""Tests for loading cli.error.v0.1 JSON files."""
|
|
18
|
+
|
|
19
|
+
def test_load_valid_array_format(self):
|
|
20
|
+
"""Load valid JSON with array format for what/why/fix."""
|
|
21
|
+
obj = load_cli_error(str(FIX / "cli_error_good.json"))
|
|
22
|
+
assert obj["code"] == "PAY001"
|
|
23
|
+
assert obj["id"] == "PAY.EXPORT.SFTP.AUTH"
|
|
24
|
+
|
|
25
|
+
def test_load_valid_string_format(self):
|
|
26
|
+
"""Load valid JSON with string format for what/why/fix."""
|
|
27
|
+
obj = load_cli_error(str(FIX / "cli_error_string_format.json"))
|
|
28
|
+
assert obj["code"] == "CFG001"
|
|
29
|
+
assert obj["what"] == "Configuration file missing"
|
|
30
|
+
|
|
31
|
+
def test_load_missing_code_raises(self):
|
|
32
|
+
"""Missing code field raises validation error."""
|
|
33
|
+
with pytest.raises(CliErrorValidationError) as exc_info:
|
|
34
|
+
load_cli_error(str(FIX / "cli_error_missing_id.json"))
|
|
35
|
+
assert "code" in str(exc_info.value.errors)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TestAssistFromCliError:
|
|
39
|
+
"""Tests for generating assist from cli.error.v0.1."""
|
|
40
|
+
|
|
41
|
+
def test_explain_from_valid_cli_error(self):
|
|
42
|
+
"""Generate high-confidence assist from valid JSON."""
|
|
43
|
+
obj = load_cli_error(str(FIX / "cli_error_good.json"))
|
|
44
|
+
res = assist_from_cli_error(obj)
|
|
45
|
+
assert res.confidence == "High"
|
|
46
|
+
assert res.anchored_id == "PAY.EXPORT.SFTP.AUTH"
|
|
47
|
+
assert res.plan
|
|
48
|
+
assert len(res.plan) >= 1
|
|
49
|
+
|
|
50
|
+
def test_explain_uses_code_as_fallback_id(self):
|
|
51
|
+
"""Use code field as fallback when id is not present."""
|
|
52
|
+
obj = load_cli_error(str(FIX / "cli_error_string_format.json"))
|
|
53
|
+
res = assist_from_cli_error(obj)
|
|
54
|
+
assert res.anchored_id == "CFG001"
|
|
55
|
+
|
|
56
|
+
def test_plan_contains_fix_steps(self):
|
|
57
|
+
"""Plan is built from fix field."""
|
|
58
|
+
obj = load_cli_error(str(FIX / "cli_error_good.json"))
|
|
59
|
+
res = assist_from_cli_error(obj)
|
|
60
|
+
assert "Verify credentials." in res.plan
|
|
61
|
+
|
|
62
|
+
def test_safe_commands_extracted(self):
|
|
63
|
+
"""SAFE commands extracted from fix with --dry-run."""
|
|
64
|
+
obj = load_cli_error(str(FIX / "cli_error_good.json"))
|
|
65
|
+
res = assist_from_cli_error(obj)
|
|
66
|
+
# Should find the dry-run command
|
|
67
|
+
dry_run_cmds = [c for c in res.next_safe_commands if "--dry-run" in c]
|
|
68
|
+
assert len(dry_run_cmds) >= 1
|
|
69
|
+
|
|
70
|
+
def test_notes_include_original_title(self):
|
|
71
|
+
"""Notes include the original title."""
|
|
72
|
+
obj = load_cli_error(str(FIX / "cli_error_good.json"))
|
|
73
|
+
res = assist_from_cli_error(obj)
|
|
74
|
+
assert any("Payment export failed" in n for n in res.notes)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Golden fixture tests for deterministic output verification.
|
|
2
|
+
|
|
3
|
+
Golden tests ensure that profile outputs remain stable and any changes
|
|
4
|
+
are explicit and reviewable. These are the memory of the engine.
|
|
5
|
+
|
|
6
|
+
Rules for updating golden files:
|
|
7
|
+
1. The change must be intentional
|
|
8
|
+
2. ENGINE.md must still hold
|
|
9
|
+
3. Guard invariants must still pass
|
|
10
|
+
4. Commit message must explain why output changed
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from a11y_assist.from_cli_error import load_cli_error, assist_from_cli_error
|
|
18
|
+
from a11y_assist.profiles import (
|
|
19
|
+
apply_cognitive_load,
|
|
20
|
+
apply_dyslexia,
|
|
21
|
+
apply_plain_language,
|
|
22
|
+
apply_screen_reader,
|
|
23
|
+
render_cognitive_load,
|
|
24
|
+
render_dyslexia,
|
|
25
|
+
render_plain_language,
|
|
26
|
+
render_screen_reader,
|
|
27
|
+
)
|
|
28
|
+
from a11y_assist.render import render_assist
|
|
29
|
+
|
|
30
|
+
FIXTURES_DIR = Path(__file__).parent / "fixtures"
|
|
31
|
+
BASE_INPUTS = FIXTURES_DIR / "base_inputs"
|
|
32
|
+
EXPECTED = FIXTURES_DIR / "expected"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def load_fixture(path: Path) -> str:
|
|
36
|
+
"""Load a fixture file as string."""
|
|
37
|
+
return path.read_text(encoding="utf-8")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def load_json_input(name: str):
|
|
41
|
+
"""Load and parse a JSON input fixture."""
|
|
42
|
+
return load_cli_error(str(BASE_INPUTS / name))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TestGoldenHighConfidence:
|
|
46
|
+
"""Golden tests for high-confidence cli.error.v0.1 input."""
|
|
47
|
+
|
|
48
|
+
@pytest.fixture
|
|
49
|
+
def base_result(self):
|
|
50
|
+
"""Load base AssistResult from cli_error_high.json."""
|
|
51
|
+
obj = load_json_input("cli_error_high.json")
|
|
52
|
+
return assist_from_cli_error(obj)
|
|
53
|
+
|
|
54
|
+
def test_lowvision_golden(self, base_result):
|
|
55
|
+
"""Lowvision profile output must match golden fixture."""
|
|
56
|
+
# Lowvision is the default, no transform needed
|
|
57
|
+
rendered = render_assist(base_result)
|
|
58
|
+
expected = load_fixture(EXPECTED / "lowvision_high.txt")
|
|
59
|
+
assert rendered.strip() == expected.strip()
|
|
60
|
+
|
|
61
|
+
def test_cognitive_load_golden(self, base_result):
|
|
62
|
+
"""Cognitive-load profile output must match golden fixture."""
|
|
63
|
+
transformed = apply_cognitive_load(base_result)
|
|
64
|
+
rendered = render_cognitive_load(transformed)
|
|
65
|
+
expected = load_fixture(EXPECTED / "cognitive_load_high.txt")
|
|
66
|
+
assert rendered.strip() == expected.strip()
|
|
67
|
+
|
|
68
|
+
def test_screen_reader_golden(self, base_result):
|
|
69
|
+
"""Screen-reader profile output must match golden fixture."""
|
|
70
|
+
transformed = apply_screen_reader(base_result)
|
|
71
|
+
rendered = render_screen_reader(transformed)
|
|
72
|
+
expected = load_fixture(EXPECTED / "screen_reader_high.txt")
|
|
73
|
+
assert rendered.strip() == expected.strip()
|
|
74
|
+
|
|
75
|
+
def test_dyslexia_golden(self, base_result):
|
|
76
|
+
"""Dyslexia profile output must match golden fixture."""
|
|
77
|
+
transformed = apply_dyslexia(base_result)
|
|
78
|
+
rendered = render_dyslexia(transformed)
|
|
79
|
+
expected = load_fixture(EXPECTED / "dyslexia_high.txt")
|
|
80
|
+
assert rendered.strip() == expected.strip()
|
|
81
|
+
|
|
82
|
+
def test_plain_language_golden(self, base_result):
|
|
83
|
+
"""Plain-language profile output must match golden fixture."""
|
|
84
|
+
transformed = apply_plain_language(base_result)
|
|
85
|
+
rendered = render_plain_language(transformed)
|
|
86
|
+
expected = load_fixture(EXPECTED / "plain_language_high.txt")
|
|
87
|
+
assert rendered.strip() == expected.strip()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TestGoldenDeterminism:
|
|
91
|
+
"""Verify determinism: same input always produces same output."""
|
|
92
|
+
|
|
93
|
+
def test_multiple_runs_identical(self):
|
|
94
|
+
"""Running the same input multiple times must produce identical output."""
|
|
95
|
+
obj = load_json_input("cli_error_high.json")
|
|
96
|
+
|
|
97
|
+
outputs = []
|
|
98
|
+
for _ in range(5):
|
|
99
|
+
result = assist_from_cli_error(obj)
|
|
100
|
+
transformed = apply_cognitive_load(result)
|
|
101
|
+
rendered = render_cognitive_load(transformed)
|
|
102
|
+
outputs.append(rendered)
|
|
103
|
+
|
|
104
|
+
# All outputs must be identical
|
|
105
|
+
assert len(set(outputs)) == 1, "Output must be deterministic"
|
|
106
|
+
|
|
107
|
+
def test_all_profiles_deterministic(self):
|
|
108
|
+
"""All profiles must produce deterministic output."""
|
|
109
|
+
obj = load_json_input("cli_error_high.json")
|
|
110
|
+
|
|
111
|
+
profiles = [
|
|
112
|
+
(lambda r: r, render_assist),
|
|
113
|
+
(apply_cognitive_load, render_cognitive_load),
|
|
114
|
+
(apply_screen_reader, render_screen_reader),
|
|
115
|
+
(apply_dyslexia, render_dyslexia),
|
|
116
|
+
(apply_plain_language, render_plain_language),
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
for transform, renderer in profiles:
|
|
120
|
+
outputs = []
|
|
121
|
+
for _ in range(3):
|
|
122
|
+
result = assist_from_cli_error(obj)
|
|
123
|
+
transformed = transform(result)
|
|
124
|
+
rendered = renderer(transformed)
|
|
125
|
+
outputs.append(rendered)
|
|
126
|
+
|
|
127
|
+
assert len(set(outputs)) == 1, f"Profile must be deterministic"
|