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.
- package/ACCESSIBILITY.md +205 -0
- package/LICENSE +21 -0
- package/README.md +940 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +528 -0
- package/dist/commands/audit-palette.d.ts +18 -0
- package/dist/commands/audit-palette.js +613 -0
- package/dist/commands/auto-pr.d.ts +19 -0
- package/dist/commands/auto-pr.js +434 -0
- package/dist/commands/badge.d.ts +11 -0
- package/dist/commands/badge.js +143 -0
- package/dist/commands/completion.d.ts +4 -0
- package/dist/commands/completion.js +185 -0
- package/dist/commands/crawl.d.ts +12 -0
- package/dist/commands/crawl.js +249 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.js +233 -0
- package/dist/commands/explain.d.ts +12 -0
- package/dist/commands/explain.js +233 -0
- package/dist/commands/fix.d.ts +13 -0
- package/dist/commands/fix.js +668 -0
- package/dist/commands/health.d.ts +11 -0
- package/dist/commands/health.js +367 -0
- package/dist/commands/history.d.ts +10 -0
- package/dist/commands/history.js +191 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +164 -0
- package/dist/commands/learn.d.ts +8 -0
- package/dist/commands/learn.js +592 -0
- package/dist/commands/pr-check.d.ts +12 -0
- package/dist/commands/pr-check.js +270 -0
- package/dist/commands/report.d.ts +11 -0
- package/dist/commands/report.js +375 -0
- package/dist/commands/scan-storybook.d.ts +18 -0
- package/dist/commands/scan-storybook.js +402 -0
- package/dist/commands/scan.d.ts +25 -0
- package/dist/commands/scan.js +673 -0
- package/dist/commands/stats.d.ts +5 -0
- package/dist/commands/stats.js +137 -0
- package/dist/commands/tree.d.ts +12 -0
- package/dist/commands/tree.js +635 -0
- package/dist/commands/triage.d.ts +13 -0
- package/dist/commands/triage.js +327 -0
- package/dist/commands/watch.d.ts +17 -0
- package/dist/commands/watch.js +302 -0
- package/dist/types/index.d.ts +60 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/baseline.d.ts +62 -0
- package/dist/utils/baseline.js +169 -0
- package/dist/utils/browser.d.ts +78 -0
- package/dist/utils/browser.js +239 -0
- package/dist/utils/cache.d.ts +76 -0
- package/dist/utils/cache.js +178 -0
- package/dist/utils/config.d.ts +102 -0
- package/dist/utils/config.js +237 -0
- package/dist/utils/converters.d.ts +77 -0
- package/dist/utils/converters.js +200 -0
- package/dist/utils/copilot.d.ts +36 -0
- package/dist/utils/copilot.js +139 -0
- package/dist/utils/detect.d.ts +22 -0
- package/dist/utils/detect.js +197 -0
- package/dist/utils/enhanced-errors.d.ts +46 -0
- package/dist/utils/enhanced-errors.js +295 -0
- package/dist/utils/errors.d.ts +31 -0
- package/dist/utils/errors.js +149 -0
- package/dist/utils/fix-patterns.d.ts +56 -0
- package/dist/utils/fix-patterns.js +529 -0
- package/dist/utils/history-tracking.d.ts +94 -0
- package/dist/utils/history-tracking.js +230 -0
- package/dist/utils/history.d.ts +42 -0
- package/dist/utils/history.js +255 -0
- package/dist/utils/impact-scores.d.ts +44 -0
- package/dist/utils/impact-scores.js +257 -0
- package/dist/utils/retry.d.ts +24 -0
- package/dist/utils/retry.js +76 -0
- package/dist/utils/scanner.d.ts +74 -0
- package/dist/utils/scanner.js +606 -0
- package/dist/utils/scanner.test.d.ts +4 -0
- package/dist/utils/scanner.test.js +162 -0
- package/dist/utils/ui.d.ts +44 -0
- package/dist/utils/ui.js +276 -0
- package/mcp-server/dist/index.d.ts +8 -0
- package/mcp-server/dist/index.js +1923 -0
- 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;
|