ultra11y 5.44.0 → 5.45.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 before commit, push and pull request, plus detailed evidenced Markdown, HTML and per-page WCAG 2.2 AA / RGAA reports in Claude Code.",
13
- "version": "5.44.0",
13
+ "version": "5.45.0",
14
14
  "author": { "name": "maxgfr" },
15
15
  "homepage": "https://github.com/SocialGouv/ultra11y",
16
16
  "repository": "https://github.com/SocialGouv/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): automatic review before commit, push and pull request, plus detailed evidenced Markdown, HTML and per-page conformance reports in interactive agent sessions.",
4
- "version": "5.44.0",
4
+ "version": "5.45.0",
5
5
  "author": { "name": "maxgfr" },
6
6
  "homepage": "https://github.com/SocialGouv/ultra11y",
7
7
  "repository": "https://github.com/SocialGouv/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): automatic review before commit, push and pull request, plus detailed evidenced Markdown, HTML and per-page conformance reports in interactive agent sessions.",
4
- "version": "5.44.0",
4
+ "version": "5.45.0",
5
5
  "author": { "name": "maxgfr", "url": "https://github.com/maxgfr" },
6
6
  "homepage": "https://github.com/SocialGouv/ultra11y",
7
7
  "repository": "https://github.com/SocialGouv/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 = "5.44.0";
30
+ const ULTRA11Y_PLUGIN_VERSION = "5.45.0";
31
31
 
32
32
  const here = dirname(fileURLToPath(import.meta.url));
33
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultra11y",
3
- "version": "5.44.0",
3
+ "version": "5.45.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 join52, relative as relative6, resolve as resolve17, sep as sep
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "5.44.0";
9
+ var VERSION = "5.45.0";
10
10
  var SCHEMA_VERSION = 2;
11
11
  var INAPPLICABLE_STATUS = "C";
12
12
 
@@ -66879,6 +66879,13 @@ function resolveHostAnchor(file, snippet2) {
66879
66879
  }
66880
66880
  return null;
66881
66881
  }
66882
+ function scanPageSummary(dynamic, sampleSize) {
66883
+ return {
66884
+ ...sampleSize === void 0 ? {} : { sample: sampleSize },
66885
+ recorded: dynamic.snapshots ?? [],
66886
+ refused: (dynamic.redirected ?? []).map((r) => ({ id: r.id, reason: r.reason }))
66887
+ };
66888
+ }
66882
66889
  function mergeDynamic(audit2, dynamic, lang = "en") {
66883
66890
  const merged = JSON.parse(JSON.stringify(audit2));
66884
66891
  const seenFindings = new Set(merged.findings.map(portableFindingId));
@@ -74493,7 +74500,9 @@ Options:
74493
74500
  (exit 2 with the producing command if its worklist is missing)
74494
74501
  --eco orchestrate: emit only RUNBOOK.md + agents/*.md \u2014 the explicit
74495
74502
  sequential low-token path (also what a no-subagent harness follows)
74496
- --merge <file> scan: fold dynamic findings into this AuditResult JSON
74503
+ --merge <file> scan: fold dynamic findings into this AuditResult JSON. With --json the
74504
+ printed document opens with a 'pages' summary of the run \u2014
74505
+ {sample, recorded, refused:[{id, reason}]} \u2014 beside the document
74497
74506
  --sitemap <url> scan: scan every URL listed in a sitemap.xml
74498
74507
  --crawl <url> scan: BFS same-origin links from a start URL (served HTML)
74499
74508
  --depth <n> scan: crawl link-hop depth from the start URL (default: no limit; 0 = no limit)
@@ -77216,7 +77225,19 @@ async function cmdScan(p) {
77216
77225
  mkdirSync18(out2, { recursive: true });
77217
77226
  const mergedDocument = auditDocumentFor(merged, standard, lang);
77218
77227
  writeFileSync21(join52(out2, "audit-latest.json"), JSON.stringify(mergedDocument, null, 2) + "\n");
77219
- if (p.flags.json) console.log(JSON.stringify(mergedDocument, null, 2));
77228
+ if (p.flags.json)
77229
+ console.log(
77230
+ JSON.stringify(
77231
+ // `pages` opens the printed document: what this scan recorded (the
77232
+ // .ultra11y/pages ids) and refused, which the stderr warnings alone don't make
77233
+ // countable. An extra top-level key the `--in` readers never look at, so the
77234
+ // document beside it stays pipeable into report/verify — and the file above
77235
+ // keeps the plain audit schema.
77236
+ { pages: scanPageSummary(dynamic, sampleConfig?.pages.length), ...mergedDocument },
77237
+ null,
77238
+ 2
77239
+ )
77240
+ );
77220
77241
  else {
77221
77242
  console.log(
77222
77243
  lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join52(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join52(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
@@ -3,7 +3,7 @@ name: review-a11y
3
3
  description: "Use to REVIEW changed frontend code for accessibility: staged files, a working diff, branch, or PR; prompts such as 'review a11y', 'is this accessible?', or 'anything to fix before merge?'; and hook or CI findings on a change. Run the bundled install-free engine with cross-file JSX/TSX analysis, then adjudicate candidates and return a severity-ranked WCAG 2.2 AA review with file:line fixes, residual rendering risks, and a verdict. Use `ultra11y` instead for whole-repo audits, dated conformance reports, country standards, PRD backlogs, or authoring accessible markup."
4
4
  license: MIT
5
5
  metadata:
6
- version: 5.44.0
6
+ version: 5.45.0
7
7
  ---
8
8
 
9
9
  # review-a11y — accessibility review of changed code
@@ -6,7 +6,7 @@ import { join as join52, relative as relative6, resolve as resolve17, sep as sep
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "5.44.0";
9
+ var VERSION = "5.45.0";
10
10
  var SCHEMA_VERSION = 2;
11
11
  var INAPPLICABLE_STATUS = "C";
12
12
 
@@ -66879,6 +66879,13 @@ function resolveHostAnchor(file, snippet2) {
66879
66879
  }
66880
66880
  return null;
66881
66881
  }
66882
+ function scanPageSummary(dynamic, sampleSize) {
66883
+ return {
66884
+ ...sampleSize === void 0 ? {} : { sample: sampleSize },
66885
+ recorded: dynamic.snapshots ?? [],
66886
+ refused: (dynamic.redirected ?? []).map((r) => ({ id: r.id, reason: r.reason }))
66887
+ };
66888
+ }
66882
66889
  function mergeDynamic(audit2, dynamic, lang = "en") {
66883
66890
  const merged = JSON.parse(JSON.stringify(audit2));
66884
66891
  const seenFindings = new Set(merged.findings.map(portableFindingId));
@@ -74493,7 +74500,9 @@ Options:
74493
74500
  (exit 2 with the producing command if its worklist is missing)
74494
74501
  --eco orchestrate: emit only RUNBOOK.md + agents/*.md \u2014 the explicit
74495
74502
  sequential low-token path (also what a no-subagent harness follows)
74496
- --merge <file> scan: fold dynamic findings into this AuditResult JSON
74503
+ --merge <file> scan: fold dynamic findings into this AuditResult JSON. With --json the
74504
+ printed document opens with a 'pages' summary of the run \u2014
74505
+ {sample, recorded, refused:[{id, reason}]} \u2014 beside the document
74497
74506
  --sitemap <url> scan: scan every URL listed in a sitemap.xml
74498
74507
  --crawl <url> scan: BFS same-origin links from a start URL (served HTML)
74499
74508
  --depth <n> scan: crawl link-hop depth from the start URL (default: no limit; 0 = no limit)
@@ -77216,7 +77225,19 @@ async function cmdScan(p) {
77216
77225
  mkdirSync18(out2, { recursive: true });
77217
77226
  const mergedDocument = auditDocumentFor(merged, standard, lang);
77218
77227
  writeFileSync21(join52(out2, "audit-latest.json"), JSON.stringify(mergedDocument, null, 2) + "\n");
77219
- if (p.flags.json) console.log(JSON.stringify(mergedDocument, null, 2));
77228
+ if (p.flags.json)
77229
+ console.log(
77230
+ JSON.stringify(
77231
+ // `pages` opens the printed document: what this scan recorded (the
77232
+ // .ultra11y/pages ids) and refused, which the stderr warnings alone don't make
77233
+ // countable. An extra top-level key the `--in` readers never look at, so the
77234
+ // document beside it stays pipeable into report/verify — and the file above
77235
+ // keeps the plain audit schema.
77236
+ { pages: scanPageSummary(dynamic, sampleConfig?.pages.length), ...mergedDocument },
77237
+ null,
77238
+ 2
77239
+ )
77240
+ );
77220
77241
  else {
77221
77242
  console.log(
77222
77243
  lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join52(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join52(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
@@ -3,7 +3,7 @@ name: ultra11y
3
3
  description: "Use to AUDIT a repository, site, or page against WCAG 2.2 AA or a country standard such as RGAA; produce a dated conformance report, criterion grid, PRD backlog, or tickets; AUTHOR accessible markup; fix accessibility; or wire a repo gate and rendered-DOM capture pipeline. The bundled install-free engine runs 93 static checks tied to success criteria, while the agent adjudicates judgment criteria and routes rendering criteria to browser scans; check/verify reject invented non-conformities. Use `review-a11y` instead for a review scoped only to a diff, branch, or PR."
4
4
  license: MIT
5
5
  metadata:
6
- version: 5.44.0
6
+ version: 5.45.0
7
7
  ---
8
8
 
9
9
  # ultra11y — audit WCAG 2.2 AA and write accessible markup
@@ -208,6 +208,12 @@ Read exit codes from the process, **not** from a pipeline — `cmd --json | head
208
208
  standard's own conformance rate (validated ÷ validated + NC + à évaluer, NA excluded) is
209
209
  printed by the report; derive it from `criteria[]` if a number is needed in a pipeline.
210
210
  - `check --json` → `{ ok, issues[] }`.
211
+ - `scan --merge --json` → the merged audit document, opened by a top-level `pages` summary:
212
+ `{ sample, recorded[], refused: [{id, reason}] }` — the declared sample's size (`--sample`),
213
+ the snapshot ids written under `.ultra11y/pages/`, and the pages the scan refused with
214
+ their reason (the refusal warnings also go to stderr). Report the rendered layer from it —
215
+ captured vs refused — instead of re-deriving it from directory mtimes. The document written
216
+ to `--out` keeps the plain audit schema; the summary travels in stdout only.
211
217
 
212
218
  ## Honesty rules an orchestrator has to carry
213
219
 
@@ -6,7 +6,7 @@ import { join as join52, relative as relative6, resolve as resolve17, sep as sep
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "5.44.0";
9
+ var VERSION = "5.45.0";
10
10
  var SCHEMA_VERSION = 2;
11
11
  var INAPPLICABLE_STATUS = "C";
12
12
 
@@ -66879,6 +66879,13 @@ function resolveHostAnchor(file, snippet2) {
66879
66879
  }
66880
66880
  return null;
66881
66881
  }
66882
+ function scanPageSummary(dynamic, sampleSize) {
66883
+ return {
66884
+ ...sampleSize === void 0 ? {} : { sample: sampleSize },
66885
+ recorded: dynamic.snapshots ?? [],
66886
+ refused: (dynamic.redirected ?? []).map((r) => ({ id: r.id, reason: r.reason }))
66887
+ };
66888
+ }
66882
66889
  function mergeDynamic(audit2, dynamic, lang = "en") {
66883
66890
  const merged = JSON.parse(JSON.stringify(audit2));
66884
66891
  const seenFindings = new Set(merged.findings.map(portableFindingId));
@@ -74493,7 +74500,9 @@ Options:
74493
74500
  (exit 2 with the producing command if its worklist is missing)
74494
74501
  --eco orchestrate: emit only RUNBOOK.md + agents/*.md \u2014 the explicit
74495
74502
  sequential low-token path (also what a no-subagent harness follows)
74496
- --merge <file> scan: fold dynamic findings into this AuditResult JSON
74503
+ --merge <file> scan: fold dynamic findings into this AuditResult JSON. With --json the
74504
+ printed document opens with a 'pages' summary of the run \u2014
74505
+ {sample, recorded, refused:[{id, reason}]} \u2014 beside the document
74497
74506
  --sitemap <url> scan: scan every URL listed in a sitemap.xml
74498
74507
  --crawl <url> scan: BFS same-origin links from a start URL (served HTML)
74499
74508
  --depth <n> scan: crawl link-hop depth from the start URL (default: no limit; 0 = no limit)
@@ -77216,7 +77225,19 @@ async function cmdScan(p) {
77216
77225
  mkdirSync18(out2, { recursive: true });
77217
77226
  const mergedDocument = auditDocumentFor(merged, standard, lang);
77218
77227
  writeFileSync21(join52(out2, "audit-latest.json"), JSON.stringify(mergedDocument, null, 2) + "\n");
77219
- if (p.flags.json) console.log(JSON.stringify(mergedDocument, null, 2));
77228
+ if (p.flags.json)
77229
+ console.log(
77230
+ JSON.stringify(
77231
+ // `pages` opens the printed document: what this scan recorded (the
77232
+ // .ultra11y/pages ids) and refused, which the stderr warnings alone don't make
77233
+ // countable. An extra top-level key the `--in` readers never look at, so the
77234
+ // document beside it stays pipeable into report/verify — and the file above
77235
+ // keeps the plain audit schema.
77236
+ { pages: scanPageSummary(dynamic, sampleConfig?.pages.length), ...mergedDocument },
77237
+ null,
77238
+ 2
77239
+ )
77240
+ );
77220
77241
  else {
77221
77242
  console.log(
77222
77243
  lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join52(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join52(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
@@ -27,7 +27,7 @@ variables:
27
27
  ULTRA11Y_LANG: en
28
28
  # Exact by default: a CI report must say which engine produced it. The release script keeps
29
29
  # this pin aligned with the package version; override only for a deliberate upgrade test.
30
- ULTRA11Y_VERSION: '5.44.0'
30
+ ULTRA11Y_VERSION: '5.45.0'
31
31
  # Batches of 8 amortise the prompt; each accepted verdict is checkpointed before the next
32
32
  # batch, and later passes receive only the residue.
33
33
  ULTRA11Y_GRAIN: batch
package/src/cli.ts CHANGED
@@ -82,7 +82,7 @@ import {
82
82
  import { DEFAULT_CLI_MODEL, EFFORT_LEVELS, judgeBatchCli, refuteBatchCli } from "./agent-cli.js";
83
83
  import { CODEX_EFFORT_LEVELS, judgeBatchCodex, refuteBatchCodex } from "./agent-codex.js";
84
84
  import { BATCH_SIZE, apiKeyFromEnv, applyRawVerdicts, batchWorklist, isProviderUnavailableError, judgeAll, modelFromEnv, type LlmOptions } from "./llm.js";
85
- import { runScan, runScanMany, runCrawlScan, runSampleScan, mergeDynamic, mergeSnapshotAudit, cleanDynamic, dockerAvailable } from "./scan.js";
85
+ import { runScan, runScanMany, runCrawlScan, runSampleScan, mergeDynamic, mergeSnapshotAudit, cleanDynamic, dockerAvailable, scanPageSummary } from "./scan.js";
86
86
  import { runScanLocal, runScanManyLocal, runCrawlScanLocal, runSampleScanLocal, localAvailable, localTierStatus } from "./scan-local.js";
87
87
  import { validateSample, lintSample, kindLabel, proposeSamplePages, mergeSample, sampleFromSnapshots, unionSample } from "./sample.js";
88
88
  import { createAdapter } from "./external/registry.js";
@@ -493,7 +493,9 @@ Options:
493
493
  (exit 2 with the producing command if its worklist is missing)
494
494
  --eco orchestrate: emit only RUNBOOK.md + agents/*.md — the explicit
495
495
  sequential low-token path (also what a no-subagent harness follows)
496
- --merge <file> scan: fold dynamic findings into this AuditResult JSON
496
+ --merge <file> scan: fold dynamic findings into this AuditResult JSON. With --json the
497
+ printed document opens with a 'pages' summary of the run —
498
+ {sample, recorded, refused:[{id, reason}]} — beside the document
497
499
  --sitemap <url> scan: scan every URL listed in a sitemap.xml
498
500
  --crawl <url> scan: BFS same-origin links from a start URL (served HTML)
499
501
  --depth <n> scan: crawl link-hop depth from the start URL (default: no limit; 0 = no limit)
@@ -4347,7 +4349,19 @@ async function cmdScan(p: ParsedArgs): Promise<number> {
4347
4349
  mkdirSync(out, { recursive: true });
4348
4350
  const mergedDocument = auditDocumentFor(merged, standard, lang);
4349
4351
  writeFileSync(join(out, "audit-latest.json"), JSON.stringify(mergedDocument, null, 2) + "\n");
4350
- if (p.flags.json) console.log(JSON.stringify(mergedDocument, null, 2));
4352
+ if (p.flags.json)
4353
+ console.log(
4354
+ JSON.stringify(
4355
+ // `pages` opens the printed document: what this scan recorded (the
4356
+ // .ultra11y/pages ids) and refused, which the stderr warnings alone don't make
4357
+ // countable. An extra top-level key the `--in` readers never look at, so the
4358
+ // document beside it stays pipeable into report/verify — and the file above
4359
+ // keeps the plain audit schema.
4360
+ { pages: scanPageSummary(dynamic, sampleConfig?.pages.length), ...mergedDocument },
4361
+ null,
4362
+ 2,
4363
+ ),
4364
+ );
4351
4365
  else {
4352
4366
  console.log(
4353
4367
  lang === "fr"
package/src/scan.ts CHANGED
@@ -10,7 +10,7 @@ import { mkdtempSync, writeFileSync, existsSync, statSync, readdirSync, rmSync,
10
10
  import { tmpdir } from "node:os";
11
11
  import { join, resolve } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
- import type { AuditResult, DynamicEngine, DynamicFinding, DynamicResult, Finding, Lang, SamplePage, Severity } from "./types.js";
13
+ import type { AuditResult, DynamicEngine, DynamicFinding, DynamicResult, Finding, Lang, SamplePage, ScanRedirect, Severity } from "./types.js";
14
14
  import { lineStartsOf, lineColAt } from "./parse/html.js";
15
15
  import { allGuidelines } from "./wcag.js";
16
16
  import { PROBE_SEVERITY, PROBE_WCAG, scForAxe, severityFromImpact, isAxeAdvisory } from "./axe-map.js";
@@ -716,6 +716,31 @@ function resolveHostAnchor(file: string, snippet: string): { line: number; col:
716
716
  return null;
717
717
  }
718
718
 
719
+ /** The `pages` block `scan --merge --json` prints in front of the merged document. A
720
+ * PRINT-side envelope only — `audit-latest.json` keeps the plain audit schema — under a
721
+ * top-level key no `--in` reader of the document looks at. */
722
+ export interface ScanPageSummary {
723
+ // The declared sample's page count, on a `--sample` run; absent otherwise (a plain
724
+ // URL/sitemap/crawl scan has no sample to be short of).
725
+ sample?: number;
726
+ // Ids of the snapshots the run persisted under `.ultra11y/pages/<id>/`.
727
+ recorded: string[];
728
+ // Pages the scan refused to record, and why (see ScanRedirect).
729
+ refused: Array<{ id: string; reason?: ScanRedirect["reason"] }>;
730
+ }
731
+
732
+ /** What one scan RECORDED and REFUSED, page by page. Without it the merged `--json` output
733
+ * carries neither: the refusals reach the caller on stderr only, and the recorded ids would
734
+ * take a listing of `.ultra11y/pages` against the run's start time to recover — exactly the
735
+ * re-derivation an orchestrator reporting « N pages capturées, M refusées » must not do. */
736
+ export function scanPageSummary(dynamic: DynamicResult, sampleSize?: number): ScanPageSummary {
737
+ return {
738
+ ...(sampleSize === undefined ? {} : { sample: sampleSize }),
739
+ recorded: dynamic.snapshots ?? [],
740
+ refused: (dynamic.redirected ?? []).map((r) => ({ id: r.id, reason: r.reason })),
741
+ };
742
+ }
743
+
719
744
  /** Fold dynamic findings into a static AuditResult: a needs-rendering/manual or
720
745
  * clean criterion that axe flags becomes NC; tallies + conformance recompute. */
721
746
  export function mergeDynamic(audit: AuditResult, dynamic: DynamicResult, lang: Lang = "en"): AuditResult {
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 = "5.44.0";
3
+ export const VERSION = "5.45.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,