{"id":50496798,"url":"https://github.com/hubzero/a11y-catscan","last_synced_at":"2026-06-02T08:03:38.894Z","repository":{"id":352742569,"uuid":"1216430416","full_name":"hubzero/a11y-catscan","owner":"hubzero","description":"Multi-Engine WCAG Compliance Crawler","archived":false,"fork":false,"pushed_at":"2026-05-04T19:47:04.000Z","size":1160,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-04T21:33:13.470Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hubzero.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-20T22:38:03.000Z","updated_at":"2026-05-04T19:47:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hubzero/a11y-catscan","commit_stats":null,"previous_names":["hubzero/a11y-catscan"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hubzero/a11y-catscan","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hubzero%2Fa11y-catscan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hubzero%2Fa11y-catscan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hubzero%2Fa11y-catscan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hubzero%2Fa11y-catscan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hubzero","download_url":"https://codeload.github.com/hubzero/a11y-catscan/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hubzero%2Fa11y-catscan/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33812205,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-02T02:00:07.132Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-02T08:03:37.692Z","updated_at":"2026-06-02T08:03:38.886Z","avatar_url":"https://github.com/hubzero.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/assets/logo-a11y-catscan.svg\" alt=\"a11y-catscan\" width=\"180\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eMulti-engine accessibility scans that survive real crawls.\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/hubzero/a11y-catscan/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/hubzero/a11y-catscan/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-yellow.svg\" alt=\"License: MIT\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://hubzero.github.io/a11y-catscan/\"\u003e\u003cimg src=\"https://img.shields.io/badge/docs-Pages-blue\" alt=\"docs\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.python.org/\"\u003e\u003cimg src=\"https://img.shields.io/badge/python-3.12+-3776AB?logo=python\u0026logoColor=white\" alt=\"Python 3.12+\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://playwright.dev/\"\u003e\u003cimg src=\"https://img.shields.io/badge/Playwright-1.59-2EAD33?logo=playwright\u0026logoColor=white\" alt=\"Playwright 1.59\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.w3.org/TR/WCAG22/\"\u003e\u003cimg src=\"https://img.shields.io/badge/WCAG-2.2-005A9C\" alt=\"WCAG 2.2\"\u003e\u003c/a\u003e\n  \u003ca href=\"docs-src/mcp.md\"\u003e\u003cimg src=\"https://img.shields.io/badge/MCP-server-444\" alt=\"MCP server\"\u003e\u003c/a\u003e\n  \u003ca href=\"#whats-shipped\"\u003e\u003cimg src=\"https://img.shields.io/badge/status-beta-orange\" alt=\"status: beta\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\na11y-catscan crawls a website with Playwright and runs four\naccessibility engines — axe-core, Siteimprove Alfa, IBM Equal\nAccess, and HTML_CodeSniffer — sharing one Chromium instance.\nFindings are deduped across engines, streamed to JSONL/HTML/JSON\nreports, and exposed as MCP tools so an LLM can analyze them\ndirectly.\n\n**Status: beta.** Production-shaped, exercising in dev; recovery\ncycle and worker pool work end-to-end on multi-thousand-page\nauthenticated crawls. Architecture and per-module design notes\nlive in [DESIGN.md](DESIGN.md). Site handbook is rendered to\nGitHub Pages from `docs-src/`; see the [documentation index](#documentation)\nbelow.\n\n## What's shipped\n\n- **Four scan engines.** axe-core (Deque), Siteimprove Alfa\n  (ACT-rules native), IBM Equal Access, HTML_CodeSniffer. Run\n  one or combine them — `--engine axe,alfa,ibm,htmlcs` — all\n  sharing one Chromium so a multi-engine scan isn't 4× the\n  page loads. Each finding carries an `engine` attribution.\n- **Cross-engine dedup.** Findings sharing\n  `(selector, primary-tag, outcome)` collapse into one entry\n  with `engines: {axe: ..., ibm: ...}` and per-engine impact\n  upgraded to the worst severity. EARL outcomes\n  (`failed` / `cantTell` / `passed` / `inapplicable`) are the\n  internal vocabulary.\n- **Streaming reports.** JSONL is written one page per line so\n  memory stays flat across 5000-page crawls; HTML and the\n  LLM-friendly markdown summary stream from disk on demand.\n- **Sliding-window async crawler.** N-worker pool with one\n  Chromium, periodic browser restart for memory hygiene\n  (`restart_every`), atomic state save (`--resume`), graceful\n  shutdown on SIGTERM/SIGINT, on-demand snapshot via SIGUSR1.\n- **Authenticated scans with mid-scan session recovery.** A\n  Python login plugin authenticates once, the saved session\n  state shortcuts subsequent starts, and if the session expires\n  mid-crawl the scanner drains workers, re-logs-in, bans\n  detected logout-trap URLs, and resumes. Persistent re-login\n  failure trips a circuit breaker so the crawl exits instead\n  of looping.\n- **Allowlist with engine + outcome filters.** YAML allowlist\n  suppresses known-acceptable findings by rule, URL, target,\n  engine, and outcome — all AND'd. O(1) average lookup via a\n  rule-id index.\n- **MCP server.** `--mcp` exposes\n  `scan_page` / `analyze_report` / `find_issues` / `check_page`\n  / `compare_scans` / `manage_scans` / `lookup_wcag` /\n  `list_engines` as Claude Code tools. URL-scheme validated to\n  http(s).\n- **Diff and rescan workflows.** `--diff PREV.jsonl` shows\n  fixed/new/remaining findings; `--rescan PREV.jsonl` re-scans\n  only pages that previously had issues; `--violations-from`\n  / `--incompletes-from` extract specific URL sets from prior\n  reports.\n- **Group-by analysis.** `--group-by {rule, selector, color,\n  reason, wcag, level, engine, bp}` prints a sorted summary\n  with per-group page counts and one example.\n- **Niceness + OOM-resistance.** Defaults to `nice 10` and\n  `oom_score_adj=1000` so the scanner doesn't starve\n  production services on shared hosts.\n\n## Quick start\n\nRequires Python 3.12 and Node.js 18+.\n\n```sh\npip install -e .              # installs playwright, pyyaml, mcp\nplaywright install chromium\nnpm install                   # bundles the four engines\n```\n\nScan one URL:\n\n```sh\n./a11y-catscan.py --page https://example.com/\n```\n\nCrawl with all four engines, write LLM-friendly report:\n\n```sh\n./a11y-catscan.py --engine all --max-pages 500 --llm \\\n    https://example.com/\n```\n\nCompare against last week's baseline:\n\n```sh\n./a11y-catscan.py --diff baseline.jsonl --max-pages 500 \\\n    https://example.com/\n```\n\nFull setup walkthrough in [`docs-src/getting-started.md`](docs-src/getting-started.md).\n\n## Documentation\n\nSite handbook (rendered to\n[hubzero.github.io/a11y-catscan](https://hubzero.github.io/a11y-catscan/)\nfrom these sources):\n\n| Topic | Source |\n|---|---|\n| Getting started — install, first scan, exit codes | [`docs-src/getting-started.md`](docs-src/getting-started.md) |\n| Configuration — every YAML setting + CLI override | [`docs-src/configuration.md`](docs-src/configuration.md) |\n| Scan workflows — crawl, page, urls, rescan, diff, resume | [`docs-src/scan-workflows.md`](docs-src/scan-workflows.md) |\n| Reports — JSON, JSONL, HTML, LLM markdown formats | [`docs-src/reports.md`](docs-src/reports.md) |\n| Authentication — login plugin, session recovery, logout traps | [`docs-src/authentication.md`](docs-src/authentication.md) |\n| MCP server — tool surface for Claude Code | [`docs-src/mcp.md`](docs-src/mcp.md) |\n| Troubleshooting | [`docs-src/troubleshooting.md`](docs-src/troubleshooting.md) |\n| FAQ | [`docs-src/faq.md`](docs-src/faq.md) |\n\nInternal references:\n\n- [DESIGN.md](DESIGN.md) — current-state design specification\n- [CHANGELOG.md](CHANGELOG.md) — date-organized log of changes\n\n## Engines\n\n| Engine | Flag | Type | License |\n|---|---|---|---|\n| [axe-core](https://github.com/dequelabs/axe-core) (Deque) | `--engine axe` | Browser injection (default) | MPL-2.0 |\n| [Siteimprove Alfa](https://github.com/Siteimprove/alfa) | `--engine alfa` | Node.js subprocess via CDP | MIT |\n| [IBM Equal Access](https://github.com/IBMa/equal-access) | `--engine ibm` | Browser injection | Apache-2.0 |\n| [HTML_CodeSniffer](https://github.com/squizlabs/HTML_CodeSniffer) | `--engine htmlcs` | Browser injection | BSD-3 |\n\n`--engine all` runs all four; engines that aren't listed are\nskipped. axe-core, IBM, and HTML_CodeSniffer inject JavaScript\ninto the live page and run in-browser. Alfa's TypeScript engine\nruns as a Node.js subprocess and connects to the shared Chromium\nvia CDP — no second page load.\n\n## Local development\n\nThe full test suite runs against the bundled fixtures:\n\n```sh\npip install -e '.[dev]'\npytest                       # 368 tests, ~70s with browser\npytest -m \"not browser\"      # 285 fast tests, \u003c10s\n```\n\nCoverage is configured in `pyproject.toml`; see\n[`tests/`](tests/) for the layout (`test_engine_normalizers.py`,\n`test_crawl_loop.py`, `test_mcp_tools.py`, etc.).\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n\nEngine licenses: axe-core (MPL-2.0), Siteimprove Alfa (MIT),\nIBM Equal Access (Apache-2.0), HTML_CodeSniffer (BSD-3). The\nfour engines are vendored via npm and ship under their own\nlicenses; this repo wraps them.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhubzero%2Fa11y-catscan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhubzero%2Fa11y-catscan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhubzero%2Fa11y-catscan/lists"}