ally-a11y 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/ACCESSIBILITY.md +205 -0
  2. package/LICENSE +21 -0
  3. package/README.md +940 -0
  4. package/dist/cli.d.ts +7 -0
  5. package/dist/cli.js +528 -0
  6. package/dist/commands/audit-palette.d.ts +18 -0
  7. package/dist/commands/audit-palette.js +613 -0
  8. package/dist/commands/auto-pr.d.ts +19 -0
  9. package/dist/commands/auto-pr.js +434 -0
  10. package/dist/commands/badge.d.ts +11 -0
  11. package/dist/commands/badge.js +143 -0
  12. package/dist/commands/completion.d.ts +4 -0
  13. package/dist/commands/completion.js +185 -0
  14. package/dist/commands/crawl.d.ts +12 -0
  15. package/dist/commands/crawl.js +249 -0
  16. package/dist/commands/doctor.d.ts +5 -0
  17. package/dist/commands/doctor.js +233 -0
  18. package/dist/commands/explain.d.ts +12 -0
  19. package/dist/commands/explain.js +233 -0
  20. package/dist/commands/fix.d.ts +13 -0
  21. package/dist/commands/fix.js +668 -0
  22. package/dist/commands/health.d.ts +11 -0
  23. package/dist/commands/health.js +367 -0
  24. package/dist/commands/history.d.ts +10 -0
  25. package/dist/commands/history.js +191 -0
  26. package/dist/commands/init.d.ts +9 -0
  27. package/dist/commands/init.js +164 -0
  28. package/dist/commands/learn.d.ts +8 -0
  29. package/dist/commands/learn.js +592 -0
  30. package/dist/commands/pr-check.d.ts +12 -0
  31. package/dist/commands/pr-check.js +270 -0
  32. package/dist/commands/report.d.ts +11 -0
  33. package/dist/commands/report.js +375 -0
  34. package/dist/commands/scan-storybook.d.ts +18 -0
  35. package/dist/commands/scan-storybook.js +402 -0
  36. package/dist/commands/scan.d.ts +25 -0
  37. package/dist/commands/scan.js +673 -0
  38. package/dist/commands/stats.d.ts +5 -0
  39. package/dist/commands/stats.js +137 -0
  40. package/dist/commands/tree.d.ts +12 -0
  41. package/dist/commands/tree.js +635 -0
  42. package/dist/commands/triage.d.ts +13 -0
  43. package/dist/commands/triage.js +327 -0
  44. package/dist/commands/watch.d.ts +17 -0
  45. package/dist/commands/watch.js +302 -0
  46. package/dist/types/index.d.ts +60 -0
  47. package/dist/types/index.js +4 -0
  48. package/dist/utils/baseline.d.ts +62 -0
  49. package/dist/utils/baseline.js +169 -0
  50. package/dist/utils/browser.d.ts +78 -0
  51. package/dist/utils/browser.js +239 -0
  52. package/dist/utils/cache.d.ts +76 -0
  53. package/dist/utils/cache.js +178 -0
  54. package/dist/utils/config.d.ts +102 -0
  55. package/dist/utils/config.js +237 -0
  56. package/dist/utils/converters.d.ts +77 -0
  57. package/dist/utils/converters.js +200 -0
  58. package/dist/utils/copilot.d.ts +36 -0
  59. package/dist/utils/copilot.js +139 -0
  60. package/dist/utils/detect.d.ts +22 -0
  61. package/dist/utils/detect.js +197 -0
  62. package/dist/utils/enhanced-errors.d.ts +46 -0
  63. package/dist/utils/enhanced-errors.js +295 -0
  64. package/dist/utils/errors.d.ts +31 -0
  65. package/dist/utils/errors.js +149 -0
  66. package/dist/utils/fix-patterns.d.ts +56 -0
  67. package/dist/utils/fix-patterns.js +529 -0
  68. package/dist/utils/history-tracking.d.ts +94 -0
  69. package/dist/utils/history-tracking.js +230 -0
  70. package/dist/utils/history.d.ts +42 -0
  71. package/dist/utils/history.js +255 -0
  72. package/dist/utils/impact-scores.d.ts +44 -0
  73. package/dist/utils/impact-scores.js +257 -0
  74. package/dist/utils/retry.d.ts +24 -0
  75. package/dist/utils/retry.js +76 -0
  76. package/dist/utils/scanner.d.ts +74 -0
  77. package/dist/utils/scanner.js +606 -0
  78. package/dist/utils/scanner.test.d.ts +4 -0
  79. package/dist/utils/scanner.test.js +162 -0
  80. package/dist/utils/ui.d.ts +44 -0
  81. package/dist/utils/ui.js +276 -0
  82. package/mcp-server/dist/index.d.ts +8 -0
  83. package/mcp-server/dist/index.js +1923 -0
  84. package/package.json +88 -0
@@ -0,0 +1,12 @@
1
+ /**
2
+ * ally explain command - Uses Copilot to explain violations in plain language
3
+ */
4
+ import type { Severity } from '../types/index.js';
5
+ interface ExplainOptions {
6
+ input?: string;
7
+ severity?: Severity;
8
+ limit?: number;
9
+ ai?: boolean;
10
+ }
11
+ export declare function explainCommand(options?: ExplainOptions): Promise<void>;
12
+ export default explainCommand;
@@ -0,0 +1,233 @@
1
+ /**
2
+ * ally explain command - Uses Copilot to explain violations in plain language
3
+ */
4
+ import { readFile } from 'fs/promises';
5
+ import { existsSync } from 'fs';
6
+ import { resolve } from 'path';
7
+ import chalk from 'chalk';
8
+ import { printBanner, createSpinner, printError, printInfo, printWarning, } from '../utils/ui.js';
9
+ import { suggestInit, suggestRescan } from '../utils/errors.js';
10
+ import { checkCopilotCli } from '../utils/copilot.js';
11
+ import { execSync } from 'child_process';
12
+ export async function explainCommand(options = {}) {
13
+ printBanner();
14
+ const { input = '.ally/scan.json', severity, limit = 10, ai = false } = options;
15
+ // Load scan results
16
+ const spinner = createSpinner('Loading scan results...');
17
+ spinner.start();
18
+ const reportPath = resolve(input);
19
+ if (!existsSync(reportPath)) {
20
+ spinner.stop();
21
+ suggestInit(reportPath);
22
+ return;
23
+ }
24
+ let report;
25
+ try {
26
+ const content = await readFile(reportPath, 'utf-8');
27
+ report = JSON.parse(content);
28
+ spinner.succeed(`Loaded ${report.summary.totalViolations} violations from scan`);
29
+ }
30
+ catch (error) {
31
+ spinner.fail('Failed to load scan results');
32
+ if (error instanceof SyntaxError) {
33
+ suggestRescan(reportPath);
34
+ }
35
+ else {
36
+ printError(error instanceof Error ? error.message : String(error));
37
+ }
38
+ return;
39
+ }
40
+ // Collect and deduplicate violations
41
+ const uniqueViolations = new Map();
42
+ for (const result of report.results) {
43
+ for (const violation of result.violations) {
44
+ if (severity && violation.impact !== severity)
45
+ continue;
46
+ if (!uniqueViolations.has(violation.id)) {
47
+ uniqueViolations.set(violation.id, violation);
48
+ }
49
+ }
50
+ }
51
+ const violations = Array.from(uniqueViolations.values()).slice(0, limit);
52
+ if (violations.length === 0) {
53
+ printInfo('No violations to explain');
54
+ return;
55
+ }
56
+ console.log();
57
+ console.log(chalk.bold.cyan('Accessibility Issues Explained'));
58
+ console.log(chalk.dim('━'.repeat(50)));
59
+ console.log();
60
+ // Check if AI mode requested
61
+ if (ai) {
62
+ const copilot = checkCopilotCli();
63
+ if (copilot.available) {
64
+ await explainWithCopilot(violations, copilot.command);
65
+ return;
66
+ }
67
+ else {
68
+ printWarning('Copilot CLI not available. Using built-in explanations.');
69
+ printInfo('Install Copilot CLI: npm install -g @github/copilot-cli');
70
+ console.log();
71
+ }
72
+ }
73
+ // Generate explanations for each violation
74
+ for (let i = 0; i < violations.length; i++) {
75
+ const violation = violations[i];
76
+ await printExplanation(violation, i + 1);
77
+ }
78
+ // Print Copilot CLI instruction
79
+ console.log();
80
+ console.log(chalk.dim('━'.repeat(50)));
81
+ console.log();
82
+ printInfo('For AI-powered explanations, run:');
83
+ console.log(chalk.cyan(` ally explain --ai`));
84
+ console.log();
85
+ printInfo('Or fix issues directly:');
86
+ console.log(chalk.cyan(` ally fix`));
87
+ }
88
+ /**
89
+ * Use Copilot CLI to explain violations with deep context
90
+ */
91
+ async function explainWithCopilot(violations, command) {
92
+ console.log(chalk.cyan('🤖 Using GitHub Copilot CLI for AI-powered explanations...'));
93
+ console.log();
94
+ for (let i = 0; i < violations.length; i++) {
95
+ const violation = violations[i];
96
+ console.log(chalk.bold(`${i + 1}. ${violation.help}`));
97
+ console.log(chalk.dim(` Rule: ${violation.id} | Severity: ${violation.impact}`));
98
+ console.log();
99
+ // Create a focused prompt for Copilot
100
+ const prompt = `Explain this accessibility violation in plain language for a developer:
101
+
102
+ Issue: ${violation.help}
103
+ Rule ID: ${violation.id}
104
+ Severity: ${violation.impact}
105
+ Description: ${violation.description}
106
+
107
+ Please explain:
108
+ 1. What is wrong (1-2 sentences)
109
+ 2. Who is affected and how (specific user groups)
110
+ 3. How to fix it (with a code example if helpful)
111
+ 4. Why this matters for the business
112
+
113
+ Keep the explanation concise and actionable.`;
114
+ try {
115
+ // Execute Copilot CLI synchronously
116
+ const cmdParts = command.split(' ');
117
+ const fullCommand = cmdParts.length > 1
118
+ ? `${command} explain "${prompt.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`
119
+ : `${command} -p "${prompt.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`;
120
+ const result = execSync(fullCommand, {
121
+ encoding: 'utf-8',
122
+ timeout: 30000,
123
+ stdio: ['pipe', 'pipe', 'pipe'],
124
+ });
125
+ console.log(chalk.white(result.trim()));
126
+ }
127
+ catch (error) {
128
+ // Fallback to built-in explanation if Copilot fails
129
+ const explanation = getPlainLanguageExplanation(violation);
130
+ console.log(chalk.white(` What's wrong: ${explanation.problem}`));
131
+ console.log(chalk.white(` Who it affects: ${explanation.impact}`));
132
+ console.log(chalk.white(` How to fix: ${explanation.fix}`));
133
+ }
134
+ console.log();
135
+ console.log(chalk.dim(` Learn more: ${violation.helpUrl}`));
136
+ console.log();
137
+ console.log(chalk.dim(' ' + '─'.repeat(45)));
138
+ console.log();
139
+ }
140
+ }
141
+ async function printExplanation(violation, index) {
142
+ const severityColors = {
143
+ critical: chalk.red.bold,
144
+ serious: chalk.red,
145
+ moderate: chalk.yellow,
146
+ minor: chalk.blue,
147
+ };
148
+ const color = severityColors[violation.impact];
149
+ console.log(chalk.bold(`${index}. ${violation.help}`));
150
+ console.log(color(` Severity: ${violation.impact.toUpperCase()}`));
151
+ console.log();
152
+ // Plain language explanation based on rule ID
153
+ const explanation = getPlainLanguageExplanation(violation);
154
+ console.log(chalk.white(` What's wrong:`));
155
+ console.log(chalk.dim(` ${explanation.problem}`));
156
+ console.log();
157
+ console.log(chalk.white(` Who it affects:`));
158
+ console.log(chalk.dim(` ${explanation.impact}`));
159
+ console.log();
160
+ console.log(chalk.white(` How to fix:`));
161
+ console.log(chalk.dim(` ${explanation.fix}`));
162
+ console.log();
163
+ // WCAG reference
164
+ const wcagTags = violation.tags.filter((t) => t.startsWith('wcag'));
165
+ if (wcagTags.length > 0) {
166
+ console.log(chalk.dim(` WCAG: ${wcagTags.join(', ')}`));
167
+ }
168
+ console.log(chalk.dim(` Learn more: ${violation.helpUrl}`));
169
+ console.log();
170
+ console.log(chalk.dim(' ' + '─'.repeat(45)));
171
+ console.log();
172
+ }
173
+ function getPlainLanguageExplanation(violation) {
174
+ // Common accessibility issues with plain language explanations
175
+ const explanations = {
176
+ 'image-alt': {
177
+ problem: 'Images are missing alt text, which describes the image content.',
178
+ impact: 'Screen reader users cannot understand what the image shows. They only hear "image" with no context.',
179
+ fix: 'Add an alt attribute to each image describing its content. For decorative images, use alt="".',
180
+ },
181
+ 'button-name': {
182
+ problem: 'Buttons don\'t have accessible names that describe their purpose.',
183
+ impact: 'Screen reader users hear "button" but don\'t know what the button does.',
184
+ fix: 'Add visible text inside the button, or use aria-label to provide a description.',
185
+ },
186
+ 'link-name': {
187
+ problem: 'Links don\'t have text that describes where they go.',
188
+ impact: 'Screen reader users hear "link" but don\'t know where clicking will take them.',
189
+ fix: 'Add descriptive text inside the link. Avoid "click here" or "read more".',
190
+ },
191
+ 'color-contrast': {
192
+ problem: 'Text doesn\'t have enough contrast against its background.',
193
+ impact: 'People with low vision or color blindness struggle to read the text.',
194
+ fix: 'Use colors with at least 4.5:1 contrast ratio for normal text, 3:1 for large text.',
195
+ },
196
+ 'html-has-lang': {
197
+ problem: 'The HTML document doesn\'t specify a language.',
198
+ impact: 'Screen readers may pronounce words incorrectly if they don\'t know the language.',
199
+ fix: 'Add lang="en" (or appropriate language code) to the <html> element.',
200
+ },
201
+ 'label': {
202
+ problem: 'Form inputs are missing labels that describe what to enter.',
203
+ impact: 'Screen reader users don\'t know what information to type in the field.',
204
+ fix: 'Add a <label> element connected to the input via for/id attributes.',
205
+ },
206
+ 'landmark-one-main': {
207
+ problem: 'The page doesn\'t have a main landmark to identify primary content.',
208
+ impact: 'Screen reader users can\'t quickly navigate to the main content of the page.',
209
+ fix: 'Wrap your main content in a <main> element.',
210
+ },
211
+ 'region': {
212
+ problem: 'Content exists outside of landmark regions.',
213
+ impact: 'Screen reader users have difficulty understanding the page structure.',
214
+ fix: 'Organize content into semantic landmarks: header, nav, main, aside, footer.',
215
+ },
216
+ 'aria-required-attr': {
217
+ problem: 'ARIA roles are missing required attributes.',
218
+ impact: 'Assistive technology may not correctly interpret the element\'s purpose.',
219
+ fix: 'Add the required ARIA attributes for the role being used.',
220
+ },
221
+ 'tabindex': {
222
+ problem: 'Elements have tabindex greater than 0, which disrupts natural focus order.',
223
+ impact: 'Keyboard users experience confusing, unpredictable navigation.',
224
+ fix: 'Use tabindex="0" to make elements focusable, or tabindex="-1" to remove from tab order.',
225
+ },
226
+ };
227
+ return explanations[violation.id] || {
228
+ problem: violation.description,
229
+ impact: 'Users with disabilities may have difficulty using this part of the page.',
230
+ fix: violation.help,
231
+ };
232
+ }
233
+ export default explainCommand;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * ally fix command - Uses Copilot CLI agentic mode to fix violations
3
+ */
4
+ import type { Severity } from '../types/index.js';
5
+ interface FixOptions {
6
+ input?: string;
7
+ severity?: Severity;
8
+ auto?: boolean;
9
+ dryRun?: boolean;
10
+ yes?: boolean;
11
+ }
12
+ export declare function fixCommand(options?: FixOptions): Promise<void>;
13
+ export default fixCommand;