@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,599 @@
|
|
|
1
|
+
"""CLI entry point for a11y-assist.
|
|
2
|
+
|
|
3
|
+
Commands:
|
|
4
|
+
- explain: High-confidence assist from cli.error.v0.1 JSON
|
|
5
|
+
- triage: Best-effort assist from raw text
|
|
6
|
+
- last: Assist from ~/.a11y-assist/last.log
|
|
7
|
+
- assist-run: Wrapper that captures output for `last`
|
|
8
|
+
- ingest: Import findings from a11y-evidence-engine
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from dataclasses import replace
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Callable, List, Optional, Set, Tuple
|
|
19
|
+
|
|
20
|
+
import click
|
|
21
|
+
|
|
22
|
+
from . import __version__
|
|
23
|
+
from .from_cli_error import (
|
|
24
|
+
CliErrorValidationError,
|
|
25
|
+
assist_from_cli_error,
|
|
26
|
+
load_cli_error,
|
|
27
|
+
)
|
|
28
|
+
from .guard import GuardViolation, get_guard_context, validate_profile_transform
|
|
29
|
+
from .methods import (
|
|
30
|
+
METHOD_GUARD_VALIDATE,
|
|
31
|
+
METHOD_NORMALIZE_RAW_TEXT,
|
|
32
|
+
METHOD_PROFILE_COGNITIVE_LOAD,
|
|
33
|
+
METHOD_PROFILE_DYSLEXIA,
|
|
34
|
+
METHOD_PROFILE_LOWVISION,
|
|
35
|
+
METHOD_PROFILE_PLAIN_LANGUAGE,
|
|
36
|
+
METHOD_PROFILE_SCREEN_READER,
|
|
37
|
+
with_method,
|
|
38
|
+
)
|
|
39
|
+
from .parse_raw import parse_raw
|
|
40
|
+
from .profiles import (
|
|
41
|
+
apply_cognitive_load,
|
|
42
|
+
apply_dyslexia,
|
|
43
|
+
apply_plain_language,
|
|
44
|
+
apply_screen_reader,
|
|
45
|
+
render_cognitive_load,
|
|
46
|
+
render_dyslexia,
|
|
47
|
+
render_plain_language,
|
|
48
|
+
render_screen_reader,
|
|
49
|
+
)
|
|
50
|
+
from .render import AssistResult, Confidence, Evidence, render_assist, to_response_dict
|
|
51
|
+
from .storage import read_last_log, write_last_log
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def output_result(
|
|
55
|
+
rendered: str,
|
|
56
|
+
result: AssistResult,
|
|
57
|
+
json_response: bool,
|
|
58
|
+
json_out: Optional[str],
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Output the result according to flags.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
rendered: The rendered text output
|
|
64
|
+
result: The AssistResult (for JSON serialization)
|
|
65
|
+
json_response: If True, print JSON instead of rendered text
|
|
66
|
+
json_out: If set, write JSON to this path (in addition to rendered output)
|
|
67
|
+
"""
|
|
68
|
+
if json_response:
|
|
69
|
+
# JSON to stdout instead of rendered text
|
|
70
|
+
click.echo(json.dumps(to_response_dict(result), indent=2))
|
|
71
|
+
else:
|
|
72
|
+
# Rendered text to stdout (default)
|
|
73
|
+
click.echo(rendered, nl=False)
|
|
74
|
+
|
|
75
|
+
# Write JSON to file if requested (regardless of json_response)
|
|
76
|
+
if json_out:
|
|
77
|
+
Path(json_out).write_text(
|
|
78
|
+
json.dumps(to_response_dict(result), indent=2),
|
|
79
|
+
encoding="utf-8",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Profile registry
|
|
83
|
+
PROFILE_CHOICES = [
|
|
84
|
+
"lowvision",
|
|
85
|
+
"cognitive-load",
|
|
86
|
+
"screen-reader",
|
|
87
|
+
"dyslexia",
|
|
88
|
+
"plain-language",
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_renderer(profile: str) -> Callable[[AssistResult], str]:
|
|
93
|
+
"""Get the renderer function for a profile."""
|
|
94
|
+
if profile == "cognitive-load":
|
|
95
|
+
return render_cognitive_load
|
|
96
|
+
if profile == "screen-reader":
|
|
97
|
+
return render_screen_reader
|
|
98
|
+
if profile == "dyslexia":
|
|
99
|
+
return render_dyslexia
|
|
100
|
+
if profile == "plain-language":
|
|
101
|
+
return render_plain_language
|
|
102
|
+
return render_assist
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def apply_profile(result: AssistResult, profile: str) -> AssistResult:
|
|
106
|
+
"""Apply profile transformation to result and add method ID."""
|
|
107
|
+
if profile == "cognitive-load":
|
|
108
|
+
transformed = apply_cognitive_load(result)
|
|
109
|
+
return with_method(transformed, METHOD_PROFILE_COGNITIVE_LOAD)
|
|
110
|
+
if profile == "screen-reader":
|
|
111
|
+
transformed = apply_screen_reader(result)
|
|
112
|
+
return with_method(transformed, METHOD_PROFILE_SCREEN_READER)
|
|
113
|
+
if profile == "dyslexia":
|
|
114
|
+
transformed = apply_dyslexia(result)
|
|
115
|
+
return with_method(transformed, METHOD_PROFILE_DYSLEXIA)
|
|
116
|
+
if profile == "plain-language":
|
|
117
|
+
transformed = apply_plain_language(result)
|
|
118
|
+
return with_method(transformed, METHOD_PROFILE_PLAIN_LANGUAGE)
|
|
119
|
+
# Default: lowvision (no transform, just add method)
|
|
120
|
+
return with_method(result, METHOD_PROFILE_LOWVISION)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def render_with_profile_guarded(
|
|
124
|
+
base_text: str,
|
|
125
|
+
base_result: AssistResult,
|
|
126
|
+
profile: str,
|
|
127
|
+
input_kind: str,
|
|
128
|
+
) -> str:
|
|
129
|
+
"""Transform and render result according to profile, with guard validation.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
base_text: Original input text for content support checking
|
|
133
|
+
base_result: Base AssistResult before transformation
|
|
134
|
+
profile: Profile name to apply
|
|
135
|
+
input_kind: Type of input (cli_error_json, raw_text, last_log)
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Rendered output string
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
GuardViolation: If profile transform violates invariants
|
|
142
|
+
"""
|
|
143
|
+
# Apply profile transformation (adds profile method ID)
|
|
144
|
+
transformed = apply_profile(base_result, profile)
|
|
145
|
+
|
|
146
|
+
# Get allowed commands from base result
|
|
147
|
+
allowed_commands: Set[str] = set(base_result.next_safe_commands)
|
|
148
|
+
|
|
149
|
+
# Create guard context
|
|
150
|
+
ctx = get_guard_context(
|
|
151
|
+
profile=profile,
|
|
152
|
+
confidence=base_result.confidence,
|
|
153
|
+
input_kind=input_kind,
|
|
154
|
+
allowed_commands=allowed_commands,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Validate the transformation
|
|
158
|
+
validate_profile_transform(base_text, base_result, transformed, ctx)
|
|
159
|
+
|
|
160
|
+
# Add guard method ID after validation passes
|
|
161
|
+
transformed = with_method(transformed, METHOD_GUARD_VALIDATE)
|
|
162
|
+
|
|
163
|
+
# Render (metadata is not rendered, only stored in result)
|
|
164
|
+
renderer = get_renderer(profile)
|
|
165
|
+
return renderer(transformed)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _handle_guard_violation(e: GuardViolation) -> None:
|
|
169
|
+
"""Handle a guard violation by printing error and exiting."""
|
|
170
|
+
click.echo("[ERROR] A11Y.ASSIST.ENGINE.GUARD.FAIL", err=True)
|
|
171
|
+
click.echo("", err=True)
|
|
172
|
+
click.echo("What:", err=True)
|
|
173
|
+
click.echo(" A profile produced output that violates engine safety rules.", err=True)
|
|
174
|
+
click.echo("", err=True)
|
|
175
|
+
click.echo("Why:", err=True)
|
|
176
|
+
click.echo(" This indicates a bug in a profile transform or renderer.", err=True)
|
|
177
|
+
click.echo("", err=True)
|
|
178
|
+
click.echo("Fix:", err=True)
|
|
179
|
+
click.echo(" Run tests; open an issue; include profile name and guard codes.", err=True)
|
|
180
|
+
click.echo("", err=True)
|
|
181
|
+
click.echo("Guard codes:", err=True)
|
|
182
|
+
for issue in e.issues:
|
|
183
|
+
click.echo(f" - {issue.code}: {issue.message}", err=True)
|
|
184
|
+
raise SystemExit(2)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
|
|
188
|
+
@click.version_option(__version__)
|
|
189
|
+
def main():
|
|
190
|
+
"""a11y-assist: low-vision-first assistant for CLI failures (v0.1 non-interactive)."""
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@main.command("explain")
|
|
195
|
+
@click.option(
|
|
196
|
+
"--json",
|
|
197
|
+
"json_path",
|
|
198
|
+
required=True,
|
|
199
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
200
|
+
help="Path to cli.error.v0.1 JSON file.",
|
|
201
|
+
)
|
|
202
|
+
@click.option(
|
|
203
|
+
"--profile",
|
|
204
|
+
type=click.Choice(PROFILE_CHOICES),
|
|
205
|
+
default="lowvision",
|
|
206
|
+
help="Accessibility profile (default: lowvision).",
|
|
207
|
+
)
|
|
208
|
+
@click.option(
|
|
209
|
+
"--json-response",
|
|
210
|
+
"json_response",
|
|
211
|
+
is_flag=True,
|
|
212
|
+
help="Output assist.response.v0.1 JSON instead of rendered text.",
|
|
213
|
+
)
|
|
214
|
+
@click.option(
|
|
215
|
+
"--json-out",
|
|
216
|
+
"json_out",
|
|
217
|
+
type=click.Path(dir_okay=False),
|
|
218
|
+
help="Write assist.response.v0.1 JSON to file (in addition to rendered output).",
|
|
219
|
+
)
|
|
220
|
+
def explain_cmd(json_path: str, profile: str, json_response: bool, json_out: Optional[str]):
|
|
221
|
+
"""Explain a structured cli.error.v0.1 JSON message."""
|
|
222
|
+
try:
|
|
223
|
+
obj = load_cli_error(json_path)
|
|
224
|
+
result = assist_from_cli_error(obj)
|
|
225
|
+
|
|
226
|
+
# Read the original JSON for content support checking
|
|
227
|
+
with open(json_path) as f:
|
|
228
|
+
base_text = f.read()
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
output = render_with_profile_guarded(
|
|
232
|
+
base_text, result, profile, "cli_error_json"
|
|
233
|
+
)
|
|
234
|
+
# Get the transformed result for JSON output
|
|
235
|
+
transformed = apply_profile(result, profile)
|
|
236
|
+
transformed = with_method(transformed, METHOD_GUARD_VALIDATE)
|
|
237
|
+
output_result(output, transformed, json_response, json_out)
|
|
238
|
+
except GuardViolation as e:
|
|
239
|
+
_handle_guard_violation(e)
|
|
240
|
+
|
|
241
|
+
except CliErrorValidationError as e:
|
|
242
|
+
# Low confidence: we couldn't validate
|
|
243
|
+
res = AssistResult(
|
|
244
|
+
anchored_id=None,
|
|
245
|
+
confidence="Low",
|
|
246
|
+
safest_next_step="Emit a valid cli.error.v0.1 JSON message and retry.",
|
|
247
|
+
plan=[
|
|
248
|
+
"Validate your JSON output against cli.error.v0.1.",
|
|
249
|
+
"Include an (ID: NAMESPACE.CATEGORY.DETAIL) field.",
|
|
250
|
+
"Ensure What/Why/Fix are present for WARN/ERROR.",
|
|
251
|
+
],
|
|
252
|
+
next_safe_commands=[],
|
|
253
|
+
notes=["Validation errors (first 5): " + "; ".join(e.errors[:5])],
|
|
254
|
+
)
|
|
255
|
+
# For validation errors, base_text is the error message itself
|
|
256
|
+
base_text = "; ".join(e.errors)
|
|
257
|
+
try:
|
|
258
|
+
output = render_with_profile_guarded(
|
|
259
|
+
base_text, res, profile, "cli_error_json"
|
|
260
|
+
)
|
|
261
|
+
transformed = apply_profile(res, profile)
|
|
262
|
+
transformed = with_method(transformed, METHOD_GUARD_VALIDATE)
|
|
263
|
+
output_result(output, transformed, json_response, json_out)
|
|
264
|
+
except GuardViolation as ge:
|
|
265
|
+
_handle_guard_violation(ge)
|
|
266
|
+
raise SystemExit(2)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@main.command("triage")
|
|
270
|
+
@click.option(
|
|
271
|
+
"--stdin",
|
|
272
|
+
"use_stdin",
|
|
273
|
+
is_flag=True,
|
|
274
|
+
help="Read raw CLI output from stdin.",
|
|
275
|
+
)
|
|
276
|
+
@click.option(
|
|
277
|
+
"--profile",
|
|
278
|
+
type=click.Choice(PROFILE_CHOICES),
|
|
279
|
+
default="lowvision",
|
|
280
|
+
help="Accessibility profile (default: lowvision).",
|
|
281
|
+
)
|
|
282
|
+
@click.option(
|
|
283
|
+
"--json-response",
|
|
284
|
+
"json_response",
|
|
285
|
+
is_flag=True,
|
|
286
|
+
help="Output assist.response.v0.1 JSON instead of rendered text.",
|
|
287
|
+
)
|
|
288
|
+
@click.option(
|
|
289
|
+
"--json-out",
|
|
290
|
+
"json_out",
|
|
291
|
+
type=click.Path(dir_okay=False),
|
|
292
|
+
help="Write assist.response.v0.1 JSON to file (in addition to rendered output).",
|
|
293
|
+
)
|
|
294
|
+
def triage_cmd(use_stdin: bool, profile: str, json_response: bool, json_out: Optional[str]):
|
|
295
|
+
"""Triage raw CLI output (best effort)."""
|
|
296
|
+
if not use_stdin:
|
|
297
|
+
click.echo("Use: a11y-assist triage --stdin", err=True)
|
|
298
|
+
raise SystemExit(2)
|
|
299
|
+
|
|
300
|
+
text = sys.stdin.read()
|
|
301
|
+
err_id, status, blocks = parse_raw(text)
|
|
302
|
+
|
|
303
|
+
notes: List[str] = []
|
|
304
|
+
confidence: Confidence = "Low"
|
|
305
|
+
if err_id:
|
|
306
|
+
confidence = "Medium"
|
|
307
|
+
else:
|
|
308
|
+
notes.append("No (ID: ...) found. Emit cli.error.v0.1 for high-confidence assist.")
|
|
309
|
+
|
|
310
|
+
safest = "Follow the tool's Fix steps, starting with the least risky check."
|
|
311
|
+
plan: List[str] = []
|
|
312
|
+
|
|
313
|
+
fix_lines = blocks.get("Fix:", [])
|
|
314
|
+
if fix_lines:
|
|
315
|
+
plan = fix_lines[:]
|
|
316
|
+
else:
|
|
317
|
+
plan = [
|
|
318
|
+
"Re-run the command with increased verbosity/logging.",
|
|
319
|
+
"Update the tool to emit (ID: ...) and What/Why/Fix blocks.",
|
|
320
|
+
"If this is your tool, adopt cli.error.v0.1 JSON output.",
|
|
321
|
+
]
|
|
322
|
+
|
|
323
|
+
# Build evidence for raw text
|
|
324
|
+
evidence: List[Evidence] = []
|
|
325
|
+
if fix_lines:
|
|
326
|
+
evidence.append(Evidence(field="safest_next_step", source="raw_text:Fix:1"))
|
|
327
|
+
for i, _ in enumerate(plan):
|
|
328
|
+
evidence.append(Evidence(field=f"plan[{i}]", source=f"raw_text:Fix:{i+1}"))
|
|
329
|
+
|
|
330
|
+
safe_cmds = [line for line in plan if "--dry-run" in line][:3]
|
|
331
|
+
for i, cmd in enumerate(safe_cmds):
|
|
332
|
+
# Find which fix line it came from
|
|
333
|
+
for j, fix_line in enumerate(plan):
|
|
334
|
+
if cmd == fix_line:
|
|
335
|
+
evidence.append(
|
|
336
|
+
Evidence(field=f"next_safe_commands[{i}]", source=f"raw_text:Fix:{j+1}")
|
|
337
|
+
)
|
|
338
|
+
break
|
|
339
|
+
|
|
340
|
+
res = AssistResult(
|
|
341
|
+
anchored_id=err_id,
|
|
342
|
+
confidence=confidence,
|
|
343
|
+
safest_next_step=safest,
|
|
344
|
+
plan=plan,
|
|
345
|
+
next_safe_commands=safe_cmds,
|
|
346
|
+
notes=notes,
|
|
347
|
+
methods_applied=(METHOD_NORMALIZE_RAW_TEXT,),
|
|
348
|
+
evidence=tuple(evidence),
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
try:
|
|
352
|
+
output = render_with_profile_guarded(text, res, profile, "raw_text")
|
|
353
|
+
transformed = apply_profile(res, profile)
|
|
354
|
+
transformed = with_method(transformed, METHOD_GUARD_VALIDATE)
|
|
355
|
+
output_result(output, transformed, json_response, json_out)
|
|
356
|
+
except GuardViolation as e:
|
|
357
|
+
_handle_guard_violation(e)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@main.command("last")
|
|
361
|
+
@click.option(
|
|
362
|
+
"--profile",
|
|
363
|
+
type=click.Choice(PROFILE_CHOICES),
|
|
364
|
+
default="lowvision",
|
|
365
|
+
help="Accessibility profile (default: lowvision).",
|
|
366
|
+
)
|
|
367
|
+
@click.option(
|
|
368
|
+
"--json-response",
|
|
369
|
+
"json_response",
|
|
370
|
+
is_flag=True,
|
|
371
|
+
help="Output assist.response.v0.1 JSON instead of rendered text.",
|
|
372
|
+
)
|
|
373
|
+
@click.option(
|
|
374
|
+
"--json-out",
|
|
375
|
+
"json_out",
|
|
376
|
+
type=click.Path(dir_okay=False),
|
|
377
|
+
help="Write assist.response.v0.1 JSON to file (in addition to rendered output).",
|
|
378
|
+
)
|
|
379
|
+
def last_cmd(profile: str, json_response: bool, json_out: Optional[str]):
|
|
380
|
+
"""Assist using the last captured log (~/.a11y-assist/last.log)."""
|
|
381
|
+
text = read_last_log()
|
|
382
|
+
if not text.strip():
|
|
383
|
+
res = AssistResult(
|
|
384
|
+
anchored_id=None,
|
|
385
|
+
confidence="Low",
|
|
386
|
+
safest_next_step="Run a command via assist-run or provide input via triage --stdin.",
|
|
387
|
+
plan=["Try: assist-run <your-command>", "Then: a11y-assist last"],
|
|
388
|
+
next_safe_commands=[],
|
|
389
|
+
notes=["No last.log found."],
|
|
390
|
+
)
|
|
391
|
+
# For empty last log, use the error message as base text
|
|
392
|
+
base_text = "No last.log found. Run assist-run command."
|
|
393
|
+
try:
|
|
394
|
+
output = render_with_profile_guarded(base_text, res, profile, "last_log")
|
|
395
|
+
transformed = apply_profile(res, profile)
|
|
396
|
+
transformed = with_method(transformed, METHOD_GUARD_VALIDATE)
|
|
397
|
+
output_result(output, transformed, json_response, json_out)
|
|
398
|
+
except GuardViolation as e:
|
|
399
|
+
_handle_guard_violation(e)
|
|
400
|
+
raise SystemExit(2)
|
|
401
|
+
|
|
402
|
+
err_id, status, blocks = parse_raw(text)
|
|
403
|
+
confidence: Confidence = "Medium" if err_id else "Low"
|
|
404
|
+
notes: List[str] = [] if err_id else ["No (ID: ...) found in last.log."]
|
|
405
|
+
|
|
406
|
+
fix_lines = blocks.get("Fix:", [])
|
|
407
|
+
plan: List[str] = fix_lines or [
|
|
408
|
+
"Re-run with verbosity.",
|
|
409
|
+
"Adopt cli.error.v0.1 output for high-confidence assistance.",
|
|
410
|
+
]
|
|
411
|
+
|
|
412
|
+
# Build evidence for last.log (same as raw_text)
|
|
413
|
+
evidence: List[Evidence] = []
|
|
414
|
+
if fix_lines:
|
|
415
|
+
evidence.append(Evidence(field="safest_next_step", source="raw_text:Fix:1"))
|
|
416
|
+
for i, _ in enumerate(plan):
|
|
417
|
+
evidence.append(Evidence(field=f"plan[{i}]", source=f"raw_text:Fix:{i+1}"))
|
|
418
|
+
|
|
419
|
+
safe_cmds = [line for line in plan if "--dry-run" in line][:3]
|
|
420
|
+
for i, cmd in enumerate(safe_cmds):
|
|
421
|
+
for j, fix_line in enumerate(plan):
|
|
422
|
+
if cmd == fix_line:
|
|
423
|
+
evidence.append(
|
|
424
|
+
Evidence(field=f"next_safe_commands[{i}]", source=f"raw_text:Fix:{j+1}")
|
|
425
|
+
)
|
|
426
|
+
break
|
|
427
|
+
|
|
428
|
+
res = AssistResult(
|
|
429
|
+
anchored_id=err_id,
|
|
430
|
+
confidence=confidence,
|
|
431
|
+
safest_next_step="Start with the first Fix step. Prefer non-destructive checks.",
|
|
432
|
+
plan=plan,
|
|
433
|
+
next_safe_commands=safe_cmds,
|
|
434
|
+
notes=notes,
|
|
435
|
+
methods_applied=(METHOD_NORMALIZE_RAW_TEXT,),
|
|
436
|
+
evidence=tuple(evidence),
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
output = render_with_profile_guarded(text, res, profile, "last_log")
|
|
441
|
+
transformed = apply_profile(res, profile)
|
|
442
|
+
transformed = with_method(transformed, METHOD_GUARD_VALIDATE)
|
|
443
|
+
output_result(output, transformed, json_response, json_out)
|
|
444
|
+
except GuardViolation as e:
|
|
445
|
+
_handle_guard_violation(e)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def assist_run():
|
|
449
|
+
"""Wrapper entry-point (console_script): captures stdout/stderr to last.log.
|
|
450
|
+
|
|
451
|
+
Usage: assist-run <cmd> [args...]
|
|
452
|
+
"""
|
|
453
|
+
if len(sys.argv) < 2:
|
|
454
|
+
print("Usage: assist-run <command> [args...]", file=sys.stderr)
|
|
455
|
+
raise SystemExit(2)
|
|
456
|
+
|
|
457
|
+
cmd = sys.argv[1:]
|
|
458
|
+
proc = subprocess.run(
|
|
459
|
+
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
|
|
460
|
+
)
|
|
461
|
+
output = proc.stdout or ""
|
|
462
|
+
|
|
463
|
+
# Print original output unchanged
|
|
464
|
+
sys.stdout.write(output)
|
|
465
|
+
|
|
466
|
+
# Save for a11y-assist last
|
|
467
|
+
write_last_log(output)
|
|
468
|
+
|
|
469
|
+
if proc.returncode != 0:
|
|
470
|
+
print("\nTip: run `a11y-assist last` for help", file=sys.stderr)
|
|
471
|
+
|
|
472
|
+
raise SystemExit(proc.returncode)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
@main.command("ingest")
|
|
476
|
+
@click.argument(
|
|
477
|
+
"findings_path",
|
|
478
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
479
|
+
)
|
|
480
|
+
@click.option(
|
|
481
|
+
"--out",
|
|
482
|
+
"out_dir",
|
|
483
|
+
type=click.Path(file_okay=False),
|
|
484
|
+
help="Output directory for derived artifacts (default: alongside findings.json under a11y-assist/).",
|
|
485
|
+
)
|
|
486
|
+
@click.option(
|
|
487
|
+
"--format",
|
|
488
|
+
"output_format",
|
|
489
|
+
type=click.Choice(["text", "json"]),
|
|
490
|
+
default="text",
|
|
491
|
+
help="Output format for stdout (default: text).",
|
|
492
|
+
)
|
|
493
|
+
@click.option(
|
|
494
|
+
"--min-severity",
|
|
495
|
+
type=click.Choice(["info", "warning", "error"]),
|
|
496
|
+
default="info",
|
|
497
|
+
help="Minimum severity to include (default: info).",
|
|
498
|
+
)
|
|
499
|
+
@click.option(
|
|
500
|
+
"--strict",
|
|
501
|
+
is_flag=True,
|
|
502
|
+
help="Fail if evidence_ref files are missing or provenance fails validation.",
|
|
503
|
+
)
|
|
504
|
+
@click.option(
|
|
505
|
+
"--verify-provenance",
|
|
506
|
+
is_flag=True,
|
|
507
|
+
help="Validate each referenced provenance bundle and verify digests.",
|
|
508
|
+
)
|
|
509
|
+
@click.option(
|
|
510
|
+
"--fail-on",
|
|
511
|
+
type=click.Choice(["error", "warning", "never"]),
|
|
512
|
+
default="error",
|
|
513
|
+
help="Exit nonzero if findings exist at/above this severity (default: error).",
|
|
514
|
+
)
|
|
515
|
+
def ingest_cmd(
|
|
516
|
+
findings_path: str,
|
|
517
|
+
out_dir: Optional[str],
|
|
518
|
+
output_format: str,
|
|
519
|
+
min_severity: str,
|
|
520
|
+
strict: bool,
|
|
521
|
+
verify_provenance: bool,
|
|
522
|
+
fail_on: str,
|
|
523
|
+
):
|
|
524
|
+
"""Ingest findings from a11y-evidence-engine.
|
|
525
|
+
|
|
526
|
+
Takes findings.json and produces:
|
|
527
|
+
- ingest-summary.json: Normalized stats and grouping
|
|
528
|
+
- advisories.json: Fix-oriented tasks with evidence links
|
|
529
|
+
"""
|
|
530
|
+
from .ingest import (
|
|
531
|
+
IngestError,
|
|
532
|
+
ingest,
|
|
533
|
+
render_text_summary,
|
|
534
|
+
write_advisories,
|
|
535
|
+
write_ingest_summary,
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
findings = Path(findings_path)
|
|
539
|
+
|
|
540
|
+
# Determine output directory
|
|
541
|
+
if out_dir:
|
|
542
|
+
out = Path(out_dir)
|
|
543
|
+
else:
|
|
544
|
+
out = findings.parent / "a11y-assist"
|
|
545
|
+
|
|
546
|
+
# Run ingest
|
|
547
|
+
try:
|
|
548
|
+
result = ingest(
|
|
549
|
+
findings,
|
|
550
|
+
verify_provenance_flag=(verify_provenance or strict),
|
|
551
|
+
min_severity=min_severity,
|
|
552
|
+
)
|
|
553
|
+
except IngestError as e:
|
|
554
|
+
click.echo(f"Ingest failed: {e}", err=True)
|
|
555
|
+
raise SystemExit(3)
|
|
556
|
+
|
|
557
|
+
# Check strict mode
|
|
558
|
+
if strict:
|
|
559
|
+
if result.provenance_errors:
|
|
560
|
+
click.echo("Provenance verification failed:", err=True)
|
|
561
|
+
for err in result.provenance_errors:
|
|
562
|
+
click.echo(f" - {err}", err=True)
|
|
563
|
+
raise SystemExit(3)
|
|
564
|
+
|
|
565
|
+
# Write output files
|
|
566
|
+
write_ingest_summary(result, out / "ingest-summary.json")
|
|
567
|
+
write_advisories(result, out / "advisories.json")
|
|
568
|
+
|
|
569
|
+
# Output to stdout
|
|
570
|
+
if output_format == "json":
|
|
571
|
+
summary = {
|
|
572
|
+
"source_engine": result.source_engine,
|
|
573
|
+
"source_version": result.source_version,
|
|
574
|
+
"ingested_at": result.ingested_at,
|
|
575
|
+
"target": result.target,
|
|
576
|
+
"summary": result.summary,
|
|
577
|
+
"by_rule": result.by_rule,
|
|
578
|
+
"output_dir": str(out),
|
|
579
|
+
}
|
|
580
|
+
if verify_provenance or strict:
|
|
581
|
+
summary["provenance_verified"] = result.provenance_verified
|
|
582
|
+
click.echo(json.dumps(summary, indent=2))
|
|
583
|
+
else:
|
|
584
|
+
click.echo(render_text_summary(result))
|
|
585
|
+
click.echo(f"\nOutput: {out}")
|
|
586
|
+
|
|
587
|
+
# Determine exit code based on --fail-on
|
|
588
|
+
if fail_on == "never":
|
|
589
|
+
raise SystemExit(0)
|
|
590
|
+
|
|
591
|
+
errors = result.summary.get("errors", 0)
|
|
592
|
+
warnings = result.summary.get("warnings", 0)
|
|
593
|
+
|
|
594
|
+
if fail_on == "error" and errors > 0:
|
|
595
|
+
raise SystemExit(2)
|
|
596
|
+
if fail_on == "warning" and (errors > 0 or warnings > 0):
|
|
597
|
+
raise SystemExit(2)
|
|
598
|
+
|
|
599
|
+
raise SystemExit(0)
|