a11ign 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.
Files changed (59) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +140 -0
  3. package/dist/action/post-comment.d.ts +20 -0
  4. package/dist/action/post-comment.d.ts.map +1 -0
  5. package/dist/action/post-comment.js +93 -0
  6. package/dist/action/post-comment.js.map +1 -0
  7. package/dist/action/run.d.ts +2 -0
  8. package/dist/action/run.d.ts.map +1 -0
  9. package/dist/action/run.js +119 -0
  10. package/dist/action/run.js.map +1 -0
  11. package/dist/action/summary.d.ts +180 -0
  12. package/dist/action/summary.d.ts.map +1 -0
  13. package/dist/action/summary.js +355 -0
  14. package/dist/action/summary.js.map +1 -0
  15. package/dist/cli.d.ts +279 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +1028 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/fault-remediation.d.ts +87 -0
  20. package/dist/fault-remediation.d.ts.map +1 -0
  21. package/dist/fault-remediation.js +156 -0
  22. package/dist/fault-remediation.js.map +1 -0
  23. package/dist/forms/config.d.ts +69 -0
  24. package/dist/forms/config.d.ts.map +1 -0
  25. package/dist/forms/config.js +185 -0
  26. package/dist/forms/config.js.map +1 -0
  27. package/dist/forms/coverage.d.ts +51 -0
  28. package/dist/forms/coverage.d.ts.map +1 -0
  29. package/dist/forms/coverage.js +80 -0
  30. package/dist/forms/coverage.js.map +1 -0
  31. package/dist/forms/draft.d.ts +62 -0
  32. package/dist/forms/draft.d.ts.map +1 -0
  33. package/dist/forms/draft.js +165 -0
  34. package/dist/forms/draft.js.map +1 -0
  35. package/dist/index.d.ts +10 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +9 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/report.d.ts +48 -0
  40. package/dist/report.d.ts.map +1 -0
  41. package/dist/report.js +322 -0
  42. package/dist/report.js.map +1 -0
  43. package/dist/scan/axe-results.d.ts +25 -0
  44. package/dist/scan/axe-results.d.ts.map +1 -0
  45. package/dist/scan/axe-results.js +111 -0
  46. package/dist/scan/axe-results.js.map +1 -0
  47. package/dist/scan/axe.d.ts +158 -0
  48. package/dist/scan/axe.d.ts.map +1 -0
  49. package/dist/scan/axe.js +203 -0
  50. package/dist/scan/axe.js.map +1 -0
  51. package/dist/scan/page-title.d.ts +3 -0
  52. package/dist/scan/page-title.d.ts.map +1 -0
  53. package/dist/scan/page-title.js +41 -0
  54. package/dist/scan/page-title.js.map +1 -0
  55. package/dist/scan/run-axe.d.ts +2 -0
  56. package/dist/scan/run-axe.d.ts.map +1 -0
  57. package/dist/scan/run-axe.js +39 -0
  58. package/dist/scan/run-axe.js.map +1 -0
  59. package/package.json +60 -0
package/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # `a11ign`
2
+
3
+ Drives a **real screen reader** through a real page and reports the WCAG 2.2 AA failures a rule scanner cannot
4
+ see — the ones that need to know what a blind user actually heard, and whether they could still finish the task.
5
+
6
+ **Alongside axe, never instead of it.** Both layers run; neither subsumes the other.
7
+
8
+ **Two ways in that work today.** No install and no Windows machine of your own — add the action to a
9
+ workflow:
10
+
11
+ ```yaml
12
+ runs-on: windows-2022 # NVDA is Windows-only; the action fails fast and says so otherwise
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: a11ign/a11ign@main
16
+ with:
17
+ url: https://example.com/contact
18
+ task: Send an enquiry
19
+ fail-on: never # report first; gate when your team asks for it
20
+ ```
21
+
22
+ Or run it yourself from a checkout:
23
+
24
+ ```bash
25
+ git clone https://github.com/a11ign/a11ign.git
26
+ cd a11ign
27
+ npm install
28
+ npm run witness -- https://example.com --task "Find the opening hours"
29
+ ```
30
+
31
+ **See the [top-level README](../../README.md) for which of those two paths is yours** — this file does not
32
+ repeat that decision.
33
+
34
+ > **`npx a11ign` does NOT work yet, and that is why it is not the first thing on this page.**
35
+ > `npx a11ign https://example.com --task "Find the opening hours"` is what the CLI *is*, and it
36
+ > works from a checkout — but no package has been pushed to npm, so run as typed it returns `E404` and
37
+ > teaches a reader only that the tool is broken. The name is still undecided (PLAN.md, B5); publishing is
38
+ > tracked as PLAN.md B7. It will move to the top of this file on the day it is true.
39
+
40
+ ## What "a rule scanner cannot see" means, concretely
41
+
42
+ Measured against the University of Washington "Accessible University" demo — a third-party, expert-built
43
+ inaccessible page and its accessible twin:
44
+
45
+ | | before (inaccessible) | after (accessible) |
46
+ |---|---|---|
47
+ | screen-reader layer | 1.1.1, 1.1.1, 4.1.2, **2.4.4**, **1.3.1** | none |
48
+ | axe | 1.4.3, 3.1.1, 1.1.1, 4.1.2, 1.4.1, 2.5.8 | none |
49
+
50
+ Two findings only the screen-reader layer produced, quoting what a user hears:
51
+
52
+ ```
53
+ 2.4.4 Link Purpose heard: "click here, link"
54
+ 1.3.1 Info & Relationships heard: "102 announcements, no heading among them"
55
+ ```
56
+
57
+ axe reports neither, and not by oversight: its `link-name` rule asks whether a link *has* an accessible name,
58
+ and "click here" has one. Meanwhile axe found four things a screen reader cannot perceive at all — contrast,
59
+ target size, language. That is the argument for running both, and the accessible twin being clean on both
60
+ matters more than either list.
61
+
62
+ ## It will not tell you about anything visual
63
+
64
+ Contrast, focus-visible, reflow, target size: a screen reader cannot see them. Every report **says so** when
65
+ the rule layer did not run, because "we checked and found nothing" and "we did not check" must never look
66
+ alike. Reporting silence as a clean bill of health is the single most misleading thing this tool could do.
67
+
68
+ ```bash
69
+ npx a11ign <url> --no-axe # screen-reader layer only, and the report says so
70
+ npx a11ign <url> --axe-results axe.json # import a run you already did
71
+ npx a11ign <url> --json # machine-readable, for CI
72
+ ```
73
+
74
+ ## You need a Windows worker
75
+
76
+ The capture runs on Windows, with NVDA, in an interactive desktop session — that is not a limitation to work
77
+ around, it is what makes the evidence real. Point the CLI at one:
78
+
79
+ ```bash
80
+ A11Y_WORKER=http://REDACTED-INTERNAL-ADDRESS:8765 npx a11ign <url> --task "..."
81
+ ```
82
+
83
+ **UTM is DEPRECATED — it was a testing path, not the fleet.** `@a11ign/worker-fleet` can still lease a
84
+ local UTM VM on macOS and put it back as it found it, and every UTM entry point now says so at runtime. Point
85
+ `A11Y_WORKER` at a Windows machine you have, or use the GitHub Action if you have none. See
86
+ `docs/getting-started.md` for setting a worker up, and `@a11ign/nvda-worker` for the worker itself.
87
+
88
+ ## Judging what the screen reader heard
89
+
90
+ By default the judge is a small trained scorer shipped with this tool — no API key, no metered cost, no
91
+ network call for the judgment itself. Point it at a rented model instead if you want a second opinion or
92
+ do not trust the local one yet:
93
+
94
+ ```bash
95
+ JUDGE_BACKEND=openai OPENAI_API_KEY=sk-... npx a11ign <url> --task "..."
96
+ ```
97
+
98
+ `JUDGE_BACKEND=openai` speaks plain `/v1/chat/completions`, so it works against hosted OpenAI, Anthropic's
99
+ OpenAI-compatible endpoint, or a local server (Ollama, LM Studio, vLLM, llama.cpp) — set `JUDGE_BASE_URL`
100
+ to point it somewhere other than `api.openai.com`. `JUDGE_API_KEY` is accepted as a project-neutral alias
101
+ for `OPENAI_API_KEY` if you would rather not put a provider's name on the variable. Other knobs, all
102
+ optional: `JUDGE_MODEL` (which model to ask for), `JUDGE_TIMEOUT_MS` (default 120000), `JUDGE_REASONING`
103
+ (default `medium`, for models that support a reasoning-effort parameter). If a server rejects
104
+ constrained/structured JSON output, set `JUDGE_STRUCTURED=off` — most reject silently in ways that read as
105
+ a truncated or malformed response, so this is the first thing to try if `openai` backend responses look
106
+ corrupted against a self-hosted server.
107
+
108
+ `codex` and `anthropic` backends also exist, for comparison; none of the three rented backends is ever the
109
+ default.
110
+
111
+ ## A page behind a consent wall is REFUSED, not reported
112
+
113
+ The screen reader gets held inside the modal, so the capture describes the dialog rather than the page.
114
+
115
+ **What the CLI actually does — this paragraph used to say "the run exits 2", and it does not.** It writes a
116
+ warning to stderr naming which kind of doubt it was, marks the result `captureVerified: false` with an
117
+ `unverifiedReason`, and **reports no findings rather than describing the dialog**. The report says so in
118
+ words. The run's exit code is decided by `--fail-on` as usual; the only thing here that exits 2 is a bad
119
+ `--forms` config. (Exit 2 on an unverified capture is `capture-check.mjs`, a lab harness, and this README
120
+ had inherited its behaviour.)
121
+
122
+ This is deliberate and it is the check that matters most in the whole tool: on one real site the census
123
+ found 793 links and 463 headings while the screen reader reached 1 heading and 0 links, and an earlier
124
+ version reported "No lived-experience findings" — for a page it had never seen. **Refusing to report is the
125
+ point; the exit code is not what does it.**
126
+
127
+ ## Rendering the report yourself
128
+
129
+ ```js
130
+ import { reportLines } from "a11ign";
131
+
132
+ console.log(reportLines(report).join("\n"));
133
+ ```
134
+
135
+ `reportLines` and the `Report` type are the entire public API. Findings come back in the order a user meets
136
+ them — perceive, then navigate, then interact — because a finding about operating a control is not useful to
137
+ someone who could not perceive it.
138
+
139
+ The pieces underneath are packages in their own right: `@a11ign/judge` for the judgment,
140
+ `@a11ign/evidence` for the capture contract, `@a11ign/worker-fleet` for the VM lifecycle.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Does a real summary file exist at `path`? The ONE fact this module exists to check before choosing a
3
+ * message, rather than assuming the earlier step succeeded because this step was reached at all --
4
+ * `if: always()` on the caller means this runs whether or not anything upstream produced output.
5
+ * @param {string} path
6
+ * @returns {boolean}
7
+ */
8
+ export declare function reportSummaryExists(path: string): boolean;
9
+ /**
10
+ * The message to print when the PR comment could not be posted, as a pure function of whether a real
11
+ * report exists -- #493's own acceptance: the two states must never print the same sentence. `reportExists:
12
+ * true` means `gh pr comment` itself failed (network, permissions, rate limit) while a real report sits in
13
+ * the job summary, so the ORIGINAL sentence is honest there and stays. `reportExists: false` means there
14
+ * was never anything to post -- and pointing the reader at a job summary that was never written is worse
15
+ * than saying nothing, per #493's own framing: "they now doubt their own reading rather than the tool."
16
+ * @param {boolean} reportExists
17
+ * @returns {string}
18
+ */
19
+ export declare function commentFailureMessage(reportExists: boolean): string;
20
+ //# sourceMappingURL=post-comment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-comment.d.ts","sourceRoot":"","sources":["../../src/action/post-comment.ts"],"names":[],"mappings":"AAyCA;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,OAAO,GAAG,MAAM,CAKnE"}
@@ -0,0 +1,93 @@
1
+ // #493: "Could not post the PR comment. The job summary still has the report" used to print unconditionally
2
+ // on ANY `gh pr comment` failure -- including the one the V1 rehearsal hit on all three of its runs, where
3
+ // the build died before `run.ts` ever produced a summary at all. `run.ts` writes $GITHUB_STEP_SUMMARY and
4
+ // its `--summary-out` file in the SAME call, after the SAME early-exit checks (a missing/unreadable result,
5
+ // or a result with no verdict) -- so an absent summary FILE means the step summary is absent too, never
6
+ // just a different place the reader missed. This repo's own most-recorded fault class -- "a diagnostic that
7
+ // cannot report itself" -- pointed at a user instead of at us.
8
+ //
9
+ // Deliberately separate from `run.ts`, mirroring that file's own split from `action.yml`: the POLICY (which
10
+ // message, and whether the file genuinely exists) is testable here without a real `gh` call; the mechanics
11
+ // of actually posting stay in `main()`, which nothing but `action.yml` invokes.
12
+ //
13
+ // #567: NO WORKSPACE IMPORT, DELIBERATELY -- this file used to import `flagValue` from
14
+ // `@a11ign/worker-fleet/cli-flags`, which needs BOTH `node_modules` and a completed build
15
+ // (`pre-install-import-graph.test.ts`'s own header). This step runs `if: always()` in `action.yml`,
16
+ // specifically so it still reports when an EARLIER step failed -- and "Install a11ign" (the step that
17
+ // creates `node_modules` in the action's own checkout) is itself one of the steps that can fail or never
18
+ // run (measured live: the V1 rehearsal's #568, a `setup-node` cache step dying before "Install a11ign"
19
+ // ever starts). So the one step whose entire job is to report honestly on failure was, itself, silently
20
+ // unable to run under exactly the failure shape it exists to handle -- the identical class #535 fixed in
21
+ // `packages/guards/src/piped-exit-status-guard.mjs` the same day, missed here because this file was only ever tested
22
+ // inside this repo's own fully-installed checkout, never through the real composite-action path a
23
+ // consumer's workflow actually uses. `flagValue`'s own body is four lines; reproduced inline below rather
24
+ // than imported.
25
+ import { existsSync } from "node:fs";
26
+ import { execFileSync } from "node:child_process";
27
+ import { pathToFileURL } from "node:url";
28
+ /**
29
+ * `flagValue`'s own logic, reproduced rather than imported -- see this file's header for why. Pure and
30
+ * tiny enough that a second copy is cheaper than the workspace dependency it would otherwise require.
31
+ * @param {string[]} argv
32
+ * @param {string} name
33
+ * @returns {string | undefined}
34
+ */
35
+ function flagValue(argv, name) {
36
+ const prefix = `--${name}=`;
37
+ const hit = argv.find((a) => a.startsWith(prefix));
38
+ return hit === undefined ? undefined : hit.slice(prefix.length);
39
+ }
40
+ /**
41
+ * Does a real summary file exist at `path`? The ONE fact this module exists to check before choosing a
42
+ * message, rather than assuming the earlier step succeeded because this step was reached at all --
43
+ * `if: always()` on the caller means this runs whether or not anything upstream produced output.
44
+ * @param {string} path
45
+ * @returns {boolean}
46
+ */
47
+ export function reportSummaryExists(path) {
48
+ return existsSync(path);
49
+ }
50
+ /**
51
+ * The message to print when the PR comment could not be posted, as a pure function of whether a real
52
+ * report exists -- #493's own acceptance: the two states must never print the same sentence. `reportExists:
53
+ * true` means `gh pr comment` itself failed (network, permissions, rate limit) while a real report sits in
54
+ * the job summary, so the ORIGINAL sentence is honest there and stays. `reportExists: false` means there
55
+ * was never anything to post -- and pointing the reader at a job summary that was never written is worse
56
+ * than saying nothing, per #493's own framing: "they now doubt their own reading rather than the tool."
57
+ * @param {boolean} reportExists
58
+ * @returns {string}
59
+ */
60
+ export function commentFailureMessage(reportExists) {
61
+ return reportExists
62
+ ? "::warning::Could not post the PR comment. The job summary still has the report."
63
+ : "::warning::No report was produced -- the run failed before a result existed to summarize. "
64
+ + "See the earlier step's own error above for what actually went wrong.";
65
+ }
66
+ function main() {
67
+ const arg = (name) => flagValue(process.argv, name);
68
+ const summaryPath = arg("summary");
69
+ const prNumber = arg("pr");
70
+ const repo = arg("repo");
71
+ if (!summaryPath || !prNumber || !repo) {
72
+ process.stderr.write("usage: tsx packages/cli/src/action/post-comment.ts --summary=<file> --pr=<n> --repo=<owner/name>\n");
73
+ process.exit(2);
74
+ }
75
+ // CHECKED, NOT ASSUMED -- the whole point of this file. A missing summary is not something `gh pr
76
+ // comment` needs to be asked about; there is nothing honest it could report by being run against a file
77
+ // that was never written.
78
+ if (!reportSummaryExists(summaryPath)) {
79
+ process.stderr.write(`${commentFailureMessage(false)}\n`);
80
+ return;
81
+ }
82
+ try {
83
+ execFileSync("gh", ["pr", "comment", prNumber, "--body-file", summaryPath, "--edit-last", "--create-if-none", "--repo", repo], { stdio: "inherit" });
84
+ }
85
+ catch {
86
+ process.stderr.write(`${commentFailureMessage(true)}\n`);
87
+ }
88
+ }
89
+ // RUN ONLY WHEN INVOKED -- `pathToFileURL`, never a template literal, for the identical reason `run.ts`
90
+ // carries beside its own copy of this guard: concatenation does not percent-encode a space in the path.
91
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href)
92
+ main();
93
+ //# sourceMappingURL=post-comment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-comment.js","sourceRoot":"","sources":["../../src/action/post-comment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/action/run.ts"],"names":[],"mappings":""}
@@ -0,0 +1,119 @@
1
+ // The Action's entry point: read a witness run's JSON, write the summary, decide the exit code.
2
+ //
3
+ // tsx packages/cli/src/action/run.ts --result=run.json [--fail-on=never|any|blocker|serious|moderate|minor]
4
+ // [--summary-out=summary.md] [--marker=a11ign]
5
+ //
6
+ // Deliberately separate from `src/cli.ts`. The CLI's job is to capture and judge; this one's job is to
7
+ // present that to GitHub and decide whether the check passes. Keeping them apart means the Action's
8
+ // policy is testable (`summary.test.ts`) without a Windows runner, and a change to how findings are
9
+ // displayed cannot break how they are produced.
10
+ import { readFileSync, writeFileSync, appendFileSync } from "node:fs";
11
+ import { resolve } from "node:path";
12
+ import { pathToFileURL } from "node:url";
13
+ import { logLines, renderSummary, shouldFail } from "./summary.js";
14
+ import { taskVerdictLabel } from "@a11ign/judge";
15
+ import { announcedStateChanges } from "@a11ign/judge/rules";
16
+ import { flagValue } from "@a11ign/worker-fleet/cli-flags";
17
+ // audit §9 "argv parsing": this was its own copy of the fifteen-file idiom. `flagValue` is the shared,
18
+ // tested extraction; `?? fallback` stays here because defaulting is this call site's business, not the
19
+ // extractor's.
20
+ /**
21
+ * The Action's whole body, so importing this file does not RUN it.
22
+ *
23
+ * It was top-level code ending in `process.exit`, which meant nothing could import it — not a test, not
24
+ * another entry point — and `entry-points.test.ts` could not see that, because it discovers entry points
25
+ * from `package.json` scripts and this one is invoked by `action.yml`. Two blindnesses, one file: a guard
26
+ * that was never there, in a place the guard for it could not look.
27
+ *
28
+ * `process.exit` stays inside rather than becoming a returned code. This IS the process's last act, the
29
+ * exit codes are the Action's documented contract (2 = we could not look, 1 = findings met the
30
+ * threshold), and routing them through a return value would put a second place where that contract can be
31
+ * got wrong. What the wrapper buys is that the code no longer runs merely because somebody imported the
32
+ * module.
33
+ */
34
+ /**
35
+ * #1391: which state changes were announced correctly is the JUDGE's call, through the same gates as
36
+ * `4.1.2:state-change-silent`; the renderer only shows the list, the way it only shows the task label. A result
37
+ * written before the probe carries no `interaction`, and then there is nothing to show.
38
+ */
39
+ function observedStateChanges(result) {
40
+ return announcedStateChanges(result.interaction?.stateChanges ?? []);
41
+ }
42
+ function main() {
43
+ const arg = (name, fallback) => flagValue(process.argv, name) ?? fallback;
44
+ const resultPath = arg("result");
45
+ if (!resultPath) {
46
+ process.stderr.write("usage: tsx packages/cli/src/action/run.ts --result=<file.json> [--fail-on=...] [--summary-out=...]\n");
47
+ process.exit(2);
48
+ }
49
+ const failOn = arg("fail-on", "never");
50
+ const marker = arg("marker", "a11ign");
51
+ let result;
52
+ try {
53
+ result = JSON.parse(readFileSync(resolve(resultPath), "utf8"));
54
+ }
55
+ catch (error) {
56
+ // A capture that never produced JSON is an infrastructure failure, not a clean page. Failing loudly
57
+ // here is the difference between "your page is fine" and "we did not manage to look at it" — the
58
+ // distinction this whole project is built around.
59
+ process.stderr.write(`a11ign: could not read the run result at ${resultPath}: ${error.message}\n`);
60
+ process.exit(2);
61
+ }
62
+ if (!result?.verdict || !Array.isArray(result.verdict.findings)) {
63
+ process.stderr.write("a11ign: the run result has no verdict — the judge did not complete, so nothing was assessed.\n");
64
+ process.exit(2);
65
+ }
66
+ const label = taskVerdictLabel();
67
+ const markdown = renderSummary(result, {
68
+ marker, taskQuestion: label.question, isTaskClaim: label.isTaskClaim, stateChangesObserved: observedStateChanges(result),
69
+ });
70
+ // An unverified capture is an infrastructure failure, not a verdict about the page — so it exits 2, the
71
+ // same code used for "could not read the result". Green would say "we checked and it is fine"; red (1)
72
+ // would say "your page has a problem". Neither is true: we did not manage to look at it.
73
+ //
74
+ // The summary is written FIRST so the reader still gets the explanation. Found on gov.uk, where the
75
+ // capture read Edge's image-magnifier overlay, the retry warned three times, and the run reported a 4.1.2
76
+ // finding about the browser's own Zoom In / Rotate buttons.
77
+ const unverified = result.captureVerified === false;
78
+ // $GITHUB_STEP_SUMMARY is append-only and shared with other steps, so append rather than overwrite.
79
+ const stepSummary = process.env.GITHUB_STEP_SUMMARY;
80
+ if (stepSummary)
81
+ appendFileSync(stepSummary, `${markdown}\n`);
82
+ const summaryOut = arg("summary-out");
83
+ if (summaryOut)
84
+ writeFileSync(resolve(summaryOut), `${markdown}\n`, "utf8");
85
+ if (!stepSummary && !summaryOut)
86
+ process.stdout.write(`${markdown}\n`);
87
+ // The summary has already been written above, so this only decides the exit code. Writing it again here
88
+ // appended it TWICE to $GITHUB_STEP_SUMMARY, which is append-only.
89
+ if (unverified) {
90
+ process.stderr.write("a11ign: the capture could not be confirmed to have read the requested page; "
91
+ + "reporting no findings. This is a failed measurement, not a clean page.\n");
92
+ process.exit(2);
93
+ }
94
+ const { findings } = result.verdict;
95
+ let fail;
96
+ try {
97
+ fail = shouldFail(findings, failOn);
98
+ }
99
+ catch (error) {
100
+ // An unrecognised `fail-on` is a workflow typo, and the dangerous outcome is treating it as "never":
101
+ // the check goes green and nobody looks again. Refuse instead.
102
+ process.stderr.write(`a11ign: ${error.message}. Use never|any|blocker|serious|moderate|minor.\n`);
103
+ process.exit(2);
104
+ }
105
+ // #1363: the log is what a reader sees without opening the summary, so it says where the examination ended
106
+ // BEFORE it counts findings -- a bare count reads as a verdict about everything the run touched.
107
+ for (const line of logLines(result, failOn))
108
+ process.stderr.write(`${line}\n`);
109
+ if (fail) {
110
+ process.stderr.write(`a11ign: failing the check — asserted findings met the ${failOn} threshold.\n`);
111
+ process.exit(1);
112
+ }
113
+ }
114
+ // RUN ONLY WHEN INVOKED. `pathToFileURL`, never a template literal: concatenation does not
115
+ // percent-encode, so a checkout under a path containing a SPACE compares false, the guard never fires,
116
+ // and the Action exits 0 having assessed nothing -- which a workflow reads as a passing check.
117
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href)
118
+ main();
119
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/action/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Render a witness run as Markdown for a GitHub job summary or PR comment, and decide pass/fail.
3
+ *
4
+ * Kept pure and separate from the Action's YAML so the output is testable without pushing a commit and
5
+ * waiting for a Windows runner. That matters more here than usual: the whole point of the Action is to
6
+ * tell someone their page has an accessibility problem, so a renderer that drops a finding, or a gate
7
+ * that passes when it should fail, is worse than no Action at all.
8
+ */
9
+ export type Severity = "blocker" | "serious" | "moderate" | "minor";
10
+ import type { CaptureInteraction } from "@a11ign/evidence";
11
+ import type { Judgment } from "@a11ign/judge";
12
+ import type { announcedStateChanges } from "@a11ign/judge/rules";
13
+ export interface RunFinding {
14
+ issue: string;
15
+ wcag: string;
16
+ severity: Severity;
17
+ evidence: string;
18
+ confidence: number;
19
+ /**
20
+ * #1366: whether this finding ASSERTS the criterion (`conformance`) or REFERS it (absent or `secondary`) -- the
21
+ * judge's own `RequirementMapping`, taken from its `Judgment` type so it has one definition. The result JSON has
22
+ * carried it all along; this renderer dropped it, so a referral rated `serious` was logged as "1 serious".
23
+ */
24
+ mapping?: Judgment["findings"][number]["mapping"];
25
+ }
26
+ export interface RunResult {
27
+ /**
28
+ * False when the capture could not be confirmed to have read the requested page.
29
+ *
30
+ * Not cosmetic. On gov.uk the capture read Edge's image-magnifier overlay, the retry fired three times
31
+ * and warned, and the run still reported a 4.1.2 finding about the browser's own Zoom In / Rotate
32
+ * buttons as if the site were at fault. Findings from an unverified capture are not merely unreliable,
33
+ * they can blame a page for its browser — so they are not shown at all.
34
+ */
35
+ captureVerified?: boolean;
36
+ /**
37
+ * Why the capture is unverified: `"wrong-content"` (it read something else) or `"contained"` (it read
38
+ * only part of the right page, almost always a consent or cookie modal holding the screen reader).
39
+ * Absent on older results, which are explained with the original wrong-content wording.
40
+ */
41
+ captureUnverifiedReason?: "wrong-content" | "contained";
42
+ url: string;
43
+ task: string;
44
+ screenReader: string;
45
+ transcript?: string[];
46
+ /** null when the rule layer did not run — NOT the same as running and finding nothing. */
47
+ ruleBased: {
48
+ impact: string;
49
+ wcag: string[];
50
+ rule: string;
51
+ help: string;
52
+ nodes?: {
53
+ target?: unknown[];
54
+ }[];
55
+ }[] | null;
56
+ verdict: {
57
+ taskCompletable: boolean;
58
+ summary: string;
59
+ findings: RunFinding[];
60
+ confidence: number;
61
+ };
62
+ /**
63
+ * Per-criterion ACT outcomes, when the result carries them.
64
+ *
65
+ * `cli.ts --json` has emitted these all along and this renderer dropped them, so the most PUBLIC output
66
+ * this tool produces — a comment on somebody's pull request, in bold — could not say `cantTell`. A page
67
+ * where six of the ten covered criteria came back undetermined rendered identically to one where all ten
68
+ * passed. The CLI prints the tally with "Neither is clean"; the PR comment could not.
69
+ *
70
+ * Optional because an older result JSON has none, and absent must render as SILENCE rather than as a
71
+ * tally of zeroes — a fabricated "0 untested" would be worse than the omission it replaced.
72
+ */
73
+ outcomes?: {
74
+ criterion: string;
75
+ outcome: string;
76
+ reason: string;
77
+ }[];
78
+ /**
79
+ * Where the examination ENDED because an activation took the browser off the page's site (#1363). Nothing
80
+ * after it was examined and no finding describes it. Absent on results whose activations stayed on the
81
+ * page, and on results written before the check existed.
82
+ */
83
+ leftSite?: {
84
+ control: string;
85
+ to: string | null;
86
+ source: "recorded" | "derived";
87
+ } | null;
88
+ /**
89
+ * The disclosure probe's before/after pairs: a control as first announced, and what took focus after it was
90
+ * activated. Declared for #1391 only -- `run.ts` reads it to compute which changes were announced correctly --
91
+ * and TYPED FROM `@a11ign/evidence`'s own `CaptureInteraction`, never restated, so the shape has one definition.
92
+ * The import is `import type`: erased at runtime, so this renderer still imports nothing when it runs. Absent on
93
+ * results written before the probe.
94
+ */
95
+ interaction?: Partial<Pick<CaptureInteraction, "stateChanges">> | null;
96
+ /**
97
+ * WCAG §5.2's five conformance requirements, as `cli.ts --json` emits them (`@a11ign/evidence/conformance`).
98
+ * Declared for one sentence only (#1387): Requirement 2 names a capture that spanned more than one document,
99
+ * and until this field was declared the summary could not show it. Absent on older results, which say nothing.
100
+ */
101
+ conformance?: {
102
+ number: number;
103
+ name: string;
104
+ establishes: string;
105
+ limitation: string;
106
+ }[];
107
+ }
108
+ export declare function documentsSpannedSentence(conformance: RunResult["conformance"]): string | null;
109
+ /** How many undetermined criteria rest on an examination known to be partial. Zero with no outcomes at all. */
110
+ export declare function partialExaminationCount(outcomes: RunResult["outcomes"]): number;
111
+ export type FailOn = "never" | "any" | Severity;
112
+ /**
113
+ * Should this run fail the check?
114
+ *
115
+ * `never` is the default deliberately. A tool that starts failing builds the day it is installed gets
116
+ * uninstalled; one that reports first, and fails when the team asks it to, gets adopted. The same
117
+ * reasoning is why the rule layer is opt-out rather than mandatory.
118
+ */
119
+ export declare function shouldFail(findings: RunFinding[], failOn: FailOn): boolean;
120
+ /**
121
+ * #1388: IS THIS RULE-LAYER ROW ABOUT CONTENT INSIDE A FRAME?
122
+ *
123
+ * axe's `target` holds one selector per frame boundary it crossed, so a target of more than one entry points inside an
124
+ * iframe. It says the node is in A frame, never WHOSE: a same-origin frame is the author's own, and the result records no
125
+ * frame origin, so the marker claims no more than that. A single entry that is itself an array is a shadow-DOM path, not
126
+ * a frame. A row with no nodes (an older result) cannot be told, so it is not marked.
127
+ */
128
+ export declare function insideFrame(row: NonNullable<RunResult["ruleBased"]>[number]): boolean;
129
+ /**
130
+ * #1391: ONE STATE CHANGE ANNOUNCED CORRECTLY -- exactly the judge's `announcedStateChanges` element type, taken from
131
+ * it by a type-only import (erased at runtime; this renderer still loads nothing), so the shape has one definition.
132
+ * `from` and `to` are the expandable-state words the rule reads; `control` and `after` are the two announcements.
133
+ */
134
+ export type ObservedStateChange = ReturnType<typeof announcedStateChanges>[number];
135
+ export interface SummaryOptions {
136
+ /** Rows per table. Bounded because a job summary has a hard size limit and truncation must be stated. */
137
+ limit?: number;
138
+ /** Included as an HTML comment so a PR comment can be found and UPDATED rather than duplicated. */
139
+ marker?: string;
140
+ /**
141
+ * What `taskCompletable` should be LABELLED as, because it means different things per backend.
142
+ *
143
+ * Passed in rather than read from the environment here: this renderer is deliberately pure so the
144
+ * output is testable without a Windows runner, and reading process.env would end that.
145
+ *
146
+ * The default is the honest one for the shipped `local` scorer, which has no head for task completion
147
+ * and never sees the task — local-judge.ts computes `!findings.some(f => f.severity === "blocker")`
148
+ * and its own comment says claiming a task answer would be inventing one. This comment is posted on
149
+ * someone's PULL REQUEST in bold, so inventing it there is the worst place to do it. The LLM backends
150
+ * do read the task, and pass their own wording.
151
+ */
152
+ taskQuestion?: string;
153
+ /**
154
+ * `taskVerdictLabel().isTaskClaim` (`@a11ign/judge`), passed in for the identical reason `taskQuestion`
155
+ * is: this renderer is pure. `report.ts`'s own `verdictHeadline` already makes this split -- a bare
156
+ * "Yes"/"No" only when `taskCompletable` really answers a question about the task (the LLM backends).
157
+ * For the shipped `local` scorer it is `!findings.some(f => f.severity === "blocker")`, so a report of
158
+ * six SERIOUS findings answered "**No blocking findings** Yes" until this flag existed: true, and
159
+ * indistinguishable from a clean page to anyone who did not read past the first line. Defaults to
160
+ * `false`, the honest default for the backend that ships.
161
+ */
162
+ isTaskClaim?: boolean;
163
+ /**
164
+ * #1391: the state changes the screen reader announced correctly, computed by `run.ts` through the judge's
165
+ * `announcedStateChanges` -- the SAME gates as the `4.1.2:state-change-silent` rule, so this renderer never
166
+ * decides what counts. Passed in for the identical reason `taskQuestion` is: this renderer is pure. Absent or
167
+ * empty renders nothing, which is also what a result without the probe gets.
168
+ */
169
+ stateChangesObserved?: readonly ObservedStateChange[];
170
+ }
171
+ /**
172
+ * The Action's log lines -- EXPORTED so a test drives the real wording (#1363).
173
+ *
174
+ * When the examination ended because an activation left the site, that is said FIRST. Rehearsal 2's log read
175
+ * `a11ign: 1 finding(s) (1 serious)` about a finding observed on youtube.com, and a reader of that line alone
176
+ * would have filed a 2.4.2 bug against the W3C.
177
+ */
178
+ export declare function logLines(result: RunResult, failOn: FailOn): string[];
179
+ export declare function renderSummary(result: RunResult, options?: SummaryOptions): string;
180
+ //# sourceMappingURL=summary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../../src/action/summary.ts"],"names":[],"mappings":"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"}