@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,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
|
+
)
|