ultra11y 2.32.1 → 3.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.
@@ -10,7 +10,7 @@
10
10
  "name": "ultra11y",
11
11
  "source": "./",
12
12
  "description": "Automatic accessibility review of the code under change before every commit, push and pull request, plus full WCAG 2.2 AA / RGAA audits and conformance deliverables on demand.",
13
- "version": "2.32.1",
13
+ "version": "3.0.0",
14
14
  "author": { "name": "maxgfr" },
15
15
  "homepage": "https://github.com/maxgfr/ultra11y",
16
16
  "repository": "https://github.com/maxgfr/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
4
- "version": "2.32.1",
4
+ "version": "3.0.0",
5
5
  "author": { "name": "maxgfr" },
6
6
  "homepage": "https://github.com/maxgfr/ultra11y",
7
7
  "repository": "https://github.com/maxgfr/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
4
- "version": "2.32.1",
4
+ "version": "3.0.0",
5
5
  "author": { "name": "maxgfr", "url": "https://github.com/maxgfr" },
6
6
  "homepage": "https://github.com/maxgfr/ultra11y",
7
7
  "repository": "https://github.com/maxgfr/ultra11y",
@@ -27,7 +27,7 @@ import { fileURLToPath } from "node:url";
27
27
  // Ownership marker: `ultra11y install --opencode` refuses to overwrite a plugin file that
28
28
  // does not carry it, so a user's own ultra11y.js is never clobbered.
29
29
  const ULTRA11Y_OPENCODE_PLUGIN = "ULTRA11Y_OPENCODE_PLUGIN";
30
- const ULTRA11Y_PLUGIN_VERSION = "2.32.1";
30
+ const ULTRA11Y_PLUGIN_VERSION = "3.0.0";
31
31
 
32
32
  const here = dirname(fileURLToPath(import.meta.url));
33
33
 
package/README.md CHANGED
@@ -351,7 +351,9 @@ a test). See [`CONTRIBUTING.md`](CONTRIBUTING.md) and `skills/ultra11y/reference
351
351
  optionally, the **pages** (it can start your app, wait for it, then scan real URLs or your
352
352
  declared sample). The engine ships inside the action, so there is nothing to install and no
353
353
  `setup-node`. The gate runs **last**, after SARIF, annotations, the summary, the sticky PR
354
- comment and the report — a red job is never a dead end. `ultra11y init --ci` writes a
354
+ comment and the report — a red job is never a dead end. `adjudicate: api|agent` optionally
355
+ rules on the judgment criteria that would otherwise stay « à évaluer » in CI, reading its key
356
+ from the job environment and skipping itself without one. `ultra11y init --ci` writes a
355
357
  workflow using it. See `references/ci.md`.
356
358
  - **CI surfaces** — a red job says *that* the build broke, not *where*. `--format sarif` emits
357
359
  SARIF 2.1.0 for GitHub code scanning, so each finding lands as an **inline annotation on the
@@ -378,6 +380,15 @@ adjudication leaves the audit untouched. All-`manual` with reasons is accepted,
378
380
  a correct answer. It is the **only** part of the tool that takes an API key; without one the
379
381
  command explains itself and exits, and nothing else changes.
380
382
 
383
+ The GitHub Action wires this as `adjudicate: api`, and offers `adjudicate: agent` as the other
384
+ half — the same worklist handed to a `claude-code-action` run, which can **open the cited files**
385
+ instead of ruling from harvested evidence alone. Both end in the same fail-closed gate; they
386
+ differ in evidence, not in trust. Both read `ANTHROPIC_API_KEY` from the **job environment**,
387
+ never from an input, and **skip themselves when it is absent** — which is exactly what a fork's
388
+ pull request looks like, so the job stays green and the criteria stay « à évaluer ». See
389
+ `references/ci.md` for the cost per run and for `gate-adjudicated`, which lets a model-ruled
390
+ non-conformity fail the build at the price of a red/green that no longer reproduces.
391
+
381
392
  ### Optional dynamic tier (axe-core)
382
393
 
383
394
  `scan` runs **axe-core in a headless browser** to decide the *needs-rendering* criteria the static engine leaves as residual risks — chiefly **computed colour contrast (1.4.3)** plus a 320px **reflow** check (1.4.10) and a render cross-check of the structural rules. Two runtimes (default `--runtime auto`): **`--runtime local`** uses a Playwright that resolves from your project (`--cwd`) — **no Docker** — and additionally probes focus visibility (2.4.7), 200% zoom (1.4.4), text spacing (1.4.12) and content-on-hover (1.4.13), plus **stateful** probes (fill inputs then re-measure overflow, and a live-region probe for status messages 4.1.3 — opt out with `--no-interact`, `--interact-clicks` for authed-scan button clicks), and takes `--storage-state` for authenticated pages; **`--runtime docker`** falls back to a self-contained image built on first use. `--merge` folds the findings into a static `AuditResult`, upgrading `manual` criteria to `C`/`NC`. A country-standard audit scans its declared page sample with `scan --sample` (lint it first with `sample check`):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultra11y",
3
- "version": "2.32.1",
3
+ "version": "3.0.0",
4
4
  "description": "Audit HTML/CSS/JSX against WCAG 2.2 AA and produce a dated compliance report, or author/review accessible markup (native-HTML-first). A skills.sh agent skill: an install-free static engine (bundled, no npm install) plus the agent's judgment, with check/verify gates against hallucinated non-conformities, and pluggable in-repo standards packs (RGAA France first; Section 508 & EN 301 549 next).",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.33.0",
@@ -6,7 +6,7 @@ import { join as join45, relative as relative5, sep as sep7, dirname as dirname1
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "2.32.1";
9
+ var VERSION = "3.0.0";
10
10
  var SCHEMA_VERSION = 2;
11
11
 
12
12
  // src/audit.ts
@@ -58277,6 +58277,7 @@ Usage:
58277
58277
  ultra11y audit [--changed | --since <ref> | --staged] [--max-files <n>] [--dedup exact|normalized|off] [--baseline <file>] [--fail-on blocking|major|minor]
58278
58278
  ultra11y audit [--captures <dir>] [--no-captures] [--require-captures] (rendered-DOM captures + .ultra11y/pages snapshots: audit real HTML)
58279
58279
  ultra11y audit [--format sarif|github] (CI: SARIF for code scanning, or inline annotations + job summary)
58280
+ ultra11y audit --in <audit.json> [--fail-on blocking|major|minor] [--format sarif|github] (re-gate an audit already computed \u2014 e.g. one carrying adjudicated verdicts \u2014 without a second detection pass)
58280
58281
  ultra11y report --in <audit.json> [--out <dir>] [--standard <pack>] [--format sarif|github] [--lang auto|en|fr]
58281
58282
  ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--lang auto|en|fr]
58282
58283
  ultra11y tickets --in <audit.json> [--provider auto|github|gitlab|jira] [--grain criterion|page|page-criterion|single|file] [--transport auto|cli|rest]
@@ -58869,7 +58870,73 @@ function emitCiFormat(result, format, standard, lang, failOn) {
58869
58870
  );
58870
58871
  }
58871
58872
  }
58873
+ async function cmdAuditFromFile(p, inPath) {
58874
+ const scoping = [
58875
+ "since",
58876
+ "changed",
58877
+ "staged",
58878
+ "baseline",
58879
+ "graph",
58880
+ "cross-file",
58881
+ "jsx",
58882
+ "include",
58883
+ "exclude",
58884
+ "ext",
58885
+ "max-files",
58886
+ "dedup",
58887
+ "captures",
58888
+ "no-captures",
58889
+ "require-captures",
58890
+ "no-default-excludes",
58891
+ "out"
58892
+ ];
58893
+ const offenders = scoping.filter((f) => p.flags[f] !== void 0);
58894
+ if (p.positionals.length) offenders.unshift("<paths>");
58895
+ if (offenders.length) {
58896
+ console.error(
58897
+ `ultra11y audit: --in re-gates an audit that already exists, so these have nothing left to change: ${offenders.join(", ")}. Drop them, or run a fresh audit without --in.`
58898
+ );
58899
+ return 2;
58900
+ }
58901
+ let result;
58902
+ try {
58903
+ const parsed = JSON.parse(inPath === "-" ? await readStdin() : readText(inPath));
58904
+ if (!isCurrentAudit(parsed)) {
58905
+ console.error("ultra11y audit: --in is not a current ultra11y AuditResult (WCAG-keyed, schema v2). Re-run `audit`.");
58906
+ return 2;
58907
+ }
58908
+ result = parsed;
58909
+ } catch {
58910
+ console.error(`ultra11y audit: --in file not found or not valid JSON: ${inPath}.`);
58911
+ return 2;
58912
+ }
58913
+ const lang = resolveLang(p.flags, { audit: result });
58914
+ const failOnRaw = p.flags["fail-on"];
58915
+ const failOnParsed = parseFailOn(failOnRaw);
58916
+ if (failOnRaw !== void 0 && failOnParsed === null) {
58917
+ console.error(`ultra11y audit: --fail-on must be blocking|major|minor (got "${String(failOnRaw)}").`);
58918
+ return 2;
58919
+ }
58920
+ const ciFormat = parseCiFormat(p.flags.format);
58921
+ if (ciFormat === null) {
58922
+ console.error(`ultra11y audit: --format must be sarif|github (got "${String(p.flags.format)}").`);
58923
+ return 2;
58924
+ }
58925
+ const failOnSet = failOnRaw !== void 0;
58926
+ const failOn = failOnSet ? failOnParsed ?? "bloquant" : void 0;
58927
+ const failing = failOn ? findingsAtOrAbove(result.findings, failOn) : [];
58928
+ if (ciFormat) emitCiFormat(result, ciFormat, CORE2, lang, failOn);
58929
+ else if (p.flags.json) console.log(JSON.stringify(result, null, 2));
58930
+ else {
58931
+ console.log(auditSummary(result, lang));
58932
+ if (failOnSet && failing.length)
58933
+ console.error(lang === "fr" ? `\u2717 ${failing.length} non-conformit\xE9(s) \u2265 ${failOn}.` : `\u2717 ${failing.length} non-conformity(ies) \u2265 ${failOn}.`);
58934
+ }
58935
+ return failing.length ? 1 : 0;
58936
+ }
58872
58937
  async function cmdAudit(p) {
58938
+ const inFlag = p.flags.in;
58939
+ if (typeof inFlag === "string" && inFlag) return cmdAuditFromFile(p, inFlag);
58873
58940
  const inputs = p.positionals.length ? p.positionals : ["."];
58874
58941
  if (inputs.length === 0) {
58875
58942
  console.error("ultra11y audit: provide files/globs, or '-' to read stdin.");
@@ -5,7 +5,7 @@ when_to_use: "Invoke WITHOUT being asked whenever changed front-end code should
5
5
  allowed-tools: Bash(node ${CLAUDE_SKILL_DIR}/scripts/ultra11y.mjs *), Read, Grep, Glob
6
6
  license: MIT
7
7
  metadata:
8
- version: 2.32.1
8
+ version: 3.0.0
9
9
  ---
10
10
 
11
11
  # review-a11y — accessibility review of changed code
@@ -6,7 +6,7 @@ import { join as join45, relative as relative5, sep as sep7, dirname as dirname1
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "2.32.1";
9
+ var VERSION = "3.0.0";
10
10
  var SCHEMA_VERSION = 2;
11
11
 
12
12
  // src/audit.ts
@@ -58277,6 +58277,7 @@ Usage:
58277
58277
  ultra11y audit [--changed | --since <ref> | --staged] [--max-files <n>] [--dedup exact|normalized|off] [--baseline <file>] [--fail-on blocking|major|minor]
58278
58278
  ultra11y audit [--captures <dir>] [--no-captures] [--require-captures] (rendered-DOM captures + .ultra11y/pages snapshots: audit real HTML)
58279
58279
  ultra11y audit [--format sarif|github] (CI: SARIF for code scanning, or inline annotations + job summary)
58280
+ ultra11y audit --in <audit.json> [--fail-on blocking|major|minor] [--format sarif|github] (re-gate an audit already computed \u2014 e.g. one carrying adjudicated verdicts \u2014 without a second detection pass)
58280
58281
  ultra11y report --in <audit.json> [--out <dir>] [--standard <pack>] [--format sarif|github] [--lang auto|en|fr]
58281
58282
  ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--lang auto|en|fr]
58282
58283
  ultra11y tickets --in <audit.json> [--provider auto|github|gitlab|jira] [--grain criterion|page|page-criterion|single|file] [--transport auto|cli|rest]
@@ -58869,7 +58870,73 @@ function emitCiFormat(result, format, standard, lang, failOn) {
58869
58870
  );
58870
58871
  }
58871
58872
  }
58873
+ async function cmdAuditFromFile(p, inPath) {
58874
+ const scoping = [
58875
+ "since",
58876
+ "changed",
58877
+ "staged",
58878
+ "baseline",
58879
+ "graph",
58880
+ "cross-file",
58881
+ "jsx",
58882
+ "include",
58883
+ "exclude",
58884
+ "ext",
58885
+ "max-files",
58886
+ "dedup",
58887
+ "captures",
58888
+ "no-captures",
58889
+ "require-captures",
58890
+ "no-default-excludes",
58891
+ "out"
58892
+ ];
58893
+ const offenders = scoping.filter((f) => p.flags[f] !== void 0);
58894
+ if (p.positionals.length) offenders.unshift("<paths>");
58895
+ if (offenders.length) {
58896
+ console.error(
58897
+ `ultra11y audit: --in re-gates an audit that already exists, so these have nothing left to change: ${offenders.join(", ")}. Drop them, or run a fresh audit without --in.`
58898
+ );
58899
+ return 2;
58900
+ }
58901
+ let result;
58902
+ try {
58903
+ const parsed = JSON.parse(inPath === "-" ? await readStdin() : readText(inPath));
58904
+ if (!isCurrentAudit(parsed)) {
58905
+ console.error("ultra11y audit: --in is not a current ultra11y AuditResult (WCAG-keyed, schema v2). Re-run `audit`.");
58906
+ return 2;
58907
+ }
58908
+ result = parsed;
58909
+ } catch {
58910
+ console.error(`ultra11y audit: --in file not found or not valid JSON: ${inPath}.`);
58911
+ return 2;
58912
+ }
58913
+ const lang = resolveLang(p.flags, { audit: result });
58914
+ const failOnRaw = p.flags["fail-on"];
58915
+ const failOnParsed = parseFailOn(failOnRaw);
58916
+ if (failOnRaw !== void 0 && failOnParsed === null) {
58917
+ console.error(`ultra11y audit: --fail-on must be blocking|major|minor (got "${String(failOnRaw)}").`);
58918
+ return 2;
58919
+ }
58920
+ const ciFormat = parseCiFormat(p.flags.format);
58921
+ if (ciFormat === null) {
58922
+ console.error(`ultra11y audit: --format must be sarif|github (got "${String(p.flags.format)}").`);
58923
+ return 2;
58924
+ }
58925
+ const failOnSet = failOnRaw !== void 0;
58926
+ const failOn = failOnSet ? failOnParsed ?? "bloquant" : void 0;
58927
+ const failing = failOn ? findingsAtOrAbove(result.findings, failOn) : [];
58928
+ if (ciFormat) emitCiFormat(result, ciFormat, CORE2, lang, failOn);
58929
+ else if (p.flags.json) console.log(JSON.stringify(result, null, 2));
58930
+ else {
58931
+ console.log(auditSummary(result, lang));
58932
+ if (failOnSet && failing.length)
58933
+ console.error(lang === "fr" ? `\u2717 ${failing.length} non-conformit\xE9(s) \u2265 ${failOn}.` : `\u2717 ${failing.length} non-conformity(ies) \u2265 ${failOn}.`);
58934
+ }
58935
+ return failing.length ? 1 : 0;
58936
+ }
58872
58937
  async function cmdAudit(p) {
58938
+ const inFlag = p.flags.in;
58939
+ if (typeof inFlag === "string" && inFlag) return cmdAuditFromFile(p, inFlag);
58873
58940
  const inputs = p.positionals.length ? p.positionals : ["."];
58874
58941
  if (inputs.length === 0) {
58875
58942
  console.error("ultra11y audit: provide files/globs, or '-' to read stdin.");
@@ -4,7 +4,7 @@ description: "Use to AUDIT existing HTML/CSS/JSX against WCAG 2.2 AA accessibili
4
4
  when_to_use: "Invoke when the user asks for an accessibility AUDIT or a formal deliverable: audit a repo, site or page against WCAG 2.2 AA or a country standard (RGAA, Section 508, EN 301 549); produce a dated conformance report, a criterion grid, a PRD backlog, or tickets (GitHub/GitLab/Jira); author accessible markup from scratch; wire the repo gate (init --hook/--ci) or a rendered-DOM capture pipeline. For a review of the code UNDER CHANGE (diff, branch, PR), use the `review-a11y` skill instead."
5
5
  license: MIT
6
6
  metadata:
7
- version: 2.32.1
7
+ version: 3.0.0
8
8
  ---
9
9
 
10
10
  # ultra11y — audit WCAG 2.2 AA and write accessible markup
@@ -53,6 +53,69 @@ run and an RGAA run coexist rather than overwriting each other. The Markdown rep
53
53
  through `check` before being uploaded, so CI cannot publish a report citing an invented
54
54
  criterion.
55
55
 
56
+ ## Adjudicating the judgment criteria in CI (`adjudicate`)
57
+
58
+ Of the 55 WCAG 2.2 AA criteria the engine decides a handful; **38 are judgment calls**, and
59
+ under RGAA **81 of 106** can only ever derive `manual`. Inside a coding agent the agent rules
60
+ on them. In CI nobody does, so they stay « à évaluer » and the published conformance rate is
61
+ partial by construction. `adjudicate` closes that — opt-in, in two modes.
62
+
63
+ ```yaml
64
+ env:
65
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} # the job's env, never an input
66
+ steps:
67
+ - uses: maxgfr/ultra11y@v2
68
+ with:
69
+ standard: rgaa
70
+ adjudicate: api # or `agent`
71
+ ```
72
+
73
+ | input | default | |
74
+ |---|---|---|
75
+ | `adjudicate` | `none` | `api` · `agent` · `none` |
76
+ | `adjudicate-model` | *(empty)* | model id for `api`; else `$ULTRA11Y_LLM_MODEL`, else the engine default |
77
+ | `gate-adjudicated` | `false` | let a model-ruled NC fail the job |
78
+
79
+ **`api`** sends the worklist to the Messages API in batches of 8 and folds the verdicts back.
80
+ **`agent`** emits the worklist plus `orchestrate --eco`'s runbook and hands them to a
81
+ `claude-code-action` run, then folds the same way. Both end in the **same fail-closed gate** an
82
+ agent's verdicts pass through, so neither can assert a conformance the engine refuses.
83
+
84
+ The difference between them is **evidence, not trust**. `judge` rules from the harvested
85
+ evidence alone — capped at 30 items per criterion, snippets truncated — while the agent can
86
+ open the cited files and read around them, which is what *link purpose **in context*** actually
87
+ asks for. Expect the API mode to answer `manual` more often; that is it being honest, not
88
+ broken. The metric that separates them is the count of criteria left to assess.
89
+
90
+ **Both modes read `ANTHROPIC_API_KEY` from the job environment, never from an input** — a
91
+ composite action's steps inherit it, and a secret that never travels through an input is one
92
+ fewer secret to leak into a log. When the key is absent the tier **skips itself**: the job stays
93
+ green, the report is still written, and the criteria stay « à évaluer ». That is not an edge
94
+ case — it is precisely what a **fork's pull request** looks like, where secrets are never
95
+ exposed. An adjudication that fails mid-flight (a rate-limited batch refuses the whole fold) is
96
+ absorbed the same way, because losing the verdicts must never cost you the audit.
97
+
98
+ **Cost is per run and does not amortise.** Roughly $0.20 for a WCAG run and $0.50 for RGAA at
99
+ Sonnet pricing — the worklist is re-sent whole each time and no batch is large enough to earn
100
+ prompt caching. On a busy repository that is real money per push; this belongs on the default
101
+ branch or a schedule far more often than on every PR.
102
+
103
+ **`gate-adjudicated` trades reproducibility for reach.** By default the gate re-audits the
104
+ **source**, so the red/green is a pure function of the commit whatever a model said about it.
105
+ Turn it on and the gate evaluates the adjudicated audit instead (`audit --in`), letting a
106
+ model-ruled non-conformity fail the job — and accepting that two runs on the same commit may
107
+ now disagree. Default off, deliberately.
108
+
109
+ ```sh
110
+ # The same re-gate, outside the action:
111
+ node scripts/ultra11y.mjs audit --in audits/audit-latest.json --fail-on blocking
112
+ ```
113
+
114
+ `--in` re-gates an audit that already exists rather than computing one, which is the only way
115
+ to gate on verdicts a second detection pass would not see. It refuses every flag that would
116
+ change *what* is audited (`--since`, `--baseline`, paths…): a gate that silently dropped your
117
+ scoping would be a gate nobody could trust.
118
+
56
119
  # The `--format` renderings
57
120
 
58
121
  A gate tells a developer *that* the build failed; it does not put the non-conformity **on the
@@ -151,6 +151,14 @@ A rejected adjudication leaves `audit-latest.json` untouched.
151
151
  **Strictly opt-in.** With no key the command explains itself and exits 2; nothing else in the
152
152
  engine changes.
153
153
 
154
+ **In the GitHub Action.** `adjudicate: api` runs exactly the command above; `adjudicate: agent`
155
+ hands the same worklist — plus `orchestrate --eco`'s runbook and the adjudicator contract — to
156
+ a `claude-code-action` run, then folds it with `verify --apply`. The agent mode exists because
157
+ `judge` rules from the harvested evidence alone (30 items per criterion, snippets truncated)
158
+ while an agent can open the cited files, which is what *link purpose in context* really needs.
159
+ Both skip themselves when `ANTHROPIC_API_KEY` is absent from the job environment, and both
160
+ absorb their own failure rather than taking the audit down with them. See `references/ci.md`.
161
+
154
162
  `--max` bounds the spend and says out loud which criteria it did not submit. It is **refused
155
163
  together with `--apply`**, before any call is made: the gate rejects an incomplete
156
164
  adjudication by design, so the pair could only ever bill you for a guaranteed failure. Drop
@@ -64,6 +64,13 @@ verdict with no justification, an NC with no resolvable `normativeRef`, a criter
64
64
  from the adjudication — each is refused by name, and the run stops. That is what keeps the
65
65
  final report defensible.
66
66
 
67
+ **The shipped GitHub Action now runs this pipeline for you.** `adjudicate: agent` performs
68
+ steps 2–4 — `verify --manual`, then `orchestrate --run audits --phase adjudicate --eco`, then a
69
+ `claude-code-action` run pointed at the emitted `RUNBOOK.md`, then `verify --apply`. Reach for
70
+ the raw pipeline above when your orchestrator is *not* GitHub Actions, or when you want to fan
71
+ step 3 out across subagents rather than walk the eco path. Either way steps 1, 2, 4, 5 and 6
72
+ stay deterministic, and only step 3 involves a model.
73
+
67
74
  ## The adjudication contract
68
75
 
69
76
  `ADJUDICATE.todo.json` declares what may be written into it, so the filler never has to read
@@ -6,7 +6,7 @@ import { join as join45, relative as relative5, sep as sep7, dirname as dirname1
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "2.32.1";
9
+ var VERSION = "3.0.0";
10
10
  var SCHEMA_VERSION = 2;
11
11
 
12
12
  // src/audit.ts
@@ -58277,6 +58277,7 @@ Usage:
58277
58277
  ultra11y audit [--changed | --since <ref> | --staged] [--max-files <n>] [--dedup exact|normalized|off] [--baseline <file>] [--fail-on blocking|major|minor]
58278
58278
  ultra11y audit [--captures <dir>] [--no-captures] [--require-captures] (rendered-DOM captures + .ultra11y/pages snapshots: audit real HTML)
58279
58279
  ultra11y audit [--format sarif|github] (CI: SARIF for code scanning, or inline annotations + job summary)
58280
+ ultra11y audit --in <audit.json> [--fail-on blocking|major|minor] [--format sarif|github] (re-gate an audit already computed \u2014 e.g. one carrying adjudicated verdicts \u2014 without a second detection pass)
58280
58281
  ultra11y report --in <audit.json> [--out <dir>] [--standard <pack>] [--format sarif|github] [--lang auto|en|fr]
58281
58282
  ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--lang auto|en|fr]
58282
58283
  ultra11y tickets --in <audit.json> [--provider auto|github|gitlab|jira] [--grain criterion|page|page-criterion|single|file] [--transport auto|cli|rest]
@@ -58869,7 +58870,73 @@ function emitCiFormat(result, format, standard, lang, failOn) {
58869
58870
  );
58870
58871
  }
58871
58872
  }
58873
+ async function cmdAuditFromFile(p, inPath) {
58874
+ const scoping = [
58875
+ "since",
58876
+ "changed",
58877
+ "staged",
58878
+ "baseline",
58879
+ "graph",
58880
+ "cross-file",
58881
+ "jsx",
58882
+ "include",
58883
+ "exclude",
58884
+ "ext",
58885
+ "max-files",
58886
+ "dedup",
58887
+ "captures",
58888
+ "no-captures",
58889
+ "require-captures",
58890
+ "no-default-excludes",
58891
+ "out"
58892
+ ];
58893
+ const offenders = scoping.filter((f) => p.flags[f] !== void 0);
58894
+ if (p.positionals.length) offenders.unshift("<paths>");
58895
+ if (offenders.length) {
58896
+ console.error(
58897
+ `ultra11y audit: --in re-gates an audit that already exists, so these have nothing left to change: ${offenders.join(", ")}. Drop them, or run a fresh audit without --in.`
58898
+ );
58899
+ return 2;
58900
+ }
58901
+ let result;
58902
+ try {
58903
+ const parsed = JSON.parse(inPath === "-" ? await readStdin() : readText(inPath));
58904
+ if (!isCurrentAudit(parsed)) {
58905
+ console.error("ultra11y audit: --in is not a current ultra11y AuditResult (WCAG-keyed, schema v2). Re-run `audit`.");
58906
+ return 2;
58907
+ }
58908
+ result = parsed;
58909
+ } catch {
58910
+ console.error(`ultra11y audit: --in file not found or not valid JSON: ${inPath}.`);
58911
+ return 2;
58912
+ }
58913
+ const lang = resolveLang(p.flags, { audit: result });
58914
+ const failOnRaw = p.flags["fail-on"];
58915
+ const failOnParsed = parseFailOn(failOnRaw);
58916
+ if (failOnRaw !== void 0 && failOnParsed === null) {
58917
+ console.error(`ultra11y audit: --fail-on must be blocking|major|minor (got "${String(failOnRaw)}").`);
58918
+ return 2;
58919
+ }
58920
+ const ciFormat = parseCiFormat(p.flags.format);
58921
+ if (ciFormat === null) {
58922
+ console.error(`ultra11y audit: --format must be sarif|github (got "${String(p.flags.format)}").`);
58923
+ return 2;
58924
+ }
58925
+ const failOnSet = failOnRaw !== void 0;
58926
+ const failOn = failOnSet ? failOnParsed ?? "bloquant" : void 0;
58927
+ const failing = failOn ? findingsAtOrAbove(result.findings, failOn) : [];
58928
+ if (ciFormat) emitCiFormat(result, ciFormat, CORE2, lang, failOn);
58929
+ else if (p.flags.json) console.log(JSON.stringify(result, null, 2));
58930
+ else {
58931
+ console.log(auditSummary(result, lang));
58932
+ if (failOnSet && failing.length)
58933
+ console.error(lang === "fr" ? `\u2717 ${failing.length} non-conformit\xE9(s) \u2265 ${failOn}.` : `\u2717 ${failing.length} non-conformity(ies) \u2265 ${failOn}.`);
58934
+ }
58935
+ return failing.length ? 1 : 0;
58936
+ }
58872
58937
  async function cmdAudit(p) {
58938
+ const inFlag = p.flags.in;
58939
+ if (typeof inFlag === "string" && inFlag) return cmdAuditFromFile(p, inFlag);
58873
58940
  const inputs = p.positionals.length ? p.positionals : ["."];
58874
58941
  if (inputs.length === 0) {
58875
58942
  console.error("ultra11y audit: provide files/globs, or '-' to read stdin.");
package/src/cli.ts CHANGED
@@ -69,6 +69,7 @@ Usage:
69
69
  ultra11y audit [--changed | --since <ref> | --staged] [--max-files <n>] [--dedup exact|normalized|off] [--baseline <file>] [--fail-on blocking|major|minor]
70
70
  ultra11y audit [--captures <dir>] [--no-captures] [--require-captures] (rendered-DOM captures + .ultra11y/pages snapshots: audit real HTML)
71
71
  ultra11y audit [--format sarif|github] (CI: SARIF for code scanning, or inline annotations + job summary)
72
+ ultra11y audit --in <audit.json> [--fail-on blocking|major|minor] [--format sarif|github] (re-gate an audit already computed — e.g. one carrying adjudicated verdicts — without a second detection pass)
72
73
  ultra11y report --in <audit.json> [--out <dir>] [--standard <pack>] [--format sarif|github] [--lang auto|en|fr]
73
74
  ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--lang auto|en|fr]
74
75
  ultra11y tickets --in <audit.json> [--provider auto|github|gitlab|jira] [--grain criterion|page|page-criterion|single|file] [--transport auto|cli|rest]
@@ -751,7 +752,90 @@ function emitCiFormat(result: AuditResult, format: CiFormat, standard: StandardI
751
752
  }
752
753
  }
753
754
 
755
+ // `audit --in <audit.json>` — RE-GATE an audit that was already computed, instead of
756
+ // computing one. It exists for one caller: a pipeline that folded ADJUDICATED verdicts into
757
+ // audit-latest.json and now wants the same severity gate applied to that result. Re-running
758
+ // detection there would be wrong twice over — it would not see the verdicts, and it would
759
+ // spend a second full pass to reach a conclusion the file already holds.
760
+ //
761
+ // Deliberately narrow: this path GATES and RENDERS, nothing else. Every flag that would
762
+ // change what is audited is refused rather than ignored, because a gate that silently drops
763
+ // your scoping is a gate nobody can trust. Re-running detection is what `audit <globs>` is
764
+ // for.
765
+ async function cmdAuditFromFile(p: ParsedArgs, inPath: string): Promise<number> {
766
+ const scoping = [
767
+ "since",
768
+ "changed",
769
+ "staged",
770
+ "baseline",
771
+ "graph",
772
+ "cross-file",
773
+ "jsx",
774
+ "include",
775
+ "exclude",
776
+ "ext",
777
+ "max-files",
778
+ "dedup",
779
+ "captures",
780
+ "no-captures",
781
+ "require-captures",
782
+ "no-default-excludes",
783
+ "out",
784
+ ];
785
+ const offenders = scoping.filter((f) => p.flags[f] !== undefined);
786
+ if (p.positionals.length) offenders.unshift("<paths>");
787
+ if (offenders.length) {
788
+ console.error(
789
+ `ultra11y audit: --in re-gates an audit that already exists, so these have nothing left to change: ${offenders.join(", ")}. Drop them, or run a fresh audit without --in.`,
790
+ );
791
+ return 2;
792
+ }
793
+
794
+ let result: AuditResult;
795
+ try {
796
+ const parsed = JSON.parse(inPath === "-" ? await readStdin() : readText(inPath)) as unknown;
797
+ if (!isCurrentAudit(parsed)) {
798
+ console.error("ultra11y audit: --in is not a current ultra11y AuditResult (WCAG-keyed, schema v2). Re-run `audit`.");
799
+ return 2;
800
+ }
801
+ result = parsed;
802
+ } catch {
803
+ console.error(`ultra11y audit: --in file not found or not valid JSON: ${inPath}.`);
804
+ return 2;
805
+ }
806
+
807
+ const lang = resolveLang(p.flags, { audit: result });
808
+
809
+ const failOnRaw = p.flags["fail-on"];
810
+ const failOnParsed = parseFailOn(failOnRaw);
811
+ if (failOnRaw !== undefined && failOnParsed === null) {
812
+ console.error(`ultra11y audit: --fail-on must be blocking|major|minor (got "${String(failOnRaw)}").`);
813
+ return 2;
814
+ }
815
+ const ciFormat = parseCiFormat(p.flags.format);
816
+ if (ciFormat === null) {
817
+ console.error(`ultra11y audit: --format must be sarif|github (got "${String(p.flags.format)}").`);
818
+ return 2;
819
+ }
820
+
821
+ const failOnSet = failOnRaw !== undefined;
822
+ const failOn = failOnSet ? (failOnParsed ?? "bloquant") : undefined;
823
+ const failing = failOn ? findingsAtOrAbove(result.findings, failOn) : [];
824
+
825
+ if (ciFormat) emitCiFormat(result, ciFormat, CORE, lang, failOn);
826
+ else if (p.flags.json) console.log(JSON.stringify(result, null, 2));
827
+ else {
828
+ console.log(auditSummary(result, lang));
829
+ if (failOnSet && failing.length)
830
+ console.error(lang === "fr" ? `✗ ${failing.length} non-conformité(s) ≥ ${failOn}.` : `✗ ${failing.length} non-conformity(ies) ≥ ${failOn}.`);
831
+ }
832
+ return failing.length ? 1 : 0;
833
+ }
834
+
754
835
  async function cmdAudit(p: ParsedArgs): Promise<number> {
836
+ const inFlag = p.flags.in;
837
+ if (typeof inFlag === "string" && inFlag) return cmdAuditFromFile(p, inFlag);
838
+
755
839
  const inputs = p.positionals.length ? p.positionals : ["."];
756
840
  if (inputs.length === 0) {
757
841
  console.error("ultra11y audit: provide files/globs, or '-' to read stdin.");
package/src/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Single source of truth for shared types + the embedded version. sync-version.mjs
2
2
  // rewrites VERSION at release time (kept in lockstep with package.json + SKILL.md).
3
- export const VERSION = "2.32.1";
3
+ export const VERSION = "3.0.0";
4
4
  export const SCHEMA_VERSION = 2;
5
5
 
6
6
  // Lang = the UI FRAME's language — the `L` localization tables each module (report,