@binclusive/a11y 0.1.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/README.md +285 -0
- package/bin/a11y.mjs +36 -0
- package/bin/diff-scope.mjs +29 -0
- package/data/baseline-rules.json +892 -0
- package/package.json +68 -0
- package/src/agent-lane.ts +138 -0
- package/src/agents-block.ts +157 -0
- package/src/baseline/gen-baseline.ts +166 -0
- package/src/cli.ts +1026 -0
- package/src/collect-dom.ts +119 -0
- package/src/collect-liquid.ts +103 -0
- package/src/collect-swift.ts +227 -0
- package/src/collect-unity.ts +99 -0
- package/src/collect.ts +54 -0
- package/src/commands.ts +314 -0
- package/src/config-scan.ts +177 -0
- package/src/contract.ts +355 -0
- package/src/core.ts +546 -0
- package/src/detect-stack.ts +207 -0
- package/src/diff-scope-cli.ts +12 -0
- package/src/diff-scope.ts +150 -0
- package/src/emit-contract.ts +181 -0
- package/src/enforce.ts +1125 -0
- package/src/eslint-plugin-jsx-a11y.d.ts +11 -0
- package/src/evidence.ts +308 -0
- package/src/github-identity.ts +201 -0
- package/src/hook.ts +242 -0
- package/src/impact-gate.ts +107 -0
- package/src/imports-resolve.ts +248 -0
- package/src/index.ts +183 -0
- package/src/liquid-ast.ts +203 -0
- package/src/liquid-rules.ts +691 -0
- package/src/mcp.ts +363 -0
- package/src/module-scope.ts +137 -0
- package/src/phone-home.ts +470 -0
- package/src/pr-comment.ts +250 -0
- package/src/pr-summary-cli.ts +182 -0
- package/src/pr-summary.ts +291 -0
- package/src/registry.ts +605 -0
- package/src/reporter/contract.ts +154 -0
- package/src/reporter/finding.ts +87 -0
- package/src/reporter/github-adapter.ts +183 -0
- package/src/reporter/null-adapter.ts +51 -0
- package/src/reporter/registry.ts +22 -0
- package/src/reporter-cli.ts +48 -0
- package/src/resolve-components.ts +579 -0
- package/src/runner/budget.ts +90 -0
- package/src/runner/codegraph-lookup.test.ts +93 -0
- package/src/runner/codegraph-lookup.ts +197 -0
- package/src/runner/index.ts +86 -0
- package/src/runner/lookup.ts +69 -0
- package/src/runner/provider.ts +72 -0
- package/src/runner/providers/anthropic.ts +168 -0
- package/src/runner/providers/openai.ts +191 -0
- package/src/runner/reasoner.ts +73 -0
- package/src/runner/reasoning/index.ts +53 -0
- package/src/runner/reasoning/prompt.ts +200 -0
- package/src/runner/reasoning/react.ts +149 -0
- package/src/runner/reasoning/shopify.ts +214 -0
- package/src/runner/reasoning/skills-reasoner.ts +117 -0
- package/src/runner/reasoning/types.ts +99 -0
- package/src/runner/runner.ts +203 -0
- package/src/sarif.ts +148 -0
- package/src/source-identity.ts +0 -0
- package/src/source-trace.ts +814 -0
- package/src/suggest.ts +328 -0
- package/src/suppression-ranges.ts +354 -0
- package/src/suppressor-map.ts +189 -0
- package/src/suppressors.ts +284 -0
- package/src/tsconfig-aliases.ts +155 -0
- package/src/unity-ast.ts +331 -0
- package/src/unity-findings.ts +91 -0
- package/src/unity-guid-registry.ts +82 -0
- package/src/unity-label-resolve.ts +249 -0
- package/src/unity-rule-color-only.ts +127 -0
- package/src/unity-rule-missing-label.ts +156 -0
- package/src/unity-rules-baseline.ts +273 -0
- package/src/wcag-map.ts +35 -0
- package/src/wcag-tags.ts +35 -0
- package/src/workspace-resolve.ts +405 -0
package/README.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# a11y-checker — review build
|
|
2
|
+
|
|
3
|
+
A local accessibility checker for React/TSX code, grounded in a real-world audit corpus. It finds accessibility bugs at the source — **including in the design-system components a normal linter is blind to** — and tells you how common each failure is across real audits, with the fix that worked.
|
|
4
|
+
|
|
5
|
+
> **It runs entirely on your machine. No network, no account, no upload — your code never leaves the laptop.** That's not a privacy policy, it's how it's built: there's nothing to upload. Point it at a private repo with zero hesitation.
|
|
6
|
+
|
|
7
|
+
This is a private review build. Clone it, point it at any React codebase (yours, ours), and see what it finds — no setup, no explanation needed.
|
|
8
|
+
|
|
9
|
+
> **New here? Start with the [Getting Started](docs/GETTING-STARTED.md) walkthrough.** Zero to your first fix — install, `init`, wire your editor, read a finding, clear it, gate CI.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## See it in 30 seconds
|
|
14
|
+
|
|
15
|
+
On **an open-source component library's own demo app**, `eslint-plugin-jsx-a11y` (the linter everyone runs) passes the docs search box **clean** — while a11y-checker catches its unlabeled `<Input>`, ranks it (`22/26 orgs`), and hands you the fix.
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
**▶ [Watch all five demos →](demo/README.md)** — the head-to-head above, a getting-started walkthrough on an open-source monorepo, the `binclusive.json` config reference, the state of accessibility across 31 OSS repos, and the agentic self-fix loop. Each is a replayable [asciinema](https://asciinema.org) cast (`asciinema play demo/<name>.cast`), not just a GIF.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Try it (≈3 minutes)
|
|
24
|
+
|
|
25
|
+
Requires **Node ≥ 20** and **pnpm** (or npm).
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm install # or: npm install
|
|
29
|
+
pnpm scan path/to/any/app/src # any folder of .tsx files
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
That's the whole thing. It scans every `.tsx` under the folder and prints a coverage report + the findings. Run it on code you know — you'll be able to judge instantly whether each finding is real.
|
|
33
|
+
|
|
34
|
+
No clone handy? Point it at this repo's own test fixtures: `pnpm scan ./test/fixtures`.
|
|
35
|
+
|
|
36
|
+
> **No React source?** (A live site, an ASP.NET/Razor app, plain HTML.) The same checker can render a real page in a browser and audit the live DOM — `pnpm scan:url https://example.com`. See **[Auditing HTML & live pages (non-React)](#auditing-html--live-pages-non-react)** below.
|
|
37
|
+
|
|
38
|
+
> **Using your own design system?** (Almost everyone is.) A cold scan leaves most of your components in `declare` — *that's expected, not a failure.* To turn on its best trick (finding bugs *inside* your own components), it needs to know which of your components are buttons, inputs, etc. You don't write that by hand:
|
|
39
|
+
>
|
|
40
|
+
> ```bash
|
|
41
|
+
> pnpm a11y-checker init --suggest # scaffolds the config for you
|
|
42
|
+
> ```
|
|
43
|
+
>
|
|
44
|
+
> It guesses a host for each of your design-system primitives, flags the uncertain ones with `⚠`, and leaves composites alone — so adoption is a **~2-minute review**, not hand-written config. Full on-ramp: **[WALKTHROUGH.md](WALKTHROUGH.md)** — read it before judging a cold run.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## What you'll see
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
a11y coverage:
|
|
52
|
+
checked 70 — elements we inspected (findings come from here)
|
|
53
|
+
trusted 60 — from a known-accessible design system — the library handles these
|
|
54
|
+
declare 702 — unrecognized; declare in binclusive.json to inspect them
|
|
55
|
+
|
|
56
|
+
components/.../AddIntegrationModal.tsx
|
|
57
|
+
AddIntegrationModal.tsx:276
|
|
58
|
+
rule: enforce/input-no-name [block] (call-site content check)
|
|
59
|
+
wcag: 1.3.1, 3.3.2
|
|
60
|
+
corpus: [VERY COMMON] SC 1.3.1 — 22/26 orgs
|
|
61
|
+
fix: Associate every form field with a <label> via id (not placeholder-only)...
|
|
62
|
+
|
|
63
|
+
91 finding(s) VERY COMMON: 87 | COMMON: 4
|
|
64
|
+
enforcement: 91 blocking · 0 warning
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- **coverage is honest.** Most of a design-system app is *trusted* library components — nothing to flag there, and that's correct, not blindness. The number that matters is "did it find the real bugs," not "what % did it inspect."
|
|
68
|
+
- **each finding carries real-world weight** — its WCAG criterion, how widespread it is across our audits (`X/26 orgs`), and the representative fix.
|
|
69
|
+
- **`(call-site content check)`** marks findings that reach *trusted* components a normal linter skips. That's the recall win — "trusted" stops being false reassurance.
|
|
70
|
+
|
|
71
|
+
> **About the exit code:** `scan` exits non-zero when it finds *blocking* issues, so it can gate a CI build. If your run ends with `Command failed with exit code 1`, that's **not** an error — it means it found something. Read the report above it.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## What is this, in 30 seconds
|
|
76
|
+
|
|
77
|
+
Two passes + one corpus:
|
|
78
|
+
|
|
79
|
+
1. the normal structural lint (`eslint-plugin-jsx-a11y`) over a resolved component map, **plus**
|
|
80
|
+
2. a **content check at the call site** that catches bugs hiding inside "trusted" library components (an icon-only button with no name, an input with no label), **plus**
|
|
81
|
+
3. every finding **matched to a corpus** of real Binclusive audit failures — so it says not just *that* it's wrong, but *how common* it is in the wild and the fix that worked.
|
|
82
|
+
|
|
83
|
+
A generic linter can't do 2 or 3. The deeper story (and why the corpus is a moat) is in `docs/`.
|
|
84
|
+
|
|
85
|
+
There's also a **second producer**: a rendered-DOM collector that drives a real browser to a URL and runs axe-core against the live page — same corpus, same WCAG, same enforcement gate, no source required. That's the next section.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Auditing HTML & live pages (non-React)
|
|
90
|
+
|
|
91
|
+
The scan above works on `.tsx` source. But not every page *has* React source on disk — a deployed customer site, an ASP.NET/Razor app, a plain HTML/Bootstrap/jQuery page. For those, point the checker at the **rendered page** instead of the source:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pnpm exec playwright install chromium # one-time: the browser the render path drives
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pnpm scan:url https://example.com # any deployed URL
|
|
99
|
+
pnpm scan:url http://localhost:5000 # your local dev server
|
|
100
|
+
pnpm scan:url ./wwwroot/index.html # a local static .html file (bare path works)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`<target>` takes an `http(s)://` URL, a `file://` URL, or a **bare local path** (auto-converted to `file://`). Under the hood it renders the page in real Chromium (via Playwright), runs **axe-core** against the live DOM, then flows every finding through the *same* corpus / WCAG / enforcement machinery as the source scan — so a contrast bug on example.com comes back tiered and gated exactly like a missing label in your `.tsx`.
|
|
104
|
+
|
|
105
|
+
This is the source-less path — one command audits any live site, React or not.
|
|
106
|
+
|
|
107
|
+
- **Templates need a running server.** A server-side template (`.cshtml` Razor, `.erb`, etc.) is **not** valid standalone HTML — it's `@`-directives, loops, interpolation — so `file://` can't render it. Point `check-url` at the **running app** (`localhost`) for templates. Only plain `.html` files render directly via `file://`.
|
|
108
|
+
- **It catches what static analysis can't.** A real browser render surfaces categories the `.tsx` scan and even headless DOMs (jsdom) are blind to — notably **color-contrast (WCAG 1.4.3)**, computed ARIA roles, and layout-dependent rules.
|
|
109
|
+
- **Honest edge:** the seed corpus snapshot currently covers ~10 success criteria and does **not** yet include some SCs this path surfaces (e.g. 1.4.3 contrast, 1.4.1, 2.4.4). Those findings still appear — the render catches them regardless — but they roll up as tier `UNKNOWN` (no corpus fix text) until the corpus is extended.
|
|
110
|
+
|
|
111
|
+
The full walkthrough — install once, read the output, the `(rendered-DOM / axe)` provenance tag — is in **`docs/AUDIT-URL.md`**.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Use it in CI (GitHub Action)
|
|
116
|
+
|
|
117
|
+
Drop the Action into a pull-request workflow. On every PR it scans the changed
|
|
118
|
+
`.tsx` files, posts inline review comments, and writes a SARIF file. Feed that
|
|
119
|
+
file to GitHub's own `upload-sarif` step and the findings also render as
|
|
120
|
+
**native code-scanning annotations** on the PR diff — the reference UX, à la
|
|
121
|
+
CodeQL. The scan is **advisory by default: it exits 0** and never blocks a merge
|
|
122
|
+
— unless you [opt into a blocking check](#optional--opt-into-a-blocking-check-default-off).
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
name: a11y
|
|
126
|
+
on: pull_request
|
|
127
|
+
|
|
128
|
+
permissions:
|
|
129
|
+
contents: read
|
|
130
|
+
pull-requests: write # inline review comments
|
|
131
|
+
security-events: write # upload SARIF as code-scanning annotations
|
|
132
|
+
|
|
133
|
+
jobs:
|
|
134
|
+
a11y:
|
|
135
|
+
runs-on: ubuntu-latest
|
|
136
|
+
steps:
|
|
137
|
+
- uses: actions/checkout@v4
|
|
138
|
+
- id: a11y
|
|
139
|
+
uses: Binclusive/a11y-checker-plugin@main
|
|
140
|
+
- if: always() # advisory gate exits 0; upload regardless of findings
|
|
141
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
142
|
+
with:
|
|
143
|
+
sarif_file: ${{ steps.a11y.outputs.sarif-file }}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Annotations land on the exact changed file + line, and each carries its
|
|
147
|
+
provenance (`deterministic` vs `agent`) in the SARIF property bag. The SARIF
|
|
148
|
+
file exists only to render on **your** GitHub — it carries file/line for local
|
|
149
|
+
annotation and is never sent to the Binclusive dashboard.
|
|
150
|
+
|
|
151
|
+
### Optional — opt into a blocking check (default off)
|
|
152
|
+
|
|
153
|
+
The check is **non-blocking by default**: it exits 0 on any severity or volume of
|
|
154
|
+
findings, so it never breaks your CI. Blocking is **strictly opt-in**. Set either
|
|
155
|
+
input below and the check **fails** (non-zero, surfaced as an Action failure) when
|
|
156
|
+
the threshold is met — the inline comments, PR summary, and SARIF still post either
|
|
157
|
+
way.
|
|
158
|
+
|
|
159
|
+
| Input | Set it to | Effect | Absent (default) |
|
|
160
|
+
|---|---|---|---|
|
|
161
|
+
| `fail-on` | `critical` \| `major` \| `minor` | Fail when any finding is **at or above** that severity (the engine's `critical < major < minor` ordering) | Non-blocking — findings never fail the check on severity |
|
|
162
|
+
| `max-violations` | an integer `N` | Fail when the total finding count **exceeds** `N` | No volume gate |
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
- id: a11y
|
|
166
|
+
uses: Binclusive/a11y-checker-plugin@main
|
|
167
|
+
with:
|
|
168
|
+
fail-on: critical # optional — block only on critical findings
|
|
169
|
+
# max-violations: 0 # optional — block on any finding at all
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Both use the **same** severity vocabulary as the engine's `check` command — there
|
|
173
|
+
is no separate CI severity map. Leaving both unset keeps the reference,
|
|
174
|
+
always-green advisory behavior.
|
|
175
|
+
|
|
176
|
+
### Optional secrets — AI lane & dashboard
|
|
177
|
+
|
|
178
|
+
The deterministic floor above needs **no account and no secret**. Two optional,
|
|
179
|
+
independent inputs unlock the extra lanes — supply neither, one, or both. Absent
|
|
180
|
+
means "lane off", never an error; the scan still exits 0.
|
|
181
|
+
|
|
182
|
+
| Input | Secret it carries | Absent → | Notes |
|
|
183
|
+
|---|---|---|---|
|
|
184
|
+
| `llm-api-key` | Your **own** LLM provider key (BYOK) | AI enrichment lane skipped; deterministic floor still runs | Provider-agnostic — no provider is baked into the Action or image. Your credential; it never leaves the runner for the dashboard. |
|
|
185
|
+
| `llm-model` | — (a model id, not a secret) | Engine default — Anthropic `claude-haiku-4-5-20251001` | Overrides the model the AI lane uses. Only meaningful with `llm-api-key`. |
|
|
186
|
+
| `llm-provider` | — (a provider id, not a secret) | Engine default — `anthropic` | Selects the AI-lane provider. Only meaningful with `llm-api-key`; an unrecognized value degrades to the deterministic floor. |
|
|
187
|
+
| `b8e-token` | A Binclusive `b8e_` apiKey | No phone-home; scan stays fully local | Mint it in the Binclusive dashboard. Metadata-only ingestion bearer. Unrelated to the LLM key. |
|
|
188
|
+
|
|
189
|
+
```yaml
|
|
190
|
+
- id: a11y
|
|
191
|
+
uses: Binclusive/a11y-checker-plugin@main
|
|
192
|
+
with:
|
|
193
|
+
llm-api-key: ${{ secrets.LLM_API_KEY }} # optional — your BYOK model key
|
|
194
|
+
llm-model: "" # optional — override the model
|
|
195
|
+
llm-provider: "" # optional — override the provider
|
|
196
|
+
b8e-token: ${{ secrets.B8E_TOKEN }} # optional — dashboard ingestion
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Store both as encrypted repo (or org) secrets. The `b8e-token` authenticates the
|
|
200
|
+
phone-home to Binclusive; the `llm-api-key` never touches Binclusive auth — the
|
|
201
|
+
two are orthogonal. No Binclusive LLM credential ships in the image or the
|
|
202
|
+
Action defaults.
|
|
203
|
+
|
|
204
|
+
### Optional — post comments as the branded Binclusive bot
|
|
205
|
+
|
|
206
|
+
By default the PR comments (both the inline per-finding comments and the single
|
|
207
|
+
rollup comment) post under the workflow's `github-token`, which GitHub attributes
|
|
208
|
+
to **`github-actions[bot]`**. Install the **Binclusive GitHub App** and supply its
|
|
209
|
+
id + private key to have the comments post under the branded App identity — name +
|
|
210
|
+
avatar — instead. This is a pure identity swap: *who* posts changes, *what* posts
|
|
211
|
+
does not, and nothing new crosses the wire.
|
|
212
|
+
|
|
213
|
+
| Input | Carries | Absent → |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| `binclusive-app-id` | The Binclusive GitHub App id | Comments post under `github-token` as `github-actions[bot]` (no change) |
|
|
216
|
+
| `binclusive-app-private-key` | The App's PEM private key (a secret) | Same fallback |
|
|
217
|
+
| `binclusive-app-installation-id` | The App's installation id (optional) | Discovered automatically from the repo |
|
|
218
|
+
|
|
219
|
+
```yaml
|
|
220
|
+
- id: a11y
|
|
221
|
+
uses: Binclusive/a11y-checker-plugin@main
|
|
222
|
+
with:
|
|
223
|
+
binclusive-app-id: ${{ vars.BINCLUSIVE_APP_ID }}
|
|
224
|
+
binclusive-app-private-key: ${{ secrets.BINCLUSIVE_APP_PRIVATE_KEY }}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Least privilege:** the App needs only **`Pull requests: write`** — enough to
|
|
228
|
+
post inline review comments and the rollup comment, nothing more. If the App is
|
|
229
|
+
unconfigured, the id/key is wrong, or the token mint fails for any reason, the
|
|
230
|
+
Action **falls back to `github-token` and still exits 0** — a failed brand is
|
|
231
|
+
never a failed check.
|
|
232
|
+
|
|
233
|
+
## Use it on any other CI/CD (generic `--ci` mode)
|
|
234
|
+
|
|
235
|
+
Not on GitHub? The engine runs the same scan on **CircleCI, Jenkins, Drone, or a
|
|
236
|
+
bare `docker run`** with no native adapter — just run the image and emit a standard
|
|
237
|
+
artifact:
|
|
238
|
+
|
|
239
|
+
```sh
|
|
240
|
+
docker run --rm -v "$PWD:/workspace" -w /workspace -e A11Y_PLATFORM=null \
|
|
241
|
+
ghcr.io/binclusive/a11y-checker:latest \
|
|
242
|
+
check /workspace/src --ci --format sarif > a11y.sarif
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`--format sarif` emits a valid **SARIF 2.1.0** log (or `--format json` for the raw
|
|
246
|
+
report); `--ci` makes the **non-blocking exit-0 a first-class engine mode** — the
|
|
247
|
+
run always exits 0 even with blocking findings, so any platform can consume the
|
|
248
|
+
artifact without failing the build. With no PR/MR context nothing is posted and the
|
|
249
|
+
artifacts still emit. Opt into a failing build with `--fail-on` / `--max-violations`.
|
|
250
|
+
|
|
251
|
+
Copy-paste CircleCI / Jenkins / Drone snippets, and the config-scaffold pattern that
|
|
252
|
+
native platform adapters build on, are in **[`docs/CI.md`](docs/CI.md)**.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Dig deeper
|
|
257
|
+
|
|
258
|
+
| If you want… | Open / read |
|
|
259
|
+
|---|---|
|
|
260
|
+
| **Adopt it with your own design system** | **`WALKTHROUGH.md`** |
|
|
261
|
+
| **Run it on any CI/CD (CircleCI / Jenkins / Drone / generic)** | **`docs/CI.md`** |
|
|
262
|
+
| **Audit a live URL or HTML page (non-React)** | **`docs/AUDIT-URL.md`** |
|
|
263
|
+
| The pitch + the moat, with numbers | `docs/decks/numbers.html` |
|
|
264
|
+
| Real findings on real OSS projects | `docs/decks/showcase.html` |
|
|
265
|
+
| How the machine works, conceptually | `docs/decks/engineering.html` |
|
|
266
|
+
| How it's built — the craft | `docs/decks/engineering-deep.html` |
|
|
267
|
+
| **The code map — which file does what** | `docs/ARCHITECTURE.md` |
|
|
268
|
+
| **The questions you're about to ask** | `FAQ.md` |
|
|
269
|
+
|
|
270
|
+
The decks are self-contained HTML — open in a browser, arrow keys to navigate, `O` for contents.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Kick the tires
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
pnpm test # all green
|
|
278
|
+
pnpm typecheck # clean
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Editor surfaces (an MCP server + a Claude Code auto-whisper hook that fixes a11y as the AI writes) are in `plugin/` — `pnpm mcp` starts the local MCP server. The CLI above is the fastest way to feel what it does.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
*Structure note: this is the `a11y-checker` package extracted to run standalone. Where `docs/ARCHITECTURE.md` says `packages/a11y-checker/src/…`, in this repo it's just `src/…`.*
|
package/bin/a11y.mjs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Runnable entry point for the published package.
|
|
4
|
+
*
|
|
5
|
+
* The package ships TypeScript source (no build step) and is launched as
|
|
6
|
+
* `npx @binclusive/a11y <command>` by the plugin's `.mcp.json` / `hooks.json`.
|
|
7
|
+
* Node can't execute `.ts` directly, so this thin JS wrapper registers the
|
|
8
|
+
* `tsx` loader (resolved from THIS package's own node_modules, so it works no
|
|
9
|
+
* matter what the caller's cwd is) and hands argv straight to `src/cli.ts`.
|
|
10
|
+
*
|
|
11
|
+
* eslint and its plugins stay ordinary dependencies — npm installs them — so
|
|
12
|
+
* there is nothing to bundle and nothing fragile to keep in sync.
|
|
13
|
+
*/
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
|
+
import { createRequire } from "node:module";
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
|
+
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
// Absolute path to tsx's self-registering entry inside this package's deps.
|
|
20
|
+
const tsxEntry = pathToFileURL(require.resolve("tsx")).href;
|
|
21
|
+
const cli = fileURLToPath(new URL("../src/cli.ts", import.meta.url));
|
|
22
|
+
|
|
23
|
+
const child = spawn(
|
|
24
|
+
process.execPath,
|
|
25
|
+
["--import", tsxEntry, cli, ...process.argv.slice(2)],
|
|
26
|
+
{ stdio: "inherit" },
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
child.on("exit", (code, signal) => {
|
|
30
|
+
if (signal) process.kill(process.pid, signal);
|
|
31
|
+
else process.exit(code ?? 0);
|
|
32
|
+
});
|
|
33
|
+
child.on("error", (err) => {
|
|
34
|
+
console.error(err instanceof Error ? err.stack : String(err));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Runnable entry for the changed-file diff-scoper (`src/diff-scope-cli.ts`),
|
|
4
|
+
* called by `entrypoint.sh` so the CI Action shares the engine's ONE scoping
|
|
5
|
+
* module instead of an inline `git diff | grep`. Mirrors `bin/a11y.mjs`: the
|
|
6
|
+
* package ships TypeScript source (no build step), so this thin JS wrapper
|
|
7
|
+
* registers the `tsx` loader (resolved from THIS package's own deps, cwd-proof)
|
|
8
|
+
* and hands off to the CLI. Prints changed `.tsx` paths, one per line.
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import { createRequire } from "node:module";
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13
|
+
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const tsxEntry = pathToFileURL(require.resolve("tsx")).href;
|
|
16
|
+
const cli = fileURLToPath(new URL("../src/diff-scope-cli.ts", import.meta.url));
|
|
17
|
+
|
|
18
|
+
const child = spawn(process.execPath, ["--import", tsxEntry, cli, ...process.argv.slice(2)], {
|
|
19
|
+
stdio: "inherit",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
child.on("exit", (code, signal) => {
|
|
23
|
+
if (signal) process.kill(process.pid, signal);
|
|
24
|
+
else process.exit(code ?? 0);
|
|
25
|
+
});
|
|
26
|
+
child.on("error", (err) => {
|
|
27
|
+
console.error(err instanceof Error ? err.stack : String(err));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|