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,295 @@
1
+ /**
2
+ * Enhanced Error Messages System
3
+ *
4
+ * Provides actionable, helpful error messages with:
5
+ * - Clear explanations of what went wrong
6
+ * - Step-by-step remediation guidance
7
+ * - Code examples
8
+ * - Links to documentation
9
+ * - Context-specific suggestions
10
+ */
11
+ /**
12
+ * Error messages for common issues
13
+ */
14
+ export const ERROR_MESSAGES = {
15
+ // Installation & Setup Errors
16
+ NO_FILES_FOUND: {
17
+ title: 'No HTML files found',
18
+ message: 'ally couldn\'t find any HTML files to scan in the specified directory.',
19
+ remediation: [
20
+ '1. Check that the path is correct: `ally scan ./src`',
21
+ '2. Verify HTML files exist in the directory: `ls ./src/**/*.html`',
22
+ '3. For React/Vue/Svelte apps, scan build output: `ally scan ./dist`',
23
+ '4. Or scan your dev server: `ally scan --url http://localhost:3000`',
24
+ ],
25
+ example: '# For Next.js apps\nnpm run build\nally scan .next/\n\n# Or scan live dev server\nally scan --url http://localhost:3000',
26
+ docs: 'https://github.com/forbiddenlink/ally#scanning-frameworks',
27
+ tip: 'Most modern frameworks generate HTML at build time or runtime - scan the output, not source!',
28
+ },
29
+ BROWSER_LAUNCH_FAILED: {
30
+ title: 'Browser failed to launch',
31
+ message: 'Puppeteer couldn\'t start the browser for scanning.',
32
+ remediation: [
33
+ '1. Install Chromium: `npx puppeteer browsers install chrome`',
34
+ '2. Check system dependencies (Linux): `apt-get install -y libnss3 libatk1.0-0`',
35
+ '3. Try a different browser: `ally scan --browser firefox`',
36
+ '4. For Docker/CI: Add `--no-sandbox` flag (not recommended for local)',
37
+ ],
38
+ example: '# Install browser\nnpx puppeteer browsers install chrome\n\n# Scan with specific browser\nally scan --browser firefox',
39
+ docs: 'https://pptr.dev/troubleshooting',
40
+ tip: 'Running in CI? Make sure browser dependencies are installed in your Docker image.',
41
+ },
42
+ PAGE_LOAD_TIMEOUT: {
43
+ title: 'Page load timeout',
44
+ message: 'The page took too long to load and the scan timed out.',
45
+ remediation: [
46
+ '1. Increase timeout: `ally scan --timeout 60000` (60 seconds)',
47
+ '2. Check the URL is accessible: `curl -I https://example.com`',
48
+ '3. Verify network connectivity and firewall rules',
49
+ '4. For local dev servers, ensure they\'re running before scanning',
50
+ ],
51
+ example: '# Increase timeout for slow pages\nally scan --url https://example.com --timeout 60000\n\n# Start dev server first\nnpm run dev &\nsleep 5\nally scan --url http://localhost:3000',
52
+ docs: 'https://github.com/forbiddenlink/ally#troubleshooting',
53
+ tip: 'Large pages or slow connections? Try scanning individual routes instead of the homepage.',
54
+ },
55
+ INVALID_URL: {
56
+ title: 'Invalid URL',
57
+ message: 'The URL format is invalid or not supported.',
58
+ remediation: [
59
+ '1. URLs must start with http:// or https://',
60
+ '2. Check for typos: `https://` not `http:/`',
61
+ '3. For local files, use file paths: `ally scan ./dist/index.html`',
62
+ '4. For localhost, include port: `http://localhost:3000`',
63
+ ],
64
+ example: '# Valid URLs\nally scan --url https://example.com\nally scan --url http://localhost:3000\n\n# Invalid - missing protocol\nally scan --url example.com ✗',
65
+ tip: 'Scanning a local file? Drop the --url flag: `ally scan ./index.html`',
66
+ },
67
+ // Configuration Errors
68
+ INVALID_CONFIG: {
69
+ title: 'Invalid configuration',
70
+ message: 'The .allyrc.json file contains invalid configuration.',
71
+ remediation: [
72
+ '1. Validate JSON syntax: `cat .allyrc.json | jq`',
73
+ '2. Check for typos in property names',
74
+ '3. See valid options: `ally init --help`',
75
+ '4. Delete config to reset: `rm .allyrc.json && ally init`',
76
+ ],
77
+ example: '// Valid .allyrc.json\n{\n "scan": {\n "standard": "wcag22aa",\n "threshold": 5\n },\n "fix": {\n "autoApprove": ["image-alt"]\n }\n}',
78
+ docs: 'https://github.com/forbiddenlink/ally#configuration',
79
+ tip: 'Use `ally init` to create a valid config file with sensible defaults.',
80
+ },
81
+ MISSING_COPILOT_CLI: {
82
+ title: 'GitHub Copilot CLI not found',
83
+ message: 'The `copilot` command is not available. Some features require GitHub Copilot CLI.',
84
+ remediation: [
85
+ '1. Install Copilot CLI: `npm install -g @github/copilot-cli`',
86
+ '2. Login: `copilot auth login`',
87
+ '3. Verify: `copilot --version`',
88
+ '4. Or use ally without Copilot: `ally scan` works without it!',
89
+ ],
90
+ example: '# Install and setup\nnpm install -g @github/copilot-cli\ncopilot auth login\n\n# Now use AI features\nally explain\nally fix',
91
+ docs: 'https://githubnext.com/projects/copilot-cli',
92
+ tip: 'Basic scanning works without Copilot CLI - only `explain` and `fix` need it for AI features.',
93
+ },
94
+ // Runtime Errors
95
+ PERMISSION_DENIED: {
96
+ title: 'Permission denied',
97
+ message: 'ally doesn\'t have permission to access the file or directory.',
98
+ remediation: [
99
+ '1. Check file permissions: `ls -la /path/to/file`',
100
+ '2. Run with correct user: `sudo ally scan`',
101
+ '3. Fix permissions: `chmod -R 755 ./src`',
102
+ '4. Verify you own the directory: `chown -R $USER ./src`',
103
+ ],
104
+ example: '# Fix permissions\nchmod -R 755 ./src\nally scan ./src\n\n# Or run as correct user\nsudo ally scan ./protected',
105
+ tip: 'Never run ally as root unless absolutely necessary - fix permissions instead!',
106
+ },
107
+ OUT_OF_MEMORY: {
108
+ title: 'Out of memory',
109
+ message: 'ally ran out of memory while processing files.',
110
+ remediation: [
111
+ '1. Scan fewer files: `ally scan ./src/components` instead of `./src`',
112
+ '2. Increase Node memory: `NODE_OPTIONS="--max-old-space-size=4096" ally scan`',
113
+ '3. Use ignore patterns: Add large dirs to .allyignore',
114
+ '4. Scan in batches: `ally scan ./src/a* && ally scan ./src/b*`',
115
+ ],
116
+ example: '# Increase Node memory limit\nNODE_OPTIONS="--max-old-space-size=4096" ally scan ./src\n\n# Create .allyignore\necho "node_modules/" > .allyignore\necho "dist/" >> .allyignore\necho "coverage/" >> .allyignore',
117
+ docs: 'https://github.com/forbiddenlink/ally#ignore-patterns',
118
+ tip: 'Scanning node_modules? Add it to .allyignore to skip dependency files!',
119
+ },
120
+ // Git/CI Errors
121
+ NOT_A_GIT_REPO: {
122
+ title: 'Not a git repository',
123
+ message: 'Some features require a git repository but none was found.',
124
+ remediation: [
125
+ '1. Initialize git: `git init`',
126
+ '2. Or skip git features: `ally scan` works without git',
127
+ '3. In CI: Clone full repo, not shallow: `git clone --depth 1` causes issues',
128
+ ],
129
+ example: '# Initialize git\ngit init\ngit add .\ngit commit -m "Initial commit"\n\n# Now git features work\nally pr-check',
130
+ tip: 'History tracking works better with git - it shows branch and commit for each scan.',
131
+ },
132
+ PR_NOT_FOUND: {
133
+ title: 'Pull request not found',
134
+ message: 'ally couldn\'t detect or access the pull request.',
135
+ remediation: [
136
+ '1. In GitHub Actions: Ensure `gh` CLI is authenticated',
137
+ '2. Set GITHUB_TOKEN: `GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}`',
138
+ '3. Specify PR manually: `ally pr-check --pr 123`',
139
+ '4. Verify repo access: `gh pr list`',
140
+ ],
141
+ example: '# GitHub Actions workflow\n- name: Check PR\n env:\n GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n run: ally pr-check',
142
+ docs: 'https://github.com/forbiddenlink/ally#github-actions',
143
+ tip: 'Local testing? Use `ally pr-check --pr 123` to specify the PR number.',
144
+ },
145
+ // Fix Errors
146
+ FIX_FAILED: {
147
+ title: 'Fix failed',
148
+ message: 'ally couldn\'t apply the fix to the file.',
149
+ remediation: [
150
+ '1. Check file is writable: `ls -la /path/to/file`',
151
+ '2. Verify file wasn\'t deleted: `test -f /path/to/file`',
152
+ '3. Try dry-run first: `ally fix --dry-run`',
153
+ '4. Apply fixes manually using the diff shown',
154
+ ],
155
+ example: '# Dry-run to preview changes\nally fix --dry-run\n\n# Fix permissions if needed\nchmod 644 ./src/Button.tsx\nally fix',
156
+ tip: 'Files under version control? Check `git status` to see what changed.',
157
+ },
158
+ LOW_CONFIDENCE_FIX: {
159
+ title: 'Low confidence fix',
160
+ message: 'The fix has low confidence and needs manual review.',
161
+ remediation: [
162
+ '1. Review the suggested fix carefully',
163
+ '2. Apply manually if it looks correct',
164
+ '3. Use `--auto` to auto-apply high-confidence fixes only: `ally fix --auto`',
165
+ '4. Or skip low confidence: Press "skip" when prompted',
166
+ ],
167
+ example: '# Auto-apply only high-confidence fixes\nally fix --auto\n\n# Or approve each fix interactively\nally fix # Press y/n/skip for each',
168
+ tip: 'watch --fix-on-save only applies fixes ≥90% confidence - safe by default!',
169
+ },
170
+ };
171
+ /**
172
+ * Get enhanced error message
173
+ */
174
+ export function getEnhancedError(errorCode) {
175
+ return ERROR_MESSAGES[errorCode] || null;
176
+ }
177
+ /**
178
+ * Format enhanced error for console output
179
+ */
180
+ export function formatEnhancedError(error) {
181
+ let output = '';
182
+ // Title with emoji
183
+ output += `\n❌ ${error.title}\n\n`;
184
+ // Message
185
+ output += `${error.message}\n\n`;
186
+ // Remediation steps
187
+ output += `🔧 How to fix:\n`;
188
+ for (const step of error.remediation) {
189
+ output += ` ${step}\n`;
190
+ }
191
+ // Example
192
+ if (error.example) {
193
+ output += `\n💡 Example:\n`;
194
+ const lines = error.example.split('\n');
195
+ for (const line of lines) {
196
+ output += ` ${line}\n`;
197
+ }
198
+ }
199
+ // Tip
200
+ if (error.tip) {
201
+ output += `\n💭 Tip: ${error.tip}\n`;
202
+ }
203
+ // Docs
204
+ if (error.docs) {
205
+ output += `\n📖 Documentation: ${error.docs}\n`;
206
+ }
207
+ return output;
208
+ }
209
+ /**
210
+ * Common error patterns and their codes
211
+ */
212
+ export function detectErrorCode(error) {
213
+ const message = error.message.toLowerCase();
214
+ // Installation & Setup
215
+ if (message.includes('no html files') || message.includes('no files found')) {
216
+ return 'NO_FILES_FOUND';
217
+ }
218
+ if (message.includes('browser') && message.includes('launch')) {
219
+ return 'BROWSER_LAUNCH_FAILED';
220
+ }
221
+ if (message.includes('timeout') || message.includes('timed out')) {
222
+ return 'PAGE_LOAD_TIMEOUT';
223
+ }
224
+ if (message.includes('invalid url') || message.includes('malformed url')) {
225
+ return 'INVALID_URL';
226
+ }
227
+ // Configuration
228
+ if (message.includes('invalid config') || message.includes('allyrc')) {
229
+ return 'INVALID_CONFIG';
230
+ }
231
+ if (message.includes('copilot') && message.includes('not found')) {
232
+ return 'MISSING_COPILOT_CLI';
233
+ }
234
+ // Runtime
235
+ if (message.includes('permission denied') || message.includes('eacces')) {
236
+ return 'PERMISSION_DENIED';
237
+ }
238
+ if (message.includes('out of memory') || message.includes('enomem')) {
239
+ return 'OUT_OF_MEMORY';
240
+ }
241
+ // Git/CI
242
+ if (message.includes('not a git repository')) {
243
+ return 'NOT_A_GIT_REPO';
244
+ }
245
+ if (message.includes('pull request') && message.includes('not found')) {
246
+ return 'PR_NOT_FOUND';
247
+ }
248
+ // Fix
249
+ if (message.includes('fix failed') || message.includes('failed to apply')) {
250
+ return 'FIX_FAILED';
251
+ }
252
+ if (message.includes('low confidence')) {
253
+ return 'LOW_CONFIDENCE_FIX';
254
+ }
255
+ return null;
256
+ }
257
+ /**
258
+ * Show enhanced error and exit
259
+ */
260
+ export function showEnhancedErrorAndExit(errorCode, exitCode = 1) {
261
+ const enhanced = getEnhancedError(errorCode);
262
+ if (enhanced) {
263
+ const formatted = formatEnhancedError(enhanced);
264
+ console.error(formatted);
265
+ }
266
+ else {
267
+ console.error(`\n❌ Error code: ${errorCode}\n`);
268
+ }
269
+ process.exit(exitCode);
270
+ }
271
+ /**
272
+ * Throw enhanced error (for backwards compatibility)
273
+ */
274
+ export function throwEnhancedError(errorCode, originalError) {
275
+ showEnhancedErrorAndExit(errorCode, 1);
276
+ }
277
+ /**
278
+ * Handle error with enhanced message
279
+ */
280
+ export function handleErrorWithEnhancement(error) {
281
+ const errorCode = detectErrorCode(error);
282
+ if (errorCode) {
283
+ const enhanced = getEnhancedError(errorCode);
284
+ if (enhanced) {
285
+ console.error(formatEnhancedError(enhanced));
286
+ return;
287
+ }
288
+ }
289
+ // Fallback to original error
290
+ console.error(`\n❌ Error: ${error.message}\n`);
291
+ if (error.stack) {
292
+ console.error(`\n📋 Stack trace:\n${error.stack}\n`);
293
+ }
294
+ console.error(`\n💭 Tip: Run with --verbose for more details\n`);
295
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Error handling utilities with contextual hints for better user experience
3
+ */
4
+ /**
5
+ * Suggest running 'ally scan' when scan results are missing
6
+ */
7
+ export declare function suggestInit(reportPath?: string): void;
8
+ /**
9
+ * Suggest using --url flag when no HTML files are found
10
+ */
11
+ export declare function suggestUrl(): void;
12
+ /**
13
+ * Diagnose Puppeteer errors and return a helpful message
14
+ */
15
+ export declare function diagnosePuppeteer(error: Error): string;
16
+ /**
17
+ * Check if an error is a Puppeteer-related error
18
+ */
19
+ export declare function isPuppeteerError(error: Error): boolean;
20
+ /**
21
+ * Format an error with context and suggestions in a nice box
22
+ */
23
+ export declare function formatError(error: Error, context?: string): void;
24
+ /**
25
+ * Format a scan failure with helpful context
26
+ */
27
+ export declare function formatScanError(error: Error, target: string): void;
28
+ /**
29
+ * Provide helpful message when scan results file is corrupted
30
+ */
31
+ export declare function suggestRescan(reportPath: string): void;
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Error handling utilities with contextual hints for better user experience
3
+ */
4
+ import chalk from 'chalk';
5
+ import boxen from 'boxen';
6
+ import { printError, printInfo, printWarning } from './ui.js';
7
+ /**
8
+ * Suggest running 'ally scan' when scan results are missing
9
+ */
10
+ export function suggestInit(reportPath) {
11
+ printError(`No scan results found`);
12
+ printInfo(`Run 'ally scan' first to generate results`);
13
+ if (reportPath) {
14
+ console.log(chalk.dim(` Expected file: ${reportPath}`));
15
+ }
16
+ console.log();
17
+ console.log(chalk.dim('Quick start:'));
18
+ console.log(chalk.cyan(' ally scan ') + chalk.dim('# Scan HTML files in current directory'));
19
+ console.log(chalk.cyan(' ally scan --url <url> ') + chalk.dim('# Scan a running web app'));
20
+ }
21
+ /**
22
+ * Suggest using --url flag when no HTML files are found
23
+ */
24
+ export function suggestUrl() {
25
+ printWarning('No HTML files found to scan');
26
+ console.log();
27
+ console.log(chalk.dim('For static HTML projects:'));
28
+ console.log(chalk.cyan(' ally scan ./public ') + chalk.dim('# Scan a specific directory'));
29
+ console.log();
30
+ console.log(chalk.dim('For React, Vue, Next.js, or other SPA projects:'));
31
+ console.log(chalk.cyan(' ally scan --url http://localhost:3000'));
32
+ console.log();
33
+ printInfo('Start your dev server first, then scan the running app');
34
+ }
35
+ const PUPPETEER_DIAGNOSTICS = [
36
+ {
37
+ pattern: /Could not find (Chrome|Chromium|browser)/i,
38
+ message: 'Chrome browser not found',
39
+ suggestion: "Run 'npx puppeteer browsers install chrome' to download Chrome",
40
+ },
41
+ {
42
+ pattern: /No usable sandbox/i,
43
+ message: 'Chrome sandbox error (common on Linux/CI)',
44
+ suggestion: "Try running with '--no-sandbox' flag or configure user namespaces",
45
+ },
46
+ {
47
+ pattern: /ECONNREFUSED|ERR_CONNECTION_REFUSED/i,
48
+ message: 'Could not connect to the URL',
49
+ suggestion: 'Check that the server is running and the URL is correct',
50
+ },
51
+ {
52
+ pattern: /Navigation timeout|TimeoutError|Timeout exceeded/i,
53
+ message: 'Page took too long to load',
54
+ suggestion: 'Check the URL is accessible, or try scanning a simpler page first',
55
+ },
56
+ {
57
+ pattern: /net::ERR_NAME_NOT_RESOLVED/i,
58
+ message: 'Could not resolve hostname',
59
+ suggestion: 'Check the URL is spelled correctly and the domain exists',
60
+ },
61
+ {
62
+ pattern: /net::ERR_CERT/i,
63
+ message: 'SSL certificate error',
64
+ suggestion: 'The site has an invalid SSL certificate. Try using http:// instead of https://',
65
+ },
66
+ {
67
+ pattern: /Protocol error|Target closed/i,
68
+ message: 'Browser crashed or was closed unexpectedly',
69
+ suggestion: 'Try running the scan again. If the issue persists, check system memory',
70
+ },
71
+ {
72
+ pattern: /EPERM|EACCES|Permission denied/i,
73
+ message: 'Permission denied',
74
+ suggestion: 'Check file permissions or try running with elevated privileges',
75
+ },
76
+ ];
77
+ /**
78
+ * Diagnose Puppeteer errors and return a helpful message
79
+ */
80
+ export function diagnosePuppeteer(error) {
81
+ const errorMessage = error.message || String(error);
82
+ for (const diagnostic of PUPPETEER_DIAGNOSTICS) {
83
+ if (diagnostic.pattern.test(errorMessage)) {
84
+ return `${diagnostic.message}\n\nSuggestion: ${diagnostic.suggestion}`;
85
+ }
86
+ }
87
+ // Generic fallback for unknown Puppeteer errors
88
+ return `Browser error: ${errorMessage}\n\nIf this persists, try:\n - Updating Puppeteer: npm update puppeteer\n - Reinstalling Chrome: npx puppeteer browsers install chrome`;
89
+ }
90
+ /**
91
+ * Check if an error is a Puppeteer-related error
92
+ */
93
+ export function isPuppeteerError(error) {
94
+ const errorMessage = error.message || String(error);
95
+ return PUPPETEER_DIAGNOSTICS.some((d) => d.pattern.test(errorMessage));
96
+ }
97
+ /**
98
+ * Format an error with context and suggestions in a nice box
99
+ */
100
+ export function formatError(error, context) {
101
+ const errorMessage = error.message || String(error);
102
+ // Check for Puppeteer errors first
103
+ if (isPuppeteerError(error)) {
104
+ const diagnosis = diagnosePuppeteer(error);
105
+ console.log(boxen(diagnosis, {
106
+ padding: 1,
107
+ margin: { top: 1, bottom: 1, left: 0, right: 0 },
108
+ borderStyle: 'round',
109
+ borderColor: 'red',
110
+ title: 'Browser Error',
111
+ titleAlignment: 'left',
112
+ }));
113
+ return;
114
+ }
115
+ // Build error display
116
+ let content = chalk.red(errorMessage);
117
+ if (context) {
118
+ content = `${chalk.dim(context)}\n\n${content}`;
119
+ }
120
+ console.log(boxen(content, {
121
+ padding: 1,
122
+ margin: { top: 1, bottom: 1, left: 0, right: 0 },
123
+ borderStyle: 'round',
124
+ borderColor: 'red',
125
+ title: 'Error',
126
+ titleAlignment: 'left',
127
+ }));
128
+ }
129
+ /**
130
+ * Format a scan failure with helpful context
131
+ */
132
+ export function formatScanError(error, target) {
133
+ if (isPuppeteerError(error)) {
134
+ formatError(error, `Failed to scan: ${target}`);
135
+ }
136
+ else {
137
+ printError(`Failed to scan ${target}`);
138
+ console.log(chalk.dim(` ${error.message}`));
139
+ }
140
+ }
141
+ /**
142
+ * Provide helpful message when scan results file is corrupted
143
+ */
144
+ export function suggestRescan(reportPath) {
145
+ printError('Scan results file appears to be corrupted');
146
+ printInfo(`Delete ${reportPath} and run 'ally scan' again`);
147
+ console.log();
148
+ console.log(chalk.cyan(` rm ${reportPath} && ally scan`));
149
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Auto-fix patterns for common accessibility violations
3
+ *
4
+ * Each pattern takes the violation node HTML and returns a fixed version,
5
+ * or null if it can't be auto-fixed.
6
+ */
7
+ import type { Violation } from '../types/index.js';
8
+ /**
9
+ * Helper to extract attribute value from HTML string
10
+ */
11
+ export declare function getAttr(html: string, attr: string): string | null;
12
+ /**
13
+ * Helper to check if element has an attribute
14
+ */
15
+ export declare function hasAttr(html: string, attr: string): boolean;
16
+ /**
17
+ * Helper to add attribute to opening tag
18
+ */
19
+ export declare function addAttr(html: string, tag: string, attr: string, value: string): string;
20
+ /**
21
+ * Helper to extract tag name from HTML
22
+ */
23
+ export declare function getTagName(html: string): string | null;
24
+ /**
25
+ * Helper to infer a description from existing attributes or content
26
+ */
27
+ export declare function inferDescription(html: string, fallback: string): string;
28
+ /**
29
+ * Fix pattern function type
30
+ */
31
+ export type FixPatternFn = (html: string, violation: Violation) => string | null;
32
+ /**
33
+ * Confidence scores for each fix pattern (0.0 to 1.0)
34
+ *
35
+ * High confidence (0.9+): Simple attribute additions (alt, aria-label, lang)
36
+ * Medium confidence (0.7-0.9): Structural changes, role assignments
37
+ * Lower confidence (0.5-0.7): Complex fixes requiring context
38
+ */
39
+ export declare const FIX_CONFIDENCE: Record<string, number>;
40
+ /**
41
+ * Get confidence score for a fix pattern
42
+ * @returns Confidence score between 0 and 1, or null if pattern not found
43
+ */
44
+ export declare function getFixConfidence(patternId: string): number | null;
45
+ /**
46
+ * Get confidence level category
47
+ */
48
+ export declare function getConfidenceLevel(confidence: number): 'high' | 'medium' | 'low';
49
+ /**
50
+ * Auto-fix patterns for common accessibility violations
51
+ */
52
+ export declare const FIX_PATTERNS: Record<string, FixPatternFn>;
53
+ /**
54
+ * Generate a suggested fix for a violation
55
+ */
56
+ export declare function generateSuggestedFix(violation: Violation, html: string): string | null;