ally-demo-python 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mcp-tool-shop
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.4
2
+ Name: ally-demo-python
3
+ Version: 0.1.0
4
+ Summary: Reference integration for Ally (a11y-assist, a11y-lint, a11y-ci)
5
+ Author-email: mcp-tool-shop <64996768+mcp-tool-shop@users.noreply.github.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/mcp-tool-shop/ally-demo-python
8
+ Project-URL: Repository, https://github.com/mcp-tool-shop/ally-demo-python
9
+ Project-URL: Issues, https://github.com/mcp-tool-shop/ally-demo-python/issues
10
+ Keywords: a11y,accessibility,cli,demo,reference,ally,error-messages,assistive-technology
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: click>=8.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.0; extra == "dev"
26
+ Requires-Dist: a11y-assist; extra == "dev"
27
+ Requires-Dist: a11y-lint; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # Ally Demo (Python)
31
+
32
+ A minimal Python CLI that emits **cli.error.v0.1** (ground truth) and demonstrates the full Ally pipeline:
33
+
34
+ - `a11y-assist` (profiles: lowvision, cognitive-load, screen-reader, dyslexia, plain-language)
35
+ - `a11y-lint` (validate + scan)
36
+ - `a11y-ci` (gate)
37
+
38
+ This repo is intentionally small and boring. It's a reference integration.
39
+
40
+ ## Quickstart
41
+
42
+ ```bash
43
+ python -m venv .venv
44
+ # Windows: .venv\Scripts\activate
45
+ # macOS/Linux: source .venv/bin/activate
46
+
47
+ pip install -e .
48
+ pip install a11y-assist a11y-lint
49
+ ```
50
+
51
+ Run a failing command that emits structured JSON:
52
+
53
+ ```bash
54
+ demo-cli network-timeout --json-out /tmp/cli_error.json || true
55
+ ```
56
+
57
+ Explain the same error using different profiles:
58
+
59
+ ```bash
60
+ a11y-assist explain --json /tmp/cli_error.json --profile lowvision
61
+ a11y-assist explain --json /tmp/cli_error.json --profile cognitive-load
62
+ a11y-assist explain --json /tmp/cli_error.json --profile screen-reader
63
+ a11y-assist explain --json /tmp/cli_error.json --profile dyslexia
64
+ a11y-assist explain --json /tmp/cli_error.json --profile plain-language
65
+ ```
66
+
67
+ Validate the message contract:
68
+
69
+ ```bash
70
+ a11y-lint validate /tmp/cli_error.json
71
+ ```
72
+
73
+ ## Available Error Scenarios
74
+
75
+ The demo CLI includes several error scenarios to demonstrate different patterns:
76
+
77
+ | Command | Error ID | Description |
78
+ |---------|----------|-------------|
79
+ | `demo-cli network-timeout` | DEMO.NETWORK.TIMEOUT | HTTP request timeout |
80
+ | `demo-cli config-missing` | DEMO.CONFIG.MISSING | Missing config file |
81
+ | `demo-cli auth-failed` | DEMO.AUTH.INVALID_TOKEN | Authentication failure |
82
+ | `demo-cli permission-denied` | DEMO.FS.PERMISSION_DENIED | File permission error |
83
+ | `demo-cli validation-error` | DEMO.VALIDATION.SCHEMA | Schema validation failure |
84
+
85
+ Each command:
86
+ - Prints human-readable output to stdout
87
+ - Writes JSON to the specified `--json-out` path
88
+ - Emits JSON to stderr for machine capture
89
+
90
+ ## What "good" looks like
91
+
92
+ The CLI prints a human-readable message in a predictable structure:
93
+
94
+ ```
95
+ [ERROR] Title (ID: ...)
96
+
97
+ What:
98
+ Description of what happened.
99
+
100
+ Why:
101
+ Explanation of why it happened.
102
+
103
+ Fix:
104
+ Steps to resolve the issue.
105
+ Re-run: command --dry-run
106
+ ```
107
+
108
+ The CLI also emits valid `cli.error.v0.1` JSON for machine capture.
109
+
110
+ - `a11y-assist` never rewrites the original output; it adds an ASSIST block.
111
+ - SAFE-only commands are suggested (and only when present in the input).
112
+
113
+ ## Design notes
114
+
115
+ This demo intentionally emits:
116
+
117
+ - A stable `id` namespace (DEMO.*)
118
+ - Fix lines that include a `Re-run:` command containing `--dry-run` (SAFE)
119
+
120
+ That makes it easy to test the whole pipeline end-to-end.
121
+
122
+ ## Adopt Ally in 10 minutes (for tool authors)
123
+
124
+ 1. Emit `cli.error.v0.1` JSON on failure (machine output)
125
+ 2. Print the same content in a predictable text structure (human output)
126
+ 3. Add CI:
127
+ - `a11y-lint validate <message.json>`
128
+ - `a11y-ci gate` for scorecards if you lint text output
129
+ 4. Tell users:
130
+ - `a11y-assist explain --json <file>`
131
+ - or wrapper-first: `assist-run <command>` then `a11y-assist last`
132
+
133
+ ## License
134
+
135
+ MIT
@@ -0,0 +1,106 @@
1
+ # Ally Demo (Python)
2
+
3
+ A minimal Python CLI that emits **cli.error.v0.1** (ground truth) and demonstrates the full Ally pipeline:
4
+
5
+ - `a11y-assist` (profiles: lowvision, cognitive-load, screen-reader, dyslexia, plain-language)
6
+ - `a11y-lint` (validate + scan)
7
+ - `a11y-ci` (gate)
8
+
9
+ This repo is intentionally small and boring. It's a reference integration.
10
+
11
+ ## Quickstart
12
+
13
+ ```bash
14
+ python -m venv .venv
15
+ # Windows: .venv\Scripts\activate
16
+ # macOS/Linux: source .venv/bin/activate
17
+
18
+ pip install -e .
19
+ pip install a11y-assist a11y-lint
20
+ ```
21
+
22
+ Run a failing command that emits structured JSON:
23
+
24
+ ```bash
25
+ demo-cli network-timeout --json-out /tmp/cli_error.json || true
26
+ ```
27
+
28
+ Explain the same error using different profiles:
29
+
30
+ ```bash
31
+ a11y-assist explain --json /tmp/cli_error.json --profile lowvision
32
+ a11y-assist explain --json /tmp/cli_error.json --profile cognitive-load
33
+ a11y-assist explain --json /tmp/cli_error.json --profile screen-reader
34
+ a11y-assist explain --json /tmp/cli_error.json --profile dyslexia
35
+ a11y-assist explain --json /tmp/cli_error.json --profile plain-language
36
+ ```
37
+
38
+ Validate the message contract:
39
+
40
+ ```bash
41
+ a11y-lint validate /tmp/cli_error.json
42
+ ```
43
+
44
+ ## Available Error Scenarios
45
+
46
+ The demo CLI includes several error scenarios to demonstrate different patterns:
47
+
48
+ | Command | Error ID | Description |
49
+ |---------|----------|-------------|
50
+ | `demo-cli network-timeout` | DEMO.NETWORK.TIMEOUT | HTTP request timeout |
51
+ | `demo-cli config-missing` | DEMO.CONFIG.MISSING | Missing config file |
52
+ | `demo-cli auth-failed` | DEMO.AUTH.INVALID_TOKEN | Authentication failure |
53
+ | `demo-cli permission-denied` | DEMO.FS.PERMISSION_DENIED | File permission error |
54
+ | `demo-cli validation-error` | DEMO.VALIDATION.SCHEMA | Schema validation failure |
55
+
56
+ Each command:
57
+ - Prints human-readable output to stdout
58
+ - Writes JSON to the specified `--json-out` path
59
+ - Emits JSON to stderr for machine capture
60
+
61
+ ## What "good" looks like
62
+
63
+ The CLI prints a human-readable message in a predictable structure:
64
+
65
+ ```
66
+ [ERROR] Title (ID: ...)
67
+
68
+ What:
69
+ Description of what happened.
70
+
71
+ Why:
72
+ Explanation of why it happened.
73
+
74
+ Fix:
75
+ Steps to resolve the issue.
76
+ Re-run: command --dry-run
77
+ ```
78
+
79
+ The CLI also emits valid `cli.error.v0.1` JSON for machine capture.
80
+
81
+ - `a11y-assist` never rewrites the original output; it adds an ASSIST block.
82
+ - SAFE-only commands are suggested (and only when present in the input).
83
+
84
+ ## Design notes
85
+
86
+ This demo intentionally emits:
87
+
88
+ - A stable `id` namespace (DEMO.*)
89
+ - Fix lines that include a `Re-run:` command containing `--dry-run` (SAFE)
90
+
91
+ That makes it easy to test the whole pipeline end-to-end.
92
+
93
+ ## Adopt Ally in 10 minutes (for tool authors)
94
+
95
+ 1. Emit `cli.error.v0.1` JSON on failure (machine output)
96
+ 2. Print the same content in a predictable text structure (human output)
97
+ 3. Add CI:
98
+ - `a11y-lint validate <message.json>`
99
+ - `a11y-ci gate` for scorecards if you lint text output
100
+ 4. Tell users:
101
+ - `a11y-assist explain --json <file>`
102
+ - or wrapper-first: `assist-run <command>` then `a11y-assist last`
103
+
104
+ ## License
105
+
106
+ MIT
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.4
2
+ Name: ally-demo-python
3
+ Version: 0.1.0
4
+ Summary: Reference integration for Ally (a11y-assist, a11y-lint, a11y-ci)
5
+ Author-email: mcp-tool-shop <64996768+mcp-tool-shop@users.noreply.github.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/mcp-tool-shop/ally-demo-python
8
+ Project-URL: Repository, https://github.com/mcp-tool-shop/ally-demo-python
9
+ Project-URL: Issues, https://github.com/mcp-tool-shop/ally-demo-python/issues
10
+ Keywords: a11y,accessibility,cli,demo,reference,ally,error-messages,assistive-technology
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: click>=8.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.0; extra == "dev"
26
+ Requires-Dist: a11y-assist; extra == "dev"
27
+ Requires-Dist: a11y-lint; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # Ally Demo (Python)
31
+
32
+ A minimal Python CLI that emits **cli.error.v0.1** (ground truth) and demonstrates the full Ally pipeline:
33
+
34
+ - `a11y-assist` (profiles: lowvision, cognitive-load, screen-reader, dyslexia, plain-language)
35
+ - `a11y-lint` (validate + scan)
36
+ - `a11y-ci` (gate)
37
+
38
+ This repo is intentionally small and boring. It's a reference integration.
39
+
40
+ ## Quickstart
41
+
42
+ ```bash
43
+ python -m venv .venv
44
+ # Windows: .venv\Scripts\activate
45
+ # macOS/Linux: source .venv/bin/activate
46
+
47
+ pip install -e .
48
+ pip install a11y-assist a11y-lint
49
+ ```
50
+
51
+ Run a failing command that emits structured JSON:
52
+
53
+ ```bash
54
+ demo-cli network-timeout --json-out /tmp/cli_error.json || true
55
+ ```
56
+
57
+ Explain the same error using different profiles:
58
+
59
+ ```bash
60
+ a11y-assist explain --json /tmp/cli_error.json --profile lowvision
61
+ a11y-assist explain --json /tmp/cli_error.json --profile cognitive-load
62
+ a11y-assist explain --json /tmp/cli_error.json --profile screen-reader
63
+ a11y-assist explain --json /tmp/cli_error.json --profile dyslexia
64
+ a11y-assist explain --json /tmp/cli_error.json --profile plain-language
65
+ ```
66
+
67
+ Validate the message contract:
68
+
69
+ ```bash
70
+ a11y-lint validate /tmp/cli_error.json
71
+ ```
72
+
73
+ ## Available Error Scenarios
74
+
75
+ The demo CLI includes several error scenarios to demonstrate different patterns:
76
+
77
+ | Command | Error ID | Description |
78
+ |---------|----------|-------------|
79
+ | `demo-cli network-timeout` | DEMO.NETWORK.TIMEOUT | HTTP request timeout |
80
+ | `demo-cli config-missing` | DEMO.CONFIG.MISSING | Missing config file |
81
+ | `demo-cli auth-failed` | DEMO.AUTH.INVALID_TOKEN | Authentication failure |
82
+ | `demo-cli permission-denied` | DEMO.FS.PERMISSION_DENIED | File permission error |
83
+ | `demo-cli validation-error` | DEMO.VALIDATION.SCHEMA | Schema validation failure |
84
+
85
+ Each command:
86
+ - Prints human-readable output to stdout
87
+ - Writes JSON to the specified `--json-out` path
88
+ - Emits JSON to stderr for machine capture
89
+
90
+ ## What "good" looks like
91
+
92
+ The CLI prints a human-readable message in a predictable structure:
93
+
94
+ ```
95
+ [ERROR] Title (ID: ...)
96
+
97
+ What:
98
+ Description of what happened.
99
+
100
+ Why:
101
+ Explanation of why it happened.
102
+
103
+ Fix:
104
+ Steps to resolve the issue.
105
+ Re-run: command --dry-run
106
+ ```
107
+
108
+ The CLI also emits valid `cli.error.v0.1` JSON for machine capture.
109
+
110
+ - `a11y-assist` never rewrites the original output; it adds an ASSIST block.
111
+ - SAFE-only commands are suggested (and only when present in the input).
112
+
113
+ ## Design notes
114
+
115
+ This demo intentionally emits:
116
+
117
+ - A stable `id` namespace (DEMO.*)
118
+ - Fix lines that include a `Re-run:` command containing `--dry-run` (SAFE)
119
+
120
+ That makes it easy to test the whole pipeline end-to-end.
121
+
122
+ ## Adopt Ally in 10 minutes (for tool authors)
123
+
124
+ 1. Emit `cli.error.v0.1` JSON on failure (machine output)
125
+ 2. Print the same content in a predictable text structure (human output)
126
+ 3. Add CI:
127
+ - `a11y-lint validate <message.json>`
128
+ - `a11y-ci gate` for scorecards if you lint text output
129
+ 4. Tell users:
130
+ - `a11y-assist explain --json <file>`
131
+ - or wrapper-first: `assist-run <command>` then `a11y-assist last`
132
+
133
+ ## License
134
+
135
+ MIT
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ ally_demo_python.egg-info/PKG-INFO
5
+ ally_demo_python.egg-info/SOURCES.txt
6
+ ally_demo_python.egg-info/dependency_links.txt
7
+ ally_demo_python.egg-info/entry_points.txt
8
+ ally_demo_python.egg-info/requires.txt
9
+ ally_demo_python.egg-info/top_level.txt
10
+ demo_cli/__init__.py
11
+ demo_cli/cli.py
12
+ demo_cli/errors.py
13
+ tests/test_errors.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ demo-cli = demo_cli.cli:main
@@ -0,0 +1,6 @@
1
+ click>=8.0
2
+
3
+ [dev]
4
+ pytest>=7.0
5
+ a11y-assist
6
+ a11y-lint
@@ -0,0 +1,3 @@
1
+ """Demo CLI that emits cli.error.v0.1 for Ally integration."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,182 @@
1
+ """Demo CLI with multiple failure scenarios.
2
+
3
+ Each command demonstrates a different error pattern while emitting valid
4
+ cli.error.v0.1 JSON for machine capture.
5
+ """
6
+
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ import click
11
+
12
+ from .errors import CliErrorV01, emit_cli_error
13
+
14
+
15
+ def print_human_error(err: CliErrorV01) -> None:
16
+ """Print error in predictable human-readable structure.
17
+
18
+ This is the pattern a11y-lint expects for high accessibility scores.
19
+ """
20
+ click.echo(f"[{err.level}] {err.title} (ID: {err.id})")
21
+ click.echo("")
22
+ click.echo("What:")
23
+ for line in err.what:
24
+ click.echo(f" {line}")
25
+ click.echo("")
26
+ click.echo("Why:")
27
+ for line in err.why:
28
+ click.echo(f" {line}")
29
+ click.echo("")
30
+ click.echo("Fix:")
31
+ for line in err.fix:
32
+ click.echo(f" {line}")
33
+
34
+
35
+ def emit_and_exit(err: CliErrorV01, json_out: str | None) -> None:
36
+ """Emit error to human output, optional file, and stderr JSON."""
37
+ # Human-readable output (stdout)
38
+ print_human_error(err)
39
+
40
+ # Machine-readable JSON
41
+ payload = emit_cli_error(err)
42
+
43
+ # Optional file output
44
+ if json_out:
45
+ Path(json_out).write_text(payload, encoding="utf-8")
46
+ click.echo(f"\nJSON written to: {json_out}", err=True)
47
+
48
+ # Always emit JSON to stderr for machine capture
49
+ print(payload, file=sys.stderr)
50
+
51
+ raise SystemExit(1)
52
+
53
+
54
+ @click.group()
55
+ def main() -> None:
56
+ """Demo CLI for Ally integration.
57
+
58
+ Run any subcommand to see a structured error message.
59
+ Use --json-out to capture cli.error.v0.1 JSON.
60
+ """
61
+ pass
62
+
63
+
64
+ @main.command()
65
+ @click.option("--json-out", type=click.Path(dir_okay=False), help="Write JSON to file")
66
+ def network_timeout(json_out: str | None) -> None:
67
+ """Simulate a network timeout error."""
68
+ err = CliErrorV01(
69
+ level="ERROR",
70
+ code="DEM001",
71
+ id="DEMO.NETWORK.TIMEOUT",
72
+ title="Request timed out",
73
+ what=["The HTTP request did not complete within 30 seconds."],
74
+ why=[
75
+ "The server did not respond before the timeout limit.",
76
+ "This may indicate network congestion or server overload.",
77
+ ],
78
+ fix=[
79
+ "Check your network connection.",
80
+ "Verify the server is reachable: ping api.example.com",
81
+ "Re-run with increased timeout: demo-cli network-timeout --timeout 60 --dry-run",
82
+ ],
83
+ )
84
+ emit_and_exit(err, json_out)
85
+
86
+
87
+ @main.command()
88
+ @click.option("--json-out", type=click.Path(dir_okay=False), help="Write JSON to file")
89
+ def config_missing(json_out: str | None) -> None:
90
+ """Simulate a missing config file error."""
91
+ err = CliErrorV01(
92
+ level="ERROR",
93
+ code="DEM002",
94
+ id="DEMO.CONFIG.MISSING",
95
+ title="Config file not found",
96
+ what=["The configuration file 'config.json' was not found."],
97
+ why=[
98
+ "The tool requires a config file to know which project to use.",
99
+ "The file may have been deleted or never created.",
100
+ ],
101
+ fix=[
102
+ "Create config.json in the current directory.",
103
+ "Copy the template: cp config.example.json config.json",
104
+ "Re-run: demo-cli config-missing --dry-run",
105
+ ],
106
+ )
107
+ emit_and_exit(err, json_out)
108
+
109
+
110
+ @main.command()
111
+ @click.option("--json-out", type=click.Path(dir_okay=False), help="Write JSON to file")
112
+ def auth_failed(json_out: str | None) -> None:
113
+ """Simulate an authentication failure."""
114
+ err = CliErrorV01(
115
+ level="ERROR",
116
+ code="DEM003",
117
+ id="DEMO.AUTH.INVALID_TOKEN",
118
+ title="Authentication failed",
119
+ what=["The API token was rejected by the server."],
120
+ why=[
121
+ "The token may have expired.",
122
+ "The token may lack required permissions.",
123
+ ],
124
+ fix=[
125
+ "Generate a new token at https://example.com/settings/tokens",
126
+ "Set the token: export DEMO_API_TOKEN=<new-token>",
127
+ "Re-run: demo-cli auth-failed --dry-run",
128
+ ],
129
+ )
130
+ emit_and_exit(err, json_out)
131
+
132
+
133
+ @main.command()
134
+ @click.option("--json-out", type=click.Path(dir_okay=False), help="Write JSON to file")
135
+ def permission_denied(json_out: str | None) -> None:
136
+ """Simulate a file permission error."""
137
+ err = CliErrorV01(
138
+ level="ERROR",
139
+ code="DEM004",
140
+ id="DEMO.FS.PERMISSION_DENIED",
141
+ title="Permission denied",
142
+ what=["Cannot write to '/var/log/demo.log'."],
143
+ why=[
144
+ "The current user does not have write permission.",
145
+ "The directory may be owned by root.",
146
+ ],
147
+ fix=[
148
+ "Check permissions: ls -la /var/log/demo.log",
149
+ "Run with appropriate permissions or change the log path.",
150
+ "Re-run: demo-cli permission-denied --log-path ./demo.log --dry-run",
151
+ ],
152
+ )
153
+ emit_and_exit(err, json_out)
154
+
155
+
156
+ @main.command()
157
+ @click.option("--json-out", type=click.Path(dir_okay=False), help="Write JSON to file")
158
+ def validation_error(json_out: str | None) -> None:
159
+ """Simulate a data validation error."""
160
+ err = CliErrorV01(
161
+ level="ERROR",
162
+ code="DEM005",
163
+ id="DEMO.VALIDATION.SCHEMA",
164
+ title="Schema validation failed",
165
+ what=[
166
+ "The input file 'data.json' does not match the expected schema.",
167
+ "Field 'email' is required but missing.",
168
+ ],
169
+ why=[
170
+ "The schema requires all user records to have an email field.",
171
+ "Record at index 3 is missing this field.",
172
+ ],
173
+ fix=[
174
+ "Add the missing 'email' field to record 3.",
175
+ "Validate locally: demo-cli validate data.json --dry-run",
176
+ ],
177
+ )
178
+ emit_and_exit(err, json_out)
179
+
180
+
181
+ if __name__ == "__main__":
182
+ main()
@@ -0,0 +1,52 @@
1
+ """cli.error.v0.1 schema helpers.
2
+
3
+ This module provides a dataclass and helper for emitting valid cli.error.v0.1 JSON.
4
+ Copy this pattern into your own CLIs.
5
+ """
6
+
7
+ from dataclasses import asdict, dataclass
8
+ import json
9
+ import re
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class CliErrorV01:
14
+ """Ground truth error message following cli.error.v0.1 schema.
15
+
16
+ Attributes:
17
+ level: OK, WARN, or ERROR (required)
18
+ code: Error code matching pattern ^[A-Z][A-Z0-9]{1,3}[0-9]{3}$ (e.g., "DEM001")
19
+ id: Alternate dot-namespaced identifier (e.g., "DEMO.NETWORK.TIMEOUT")
20
+ title: Short human-readable summary
21
+ what: List of sentences describing what happened (required)
22
+ why: List of sentences explaining why it happened (required for ERROR)
23
+ fix: List of actionable steps (required for ERROR, include SAFE commands)
24
+ """
25
+
26
+ level: str
27
+ code: str
28
+ id: str
29
+ title: str
30
+ what: list[str]
31
+ why: list[str]
32
+ fix: list[str]
33
+
34
+ def __post_init__(self) -> None:
35
+ """Validate fields."""
36
+ if self.level not in ("OK", "WARN", "ERROR"):
37
+ raise ValueError(f"Invalid level: {self.level}")
38
+ # Code pattern: ^[A-Z][A-Z0-9]{1,3}[0-9]{3}$
39
+ if not re.match(r"^[A-Z][A-Z0-9]{1,3}[0-9]{3}$", self.code):
40
+ raise ValueError(f"Invalid code format: {self.code} (expected e.g., DEM001)")
41
+
42
+
43
+ def emit_cli_error(err: CliErrorV01) -> str:
44
+ """Serialize a CliErrorV01 to JSON string.
45
+
46
+ Args:
47
+ err: The error to serialize.
48
+
49
+ Returns:
50
+ JSON string with 2-space indent.
51
+ """
52
+ return json.dumps(asdict(err), indent=2, ensure_ascii=False)
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ally-demo-python"
7
+ version = "0.1.0"
8
+ description = "Reference integration for Ally (a11y-assist, a11y-lint, a11y-ci)"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "mcp-tool-shop", email = "64996768+mcp-tool-shop@users.noreply.github.com"}
14
+ ]
15
+ keywords = [
16
+ "a11y",
17
+ "accessibility",
18
+ "cli",
19
+ "demo",
20
+ "reference",
21
+ "ally",
22
+ "error-messages",
23
+ "assistive-technology",
24
+ ]
25
+ classifiers = [
26
+ "Development Status :: 4 - Beta",
27
+ "Environment :: Console",
28
+ "Intended Audience :: Developers",
29
+ "License :: OSI Approved :: MIT License",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3.10",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Topic :: Software Development :: Quality Assurance",
35
+ ]
36
+ dependencies = [
37
+ "click>=8.0",
38
+ ]
39
+
40
+ [project.optional-dependencies]
41
+ dev = [
42
+ "pytest>=7.0",
43
+ "a11y-assist",
44
+ "a11y-lint",
45
+ ]
46
+
47
+ [project.scripts]
48
+ demo-cli = "demo_cli.cli:main"
49
+
50
+ [project.urls]
51
+ Homepage = "https://github.com/mcp-tool-shop/ally-demo-python"
52
+ Repository = "https://github.com/mcp-tool-shop/ally-demo-python"
53
+ Issues = "https://github.com/mcp-tool-shop/ally-demo-python/issues"
54
+
55
+ [tool.setuptools.packages.find]
56
+ include = ["demo_cli*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,137 @@
1
+ """Tests for cli.error.v0.1 emission."""
2
+
3
+ import json
4
+
5
+ import pytest
6
+
7
+ from demo_cli.errors import CliErrorV01, emit_cli_error
8
+
9
+
10
+ def test_cli_error_v01_valid():
11
+ """Valid CliErrorV01 should serialize correctly."""
12
+ err = CliErrorV01(
13
+ level="ERROR",
14
+ code="TST001",
15
+ id="TEST.ERROR.001",
16
+ title="Test error",
17
+ what=["Something happened."],
18
+ why=["Because of reasons."],
19
+ fix=["Do this.", "Re-run: test --dry-run"],
20
+ )
21
+ payload = emit_cli_error(err)
22
+ parsed = json.loads(payload)
23
+
24
+ assert parsed["level"] == "ERROR"
25
+ assert parsed["code"] == "TST001"
26
+ assert parsed["id"] == "TEST.ERROR.001"
27
+ assert parsed["title"] == "Test error"
28
+ assert len(parsed["what"]) == 1
29
+ assert len(parsed["why"]) == 1
30
+ assert len(parsed["fix"]) == 2
31
+
32
+
33
+ def test_cli_error_v01_invalid_level():
34
+ """Invalid level should raise ValueError."""
35
+ with pytest.raises(ValueError, match="Invalid level"):
36
+ CliErrorV01(
37
+ level="FAILURE",
38
+ code="TST001",
39
+ id="TEST.001",
40
+ title="Test",
41
+ what=["X"],
42
+ why=["Y"],
43
+ fix=["Z"],
44
+ )
45
+
46
+
47
+ def test_cli_error_v01_invalid_code_format():
48
+ """Invalid code format should raise ValueError."""
49
+ with pytest.raises(ValueError, match="Invalid code format"):
50
+ CliErrorV01(
51
+ level="ERROR",
52
+ code="invalid",
53
+ id="TEST.001",
54
+ title="Test",
55
+ what=["X"],
56
+ why=["Y"],
57
+ fix=["Z"],
58
+ )
59
+
60
+
61
+ def test_cli_error_v01_valid_code_patterns():
62
+ """Various valid code patterns should work."""
63
+ # Short code: AB001 (2 chars + 3 digits)
64
+ err1 = CliErrorV01(
65
+ level="ERROR",
66
+ code="AB001",
67
+ id="TEST.001",
68
+ title="Test",
69
+ what=["X"],
70
+ why=["Y"],
71
+ fix=["Z"],
72
+ )
73
+ assert err1.code == "AB001"
74
+
75
+ # Long code: DEMO001 (4 chars + 3 digits)
76
+ err2 = CliErrorV01(
77
+ level="ERROR",
78
+ code="DEMO001",
79
+ id="TEST.001",
80
+ title="Test",
81
+ what=["X"],
82
+ why=["Y"],
83
+ fix=["Z"],
84
+ )
85
+ assert err2.code == "DEMO001"
86
+
87
+ # Medium code: DEM001 (3 chars + 3 digits)
88
+ err3 = CliErrorV01(
89
+ level="ERROR",
90
+ code="DEM001",
91
+ id="TEST.001",
92
+ title="Test",
93
+ what=["X"],
94
+ why=["Y"],
95
+ fix=["Z"],
96
+ )
97
+ assert err3.code == "DEM001"
98
+
99
+
100
+ def test_emit_cli_error_json_format():
101
+ """Emitted JSON should be properly formatted."""
102
+ err = CliErrorV01(
103
+ level="WARN",
104
+ code="TST002",
105
+ id="TEST.WARN.001",
106
+ title="Warning",
107
+ what=["Minor issue."],
108
+ why=["Expected behavior."],
109
+ fix=["No action needed."],
110
+ )
111
+ payload = emit_cli_error(err)
112
+
113
+ # Should be valid JSON
114
+ parsed = json.loads(payload)
115
+ assert parsed["level"] == "WARN"
116
+
117
+ # Should have indentation (pretty-printed)
118
+ assert "\n" in payload
119
+ assert " " in payload
120
+
121
+
122
+ def test_emit_cli_error_unicode():
123
+ """Emitted JSON should handle unicode correctly."""
124
+ err = CliErrorV01(
125
+ level="ERROR",
126
+ code="TST003",
127
+ id="TEST.UNICODE.001",
128
+ title="Unicode test: \u2713 \u2717",
129
+ what=["Contains unicode: \u00e9\u00e8\u00ea"],
130
+ why=["Multi-language support."],
131
+ fix=["No changes needed."],
132
+ )
133
+ payload = emit_cli_error(err)
134
+ parsed = json.loads(payload)
135
+
136
+ assert "\u2713" in parsed["title"]
137
+ assert "\u00e9" in parsed["what"][0]