ultra11y 5.25.0 → 5.26.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": "5.25.0",
13
+ "version": "5.26.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": "5.25.0",
4
+ "version": "5.26.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": "5.25.0",
4
+ "version": "5.26.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 = "5.25.0";
30
+ const ULTRA11Y_PLUGIN_VERSION = "5.26.0";
31
31
 
32
32
  const here = dirname(fileURLToPath(import.meta.url));
33
33
 
@@ -720,7 +720,7 @@ var test = (() => {
720
720
  const base = load(from).test;
721
721
  if (!base?.extend) continue;
722
722
  return base.extend({
723
- // biome-ignore lint/suspicious/noExplicitAny: same
723
+ // `any` for the same reason.
724
724
  ultra11y: async ({ page }, use) => {
725
725
  await use((opts) => checkA11y(page, opts));
726
726
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultra11y",
3
- "version": "5.25.0",
3
+ "version": "5.26.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 join51, relative as relative5, resolve as resolve16, 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.25.0";
9
+ var VERSION = "5.26.0";
10
10
  var SCHEMA_VERSION = 2;
11
11
  var INAPPLICABLE_STATUS = "C";
12
12
 
@@ -36908,7 +36908,7 @@ function makeFilter(opts = {}) {
36908
36908
  const rel2 = toPosix(file);
36909
36909
  if (include && !include(rel2)) return false;
36910
36910
  if (exclude?.(rel2)) return false;
36911
- if (!opts.noDefaultExcludes && isTestArtifact(rel2) && !(include && include(rel2))) return false;
36911
+ if (!opts.noDefaultExcludes && isTestArtifact(rel2) && !include?.(rel2)) return false;
36912
36912
  return true;
36913
36913
  };
36914
36914
  }
@@ -36974,7 +36974,7 @@ function expandInputs(inputs, opts = {}) {
36974
36974
  const before = list.length;
36975
36975
  list = list.filter((f) => {
36976
36976
  const rel2 = toPosix(f);
36977
- return !isTestArtifact(rel2) || explicit.has(f) || include != null && include(rel2);
36977
+ return !isTestArtifact(rel2) || explicit.has(f) || include?.(rel2);
36978
36978
  });
36979
36979
  const dropped = before - list.length;
36980
36980
  if (dropped) opts.onWarn?.(`ultra11y: skipped ${dropped} test/spec/story file(s) \u2014 pass --no-default-excludes to audit them.`);
@@ -45436,7 +45436,8 @@ var EXISTENCE_SUBJECTS = /* @__PURE__ */ new Set([
45436
45436
  "autocomplete",
45437
45437
  "errors",
45438
45438
  "frames",
45439
- "downloadDocs"
45439
+ "downloadDocs",
45440
+ "newWindow"
45440
45441
  ]);
45441
45442
  var subjectsForSc = (sc) => SC_SUBJECTS[sc] ?? [];
45442
45443
  function subjectsForPackCriterion(standard, id, scs) {
@@ -46968,7 +46969,7 @@ var tableEmptyDataCell = {
46968
46969
  return out2;
46969
46970
  }
46970
46971
  };
46971
- function ownerTable(el, doc) {
46972
+ function ownerTable(el) {
46972
46973
  return ancestors(el).find((a) => a.tag === "table");
46973
46974
  }
46974
46975
  var headersAttrDangling = {
@@ -46981,7 +46982,7 @@ var headersAttrDangling = {
46981
46982
  if (el.tag !== "td" && el.tag !== "th") continue;
46982
46983
  const raw = (attr(el, "headers") ?? "").trim();
46983
46984
  if (!raw || raw.includes("{")) continue;
46984
- const table = ownerTable(el, doc);
46985
+ const table = ownerTable(el);
46985
46986
  if (!table) continue;
46986
46987
  const inTable = new Map(
46987
46988
  descendants(table).filter((d) => d.tag === "th" || d.tag === "td").map((d) => [attr(d, "id") ?? "", d])
@@ -48784,7 +48785,7 @@ function matchNode(el, node, depth) {
48784
48785
  if (node.has || node.lacks) {
48785
48786
  const desc = descendants(el);
48786
48787
  if (node.has && !node.has.every((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
48787
- if (node.lacks && node.lacks.some((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
48788
+ if (node.lacks?.some((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
48788
48789
  }
48789
48790
  return true;
48790
48791
  }
@@ -49000,7 +49001,7 @@ function extractGraphNode(ast, doc, file, opts = {}) {
49000
49001
  if (ast) {
49001
49002
  for (const stmt of asNodes((asNode(ast.program) ?? ast).body)) {
49002
49003
  const vd = stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" ? asNode(stmt.declaration) : void 0;
49003
- if (!vd || vd.type !== "VariableDeclaration") continue;
49004
+ if (vd?.type !== "VariableDeclaration") continue;
49004
49005
  for (const d of asNodes(vd.declarations)) {
49005
49006
  const idn = asNode(d.id);
49006
49007
  const init2 = asNode(d.init);
@@ -49353,7 +49354,7 @@ function hasMedia(d) {
49353
49354
  return d.elements.some((el) => {
49354
49355
  if (el.tag === "object" || el.tag === "embed") {
49355
49356
  const type = (el.attribs.type ?? "").toLowerCase();
49356
- return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/") ? true : false;
49357
+ return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/");
49357
49358
  }
49358
49359
  if (el.tag !== "iframe") return false;
49359
49360
  const hay = `${el.attribs.src ?? ""} ${el.attribs.title ?? ""} ${el.attribs.allow ?? ""}`;
@@ -59219,7 +59220,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
59219
59220
  if (v === null) {
59220
59221
  blame(it.criteriaId, `criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
59221
59222
  } else if (v === "C" || v === "NA") {
59222
- if (!it.justification || !it.justification.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
59223
+ if (!it.justification?.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
59223
59224
  const cites = (it.citations ?? []).map(readCitation).filter((c2) => c2 !== null);
59224
59225
  if (v === "C" && it.evidenceComplete === false) {
59225
59226
  blame(
@@ -59272,7 +59273,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
59272
59273
  );
59273
59274
  continue;
59274
59275
  }
59275
- if (!f.normativeRef || !f.normativeRef.trim()) {
59276
+ if (!f.normativeRef?.trim()) {
59276
59277
  blame(it.criteriaId, `criterion ${it.criteriaId}: an NC finding requires a normativeRef citing the failed test of the active standard`);
59277
59278
  } else if (!normativeRefResolves(f.normativeRef, adj.standard, isCore(adj.standard) ? void 0 : it.criteriaId)) {
59278
59279
  blame(
@@ -60873,7 +60874,7 @@ function mergeSnapshotAudit(base, snap) {
60873
60874
  for (const c2 of merged.criteria) {
60874
60875
  if (!c2.inapplicable) continue;
60875
60876
  const measured = snapById.get(c2.id);
60876
- if (!measured || measured.status !== "C" || measured.inapplicable) continue;
60877
+ if (measured?.status !== "C" || measured.inapplicable) continue;
60877
60878
  c2.status = "C";
60878
60879
  delete c2.inapplicable;
60879
60880
  if (measured.justification) c2.justification = measured.justification;
@@ -64916,7 +64917,7 @@ function foldRecordedAdjudication(root, fresh) {
64916
64917
  for (const c2 of prior.criteria) {
64917
64918
  if (c2.decidedBy !== "agent") continue;
64918
64919
  const target = byId2.get(c2.id);
64919
- if (!target || target.status !== "manual") continue;
64920
+ if (target?.status !== "manual") continue;
64920
64921
  target.status = c2.status;
64921
64922
  target.decidedBy = "agent";
64922
64923
  if (c2.justification) target.justification = c2.justification;
@@ -65541,7 +65542,7 @@ For EACH entry:
65541
65542
  Then fold: \`ultra11y verify --apply ${join47(runAbs, "VERIFY.todo.json")} --report <the report .md>\`. Without a shell, leave the fold to whoever gave you the file.
65542
65543
  `;
65543
65544
  }
65544
- function agentContracts(runAbs, engineAbs, opts = {}) {
65545
+ function agentContracts(runAbs, opts = {}) {
65545
65546
  const footer = ONE_WRITER_FOOTER.replaceAll("<RUN>", runAbs);
65546
65547
  if (opts.eco) return { adjudicator: ecoAdjudicatorContract(runAbs), refuter: ecoRefuterContract(runAbs) };
65547
65548
  return {
@@ -65724,7 +65725,7 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
65724
65725
  mkdirSync17(agentsDir, { recursive: true });
65725
65726
  const written = [];
65726
65727
  const notices = [];
65727
- for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs, { eco: opts.eco === true }))) {
65728
+ for (const [name2, content] of Object.entries(agentContracts(run2, { eco: opts.eco === true }))) {
65728
65729
  const p = join48(agentsDir, `${name2}.md`);
65729
65730
  writeFileSync19(p, content);
65730
65731
  written.push(p);
@@ -66230,7 +66231,7 @@ function bool(v) {
66230
66231
  }
66231
66232
  function strArray2(v) {
66232
66233
  const a = Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
66233
- return a && a.length ? a : void 0;
66234
+ return a?.length ? a : void 0;
66234
66235
  }
66235
66236
  function positive(v, key) {
66236
66237
  const n = num2(v);
@@ -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: 5.25.0
8
+ version: 5.26.0
9
9
  ---
10
10
 
11
11
  # review-a11y — accessibility review of changed code
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, 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.25.0";
9
+ var VERSION = "5.26.0";
10
10
  var SCHEMA_VERSION = 2;
11
11
  var INAPPLICABLE_STATUS = "C";
12
12
 
@@ -36908,7 +36908,7 @@ function makeFilter(opts = {}) {
36908
36908
  const rel2 = toPosix(file);
36909
36909
  if (include && !include(rel2)) return false;
36910
36910
  if (exclude?.(rel2)) return false;
36911
- if (!opts.noDefaultExcludes && isTestArtifact(rel2) && !(include && include(rel2))) return false;
36911
+ if (!opts.noDefaultExcludes && isTestArtifact(rel2) && !include?.(rel2)) return false;
36912
36912
  return true;
36913
36913
  };
36914
36914
  }
@@ -36974,7 +36974,7 @@ function expandInputs(inputs, opts = {}) {
36974
36974
  const before = list.length;
36975
36975
  list = list.filter((f) => {
36976
36976
  const rel2 = toPosix(f);
36977
- return !isTestArtifact(rel2) || explicit.has(f) || include != null && include(rel2);
36977
+ return !isTestArtifact(rel2) || explicit.has(f) || include?.(rel2);
36978
36978
  });
36979
36979
  const dropped = before - list.length;
36980
36980
  if (dropped) opts.onWarn?.(`ultra11y: skipped ${dropped} test/spec/story file(s) \u2014 pass --no-default-excludes to audit them.`);
@@ -45436,7 +45436,8 @@ var EXISTENCE_SUBJECTS = /* @__PURE__ */ new Set([
45436
45436
  "autocomplete",
45437
45437
  "errors",
45438
45438
  "frames",
45439
- "downloadDocs"
45439
+ "downloadDocs",
45440
+ "newWindow"
45440
45441
  ]);
45441
45442
  var subjectsForSc = (sc) => SC_SUBJECTS[sc] ?? [];
45442
45443
  function subjectsForPackCriterion(standard, id, scs) {
@@ -46968,7 +46969,7 @@ var tableEmptyDataCell = {
46968
46969
  return out2;
46969
46970
  }
46970
46971
  };
46971
- function ownerTable(el, doc) {
46972
+ function ownerTable(el) {
46972
46973
  return ancestors(el).find((a) => a.tag === "table");
46973
46974
  }
46974
46975
  var headersAttrDangling = {
@@ -46981,7 +46982,7 @@ var headersAttrDangling = {
46981
46982
  if (el.tag !== "td" && el.tag !== "th") continue;
46982
46983
  const raw = (attr(el, "headers") ?? "").trim();
46983
46984
  if (!raw || raw.includes("{")) continue;
46984
- const table = ownerTable(el, doc);
46985
+ const table = ownerTable(el);
46985
46986
  if (!table) continue;
46986
46987
  const inTable = new Map(
46987
46988
  descendants(table).filter((d) => d.tag === "th" || d.tag === "td").map((d) => [attr(d, "id") ?? "", d])
@@ -48784,7 +48785,7 @@ function matchNode(el, node, depth) {
48784
48785
  if (node.has || node.lacks) {
48785
48786
  const desc = descendants(el);
48786
48787
  if (node.has && !node.has.every((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
48787
- if (node.lacks && node.lacks.some((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
48788
+ if (node.lacks?.some((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
48788
48789
  }
48789
48790
  return true;
48790
48791
  }
@@ -49000,7 +49001,7 @@ function extractGraphNode(ast, doc, file, opts = {}) {
49000
49001
  if (ast) {
49001
49002
  for (const stmt of asNodes((asNode(ast.program) ?? ast).body)) {
49002
49003
  const vd = stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" ? asNode(stmt.declaration) : void 0;
49003
- if (!vd || vd.type !== "VariableDeclaration") continue;
49004
+ if (vd?.type !== "VariableDeclaration") continue;
49004
49005
  for (const d of asNodes(vd.declarations)) {
49005
49006
  const idn = asNode(d.id);
49006
49007
  const init2 = asNode(d.init);
@@ -49353,7 +49354,7 @@ function hasMedia(d) {
49353
49354
  return d.elements.some((el) => {
49354
49355
  if (el.tag === "object" || el.tag === "embed") {
49355
49356
  const type = (el.attribs.type ?? "").toLowerCase();
49356
- return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/") ? true : false;
49357
+ return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/");
49357
49358
  }
49358
49359
  if (el.tag !== "iframe") return false;
49359
49360
  const hay = `${el.attribs.src ?? ""} ${el.attribs.title ?? ""} ${el.attribs.allow ?? ""}`;
@@ -59219,7 +59220,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
59219
59220
  if (v === null) {
59220
59221
  blame(it.criteriaId, `criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
59221
59222
  } else if (v === "C" || v === "NA") {
59222
- if (!it.justification || !it.justification.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
59223
+ if (!it.justification?.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
59223
59224
  const cites = (it.citations ?? []).map(readCitation).filter((c2) => c2 !== null);
59224
59225
  if (v === "C" && it.evidenceComplete === false) {
59225
59226
  blame(
@@ -59272,7 +59273,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
59272
59273
  );
59273
59274
  continue;
59274
59275
  }
59275
- if (!f.normativeRef || !f.normativeRef.trim()) {
59276
+ if (!f.normativeRef?.trim()) {
59276
59277
  blame(it.criteriaId, `criterion ${it.criteriaId}: an NC finding requires a normativeRef citing the failed test of the active standard`);
59277
59278
  } else if (!normativeRefResolves(f.normativeRef, adj.standard, isCore(adj.standard) ? void 0 : it.criteriaId)) {
59278
59279
  blame(
@@ -60873,7 +60874,7 @@ function mergeSnapshotAudit(base, snap) {
60873
60874
  for (const c2 of merged.criteria) {
60874
60875
  if (!c2.inapplicable) continue;
60875
60876
  const measured = snapById.get(c2.id);
60876
- if (!measured || measured.status !== "C" || measured.inapplicable) continue;
60877
+ if (measured?.status !== "C" || measured.inapplicable) continue;
60877
60878
  c2.status = "C";
60878
60879
  delete c2.inapplicable;
60879
60880
  if (measured.justification) c2.justification = measured.justification;
@@ -64916,7 +64917,7 @@ function foldRecordedAdjudication(root, fresh) {
64916
64917
  for (const c2 of prior.criteria) {
64917
64918
  if (c2.decidedBy !== "agent") continue;
64918
64919
  const target = byId2.get(c2.id);
64919
- if (!target || target.status !== "manual") continue;
64920
+ if (target?.status !== "manual") continue;
64920
64921
  target.status = c2.status;
64921
64922
  target.decidedBy = "agent";
64922
64923
  if (c2.justification) target.justification = c2.justification;
@@ -65541,7 +65542,7 @@ For EACH entry:
65541
65542
  Then fold: \`ultra11y verify --apply ${join47(runAbs, "VERIFY.todo.json")} --report <the report .md>\`. Without a shell, leave the fold to whoever gave you the file.
65542
65543
  `;
65543
65544
  }
65544
- function agentContracts(runAbs, engineAbs, opts = {}) {
65545
+ function agentContracts(runAbs, opts = {}) {
65545
65546
  const footer = ONE_WRITER_FOOTER.replaceAll("<RUN>", runAbs);
65546
65547
  if (opts.eco) return { adjudicator: ecoAdjudicatorContract(runAbs), refuter: ecoRefuterContract(runAbs) };
65547
65548
  return {
@@ -65724,7 +65725,7 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
65724
65725
  mkdirSync17(agentsDir, { recursive: true });
65725
65726
  const written = [];
65726
65727
  const notices = [];
65727
- for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs, { eco: opts.eco === true }))) {
65728
+ for (const [name2, content] of Object.entries(agentContracts(run2, { eco: opts.eco === true }))) {
65728
65729
  const p = join48(agentsDir, `${name2}.md`);
65729
65730
  writeFileSync19(p, content);
65730
65731
  written.push(p);
@@ -66230,7 +66231,7 @@ function bool(v) {
66230
66231
  }
66231
66232
  function strArray2(v) {
66232
66233
  const a = Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
66233
- return a && a.length ? a : void 0;
66234
+ return a?.length ? a : void 0;
66234
66235
  }
66235
66236
  function positive(v, key) {
66236
66237
  const n = num2(v);
@@ -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: 5.25.0
7
+ version: 5.26.0
8
8
  ---
9
9
 
10
10
  # ultra11y — audit WCAG 2.2 AA and write accessible markup
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, 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.25.0";
9
+ var VERSION = "5.26.0";
10
10
  var SCHEMA_VERSION = 2;
11
11
  var INAPPLICABLE_STATUS = "C";
12
12
 
@@ -36908,7 +36908,7 @@ function makeFilter(opts = {}) {
36908
36908
  const rel2 = toPosix(file);
36909
36909
  if (include && !include(rel2)) return false;
36910
36910
  if (exclude?.(rel2)) return false;
36911
- if (!opts.noDefaultExcludes && isTestArtifact(rel2) && !(include && include(rel2))) return false;
36911
+ if (!opts.noDefaultExcludes && isTestArtifact(rel2) && !include?.(rel2)) return false;
36912
36912
  return true;
36913
36913
  };
36914
36914
  }
@@ -36974,7 +36974,7 @@ function expandInputs(inputs, opts = {}) {
36974
36974
  const before = list.length;
36975
36975
  list = list.filter((f) => {
36976
36976
  const rel2 = toPosix(f);
36977
- return !isTestArtifact(rel2) || explicit.has(f) || include != null && include(rel2);
36977
+ return !isTestArtifact(rel2) || explicit.has(f) || include?.(rel2);
36978
36978
  });
36979
36979
  const dropped = before - list.length;
36980
36980
  if (dropped) opts.onWarn?.(`ultra11y: skipped ${dropped} test/spec/story file(s) \u2014 pass --no-default-excludes to audit them.`);
@@ -45436,7 +45436,8 @@ var EXISTENCE_SUBJECTS = /* @__PURE__ */ new Set([
45436
45436
  "autocomplete",
45437
45437
  "errors",
45438
45438
  "frames",
45439
- "downloadDocs"
45439
+ "downloadDocs",
45440
+ "newWindow"
45440
45441
  ]);
45441
45442
  var subjectsForSc = (sc) => SC_SUBJECTS[sc] ?? [];
45442
45443
  function subjectsForPackCriterion(standard, id, scs) {
@@ -46968,7 +46969,7 @@ var tableEmptyDataCell = {
46968
46969
  return out2;
46969
46970
  }
46970
46971
  };
46971
- function ownerTable(el, doc) {
46972
+ function ownerTable(el) {
46972
46973
  return ancestors(el).find((a) => a.tag === "table");
46973
46974
  }
46974
46975
  var headersAttrDangling = {
@@ -46981,7 +46982,7 @@ var headersAttrDangling = {
46981
46982
  if (el.tag !== "td" && el.tag !== "th") continue;
46982
46983
  const raw = (attr(el, "headers") ?? "").trim();
46983
46984
  if (!raw || raw.includes("{")) continue;
46984
- const table = ownerTable(el, doc);
46985
+ const table = ownerTable(el);
46985
46986
  if (!table) continue;
46986
46987
  const inTable = new Map(
46987
46988
  descendants(table).filter((d) => d.tag === "th" || d.tag === "td").map((d) => [attr(d, "id") ?? "", d])
@@ -48784,7 +48785,7 @@ function matchNode(el, node, depth) {
48784
48785
  if (node.has || node.lacks) {
48785
48786
  const desc = descendants(el);
48786
48787
  if (node.has && !node.has.every((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
48787
- if (node.lacks && node.lacks.some((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
48788
+ if (node.lacks?.some((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
48788
48789
  }
48789
48790
  return true;
48790
48791
  }
@@ -49000,7 +49001,7 @@ function extractGraphNode(ast, doc, file, opts = {}) {
49000
49001
  if (ast) {
49001
49002
  for (const stmt of asNodes((asNode(ast.program) ?? ast).body)) {
49002
49003
  const vd = stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" ? asNode(stmt.declaration) : void 0;
49003
- if (!vd || vd.type !== "VariableDeclaration") continue;
49004
+ if (vd?.type !== "VariableDeclaration") continue;
49004
49005
  for (const d of asNodes(vd.declarations)) {
49005
49006
  const idn = asNode(d.id);
49006
49007
  const init2 = asNode(d.init);
@@ -49353,7 +49354,7 @@ function hasMedia(d) {
49353
49354
  return d.elements.some((el) => {
49354
49355
  if (el.tag === "object" || el.tag === "embed") {
49355
49356
  const type = (el.attribs.type ?? "").toLowerCase();
49356
- return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/") ? true : false;
49357
+ return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/");
49357
49358
  }
49358
49359
  if (el.tag !== "iframe") return false;
49359
49360
  const hay = `${el.attribs.src ?? ""} ${el.attribs.title ?? ""} ${el.attribs.allow ?? ""}`;
@@ -59219,7 +59220,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
59219
59220
  if (v === null) {
59220
59221
  blame(it.criteriaId, `criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
59221
59222
  } else if (v === "C" || v === "NA") {
59222
- if (!it.justification || !it.justification.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
59223
+ if (!it.justification?.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
59223
59224
  const cites = (it.citations ?? []).map(readCitation).filter((c2) => c2 !== null);
59224
59225
  if (v === "C" && it.evidenceComplete === false) {
59225
59226
  blame(
@@ -59272,7 +59273,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
59272
59273
  );
59273
59274
  continue;
59274
59275
  }
59275
- if (!f.normativeRef || !f.normativeRef.trim()) {
59276
+ if (!f.normativeRef?.trim()) {
59276
59277
  blame(it.criteriaId, `criterion ${it.criteriaId}: an NC finding requires a normativeRef citing the failed test of the active standard`);
59277
59278
  } else if (!normativeRefResolves(f.normativeRef, adj.standard, isCore(adj.standard) ? void 0 : it.criteriaId)) {
59278
59279
  blame(
@@ -60873,7 +60874,7 @@ function mergeSnapshotAudit(base, snap) {
60873
60874
  for (const c2 of merged.criteria) {
60874
60875
  if (!c2.inapplicable) continue;
60875
60876
  const measured = snapById.get(c2.id);
60876
- if (!measured || measured.status !== "C" || measured.inapplicable) continue;
60877
+ if (measured?.status !== "C" || measured.inapplicable) continue;
60877
60878
  c2.status = "C";
60878
60879
  delete c2.inapplicable;
60879
60880
  if (measured.justification) c2.justification = measured.justification;
@@ -64916,7 +64917,7 @@ function foldRecordedAdjudication(root, fresh) {
64916
64917
  for (const c2 of prior.criteria) {
64917
64918
  if (c2.decidedBy !== "agent") continue;
64918
64919
  const target = byId2.get(c2.id);
64919
- if (!target || target.status !== "manual") continue;
64920
+ if (target?.status !== "manual") continue;
64920
64921
  target.status = c2.status;
64921
64922
  target.decidedBy = "agent";
64922
64923
  if (c2.justification) target.justification = c2.justification;
@@ -65541,7 +65542,7 @@ For EACH entry:
65541
65542
  Then fold: \`ultra11y verify --apply ${join47(runAbs, "VERIFY.todo.json")} --report <the report .md>\`. Without a shell, leave the fold to whoever gave you the file.
65542
65543
  `;
65543
65544
  }
65544
- function agentContracts(runAbs, engineAbs, opts = {}) {
65545
+ function agentContracts(runAbs, opts = {}) {
65545
65546
  const footer = ONE_WRITER_FOOTER.replaceAll("<RUN>", runAbs);
65546
65547
  if (opts.eco) return { adjudicator: ecoAdjudicatorContract(runAbs), refuter: ecoRefuterContract(runAbs) };
65547
65548
  return {
@@ -65724,7 +65725,7 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
65724
65725
  mkdirSync17(agentsDir, { recursive: true });
65725
65726
  const written = [];
65726
65727
  const notices = [];
65727
- for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs, { eco: opts.eco === true }))) {
65728
+ for (const [name2, content] of Object.entries(agentContracts(run2, { eco: opts.eco === true }))) {
65728
65729
  const p = join48(agentsDir, `${name2}.md`);
65729
65730
  writeFileSync19(p, content);
65730
65731
  written.push(p);
@@ -66230,7 +66231,7 @@ function bool(v) {
66230
66231
  }
66231
66232
  function strArray2(v) {
66232
66233
  const a = Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
66233
- return a && a.length ? a : void 0;
66234
+ return a?.length ? a : void 0;
66234
66235
  }
66235
66236
  function positive(v, key) {
66236
66237
  const n = num2(v);
@@ -1184,6 +1184,29 @@ export const PACK_SUBJECTS: Record<string, Record<string, string[]>> = {
1184
1184
  // about nothing when there is nothing to download. Its recall is not perfect — a document
1185
1185
  // served from an extensionless URL is missed — which is true of every subject here and is why
1186
1186
  // absence is only ever read as « nothing of that kind is in scope », never as conformance.
1187
+ //
1188
+ // `newWindow` earns its place the same way, and it is worth spelling out because the criterion
1189
+ // it closes has a single test. RGAA 13.2.1 asks one thing — « vérifier qu'à l'ouverture du
1190
+ // document, aucune nouvelle fenêtre n'est ouverte » — and a browser has exactly three ways to
1191
+ // do it: a `target` that leaves this browsing context, a `<meta http-equiv="refresh">`, and a
1192
+ // literal `window.open(`. Two are markup and the third survives minification, so a page that
1193
+ // FAILS 13.2 cannot harvest nothing. Its recall is imperfect in the same way every subject here
1194
+ // is — `open` reached through an alias is missed — which is why the absence still reads only as
1195
+ // « nothing of that kind is in scope », never as conformance.
1196
+ //
1197
+ // Measured on tests/fixtures/realworld, crawled and scanned, engine v5.25.0: 13.2 was one of
1198
+ // only two criteria still reaching the adjudicator with `evidenceComplete` and a population of
1199
+ // zero. The engine already knew the answer and was billing a model to restate it — on the run
1200
+ // this comment was written from, both were ruled NA on the first pass with no gate refusal,
1201
+ // which is the definition of a paid turn that decided nothing.
1202
+ //
1203
+ // THE OTHER ONE, 7.4, STAYS OUT, and the asymmetry is the whole point of the rule above.
1204
+ // `contextChange` does not enumerate the ways a browser CAN change context, it enumerates the
1205
+ // ways we have seen a script do it — `.submit(`, `location.href`, `router.push`, a handful of
1206
+ // handler attributes. A component that navigates through a router the list does not name
1207
+ // (`history.push`, a custom `navigate()`, `location.reload()`) fails 7.4 and matches none of
1208
+ // them. Its silence is what a failing page looks like, so admitting it would buy a cheap NA at
1209
+ // the price of a hidden non-conformity.
1187
1210
  export const EXISTENCE_SUBJECTS: ReadonlySet<string> = new Set([
1188
1211
  "images",
1189
1212
  "tables",
@@ -1194,6 +1217,7 @@ export const EXISTENCE_SUBJECTS: ReadonlySet<string> = new Set([
1194
1217
  "errors",
1195
1218
  "frames",
1196
1219
  "downloadDocs",
1220
+ "newWindow",
1197
1221
  ]);
1198
1222
 
1199
1223
  /** The subjects that decide a success criterion. Empty ⇒ the criterion has none declared,
package/src/adjudicate.ts CHANGED
@@ -799,7 +799,7 @@ export function applyAdjudication(
799
799
  if (v === null) {
800
800
  blame(it.criteriaId, `criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
801
801
  } else if (v === "C" || v === "NA") {
802
- if (!it.justification || !it.justification.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
802
+ if (!it.justification?.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
803
803
  // A clearing verdict is gated exactly like an accusing one. Before this, the only
804
804
  // check was "the justification is a non-empty string", so `"x"` cleared a criterion —
805
805
  // and a model answering C to everything published a conformance nobody had assessed.
@@ -942,7 +942,7 @@ export function applyAdjudication(
942
942
  }
943
943
  // FAIL-CLOSED: every NC finding must cite a precise, resolvable test of the active
944
944
  // standard. A good practice with no normative test is a recommendation, not an NC.
945
- if (!f.normativeRef || !f.normativeRef.trim()) {
945
+ if (!f.normativeRef?.trim()) {
946
946
  blame(it.criteriaId, `criterion ${it.criteriaId}: an NC finding requires a normativeRef citing the failed test of the active standard`);
947
947
  } else if (!normativeRefResolves(f.normativeRef, adj.standard, isCore(adj.standard) ? undefined : it.criteriaId)) {
948
948
  blame(
package/src/audit.ts CHANGED
@@ -184,7 +184,7 @@ function hasMedia(d: Doc): boolean {
184
184
  if (el.tag === "object" || el.tag === "embed") {
185
185
  const type = (el.attribs.type ?? "").toLowerCase();
186
186
  // A declared non-media type rules it out; anything else is uncertain, so it counts.
187
- return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/") ? true : false;
187
+ return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/");
188
188
  }
189
189
  if (el.tag !== "iframe") return false;
190
190
  const hay = `${el.attribs.src ?? ""} ${el.attribs.title ?? ""} ${el.attribs.allow ?? ""}`;
package/src/dev.ts CHANGED
@@ -356,7 +356,7 @@ function foldRecordedAdjudication(root: string, fresh: AuditResult): void {
356
356
  const target = byId.get(c.id);
357
357
  // A criterion the fresh run decided by itself is authoritative: the page moved, and a
358
358
  // stale ruling must never override a live measurement.
359
- if (!target || target.status !== "manual") continue;
359
+ if (target?.status !== "manual") continue;
360
360
  target.status = c.status;
361
361
  target.decidedBy = "agent";
362
362
  if (c.justification) target.justification = c.justification;
package/src/glob.ts CHANGED
@@ -89,7 +89,7 @@ export function makeFilter(opts: ExpandOpts = {}): (file: string) => boolean {
89
89
  if (include && !include(rel)) return false;
90
90
  if (exclude?.(rel)) return false;
91
91
  // Default test-artifact exclusion (re-admitted by an explicit --include match).
92
- if (!opts.noDefaultExcludes && isTestArtifact(rel) && !(include && include(rel))) return false;
92
+ if (!opts.noDefaultExcludes && isTestArtifact(rel) && !include?.(rel)) return false;
93
93
  return true;
94
94
  };
95
95
  }
@@ -197,7 +197,7 @@ export function expandInputs(inputs: string[], opts: ExpandOpts = {}): string[]
197
197
  const before = list.length;
198
198
  list = list.filter((f) => {
199
199
  const rel = toPosix(f);
200
- return !isTestArtifact(rel) || explicit.has(f) || (include != null && include(rel));
200
+ return !isTestArtifact(rel) || explicit.has(f) || include?.(rel);
201
201
  });
202
202
  const dropped = before - list.length;
203
203
  if (dropped) opts.onWarn?.(`ultra11y: skipped ${dropped} test/spec/story file(s) — pass --no-default-excludes to audit them.`);
@@ -225,7 +225,7 @@ export function extractGraphNode(ast: AstFile | null, doc: Doc, file: string, op
225
225
  if (ast) {
226
226
  for (const stmt of asNodes((asNode(ast.program) ?? ast).body)) {
227
227
  const vd = stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" ? asNode(stmt.declaration) : undefined;
228
- if (!vd || vd.type !== "VariableDeclaration") continue;
228
+ if (vd?.type !== "VariableDeclaration") continue;
229
229
  for (const d of asNodes(vd.declarations)) {
230
230
  const idn = asNode(d.id);
231
231
  const init = asNode(d.init);
@@ -22,7 +22,7 @@ interface ScreenshotDetails {
22
22
  path: string;
23
23
  }
24
24
 
25
- // biome-ignore lint/suspicious/noExplicitAny: Cypress's `on` is not a dependency of this package
25
+ // `any`: Cypress's `on` is not a dependency of this package.
26
26
  type On = (event: string, handler: any) => void;
27
27
 
28
28
  export interface TaskPayload extends SnapshotPayload {
@@ -14,10 +14,10 @@ import { type CheckOptions, buildPayload, slugify, stayedOnPage } from "./payloa
14
14
 
15
15
  // Cypress's globals are not a dependency of this package; they exist at run time only.
16
16
  declare const Cypress: {
17
- // biome-ignore lint/suspicious/noExplicitAny: Cypress's own command signature
17
+ // `any`: Cypress's own command signature.
18
18
  Commands: { add(name: string, fn: (...args: any[]) => any): void };
19
19
  };
20
- // biome-ignore lint/suspicious/noExplicitAny: same
20
+ // `any` for the same reason.
21
21
  declare const cy: any;
22
22
 
23
23
  export interface CypressCheckOptions extends CheckOptions {
@@ -88,7 +88,7 @@ function fromConfig(): { only?: string[]; limits?: Omit<ProbeTuning, "only"> } {
88
88
  async function runAxe(page: PlaywrightPage, mode: boolean | "auto" | undefined): Promise<{ ran: true; violations: unknown[] } | undefined> {
89
89
  if (mode === false) return undefined;
90
90
  const demanded = mode === true;
91
- // biome-ignore lint/suspicious/noExplicitAny: the builder's shape is axe's, which is not a dependency here
91
+ // `any`: the builder's shape is axe's, which is not a dependency here.
92
92
  let AxeBuilder: any;
93
93
  // PROJECT FIRST, then relative to this module — the same order the Playwright fixture above
94
94
  // and `scan --runtime local` use, and for the same reason: module-relative alone works for an
@@ -204,16 +204,16 @@ export async function checkA11y(page: PlaywrightPage, opts: PlaywrightCheckOptio
204
204
  *
205
205
  * Resolution is lazy and guarded either way, so importing this in a Cypress-only repo
206
206
  * gives `undefined` rather than a crash. */
207
- // biome-ignore lint/suspicious/noExplicitAny: the shape is Playwright's, which is not a dependency here
207
+ // `any`: the shape is Playwright's, which is not a dependency here.
208
208
  export const test: any = (() => {
209
- // biome-ignore lint/suspicious/noExplicitAny: same
209
+ // `any` for the same reason.
210
210
  const load = (from: string): any => createRequire(from)("@playwright/test");
211
211
  for (const from of [`${process.cwd()}/package.json`, import.meta.url]) {
212
212
  try {
213
213
  const base = load(from).test;
214
214
  if (!base?.extend) continue;
215
215
  return base.extend({
216
- // biome-ignore lint/suspicious/noExplicitAny: same
216
+ // `any` for the same reason.
217
217
  ultra11y: async ({ page }: any, use: any) => {
218
218
  await use((opts: PlaywrightCheckOptions) => checkA11y(page, opts));
219
219
  },
@@ -241,7 +241,7 @@ export interface SweepOptions {
241
241
  * `waitForLoadState`, `waitForFunction`, locators — and Playwright is not a dependency of
242
242
  * this package, so the narrow structural shape the collector needs would reject the very
243
243
  * function this option exists to take. Same reasoning as `test` below. */
244
- // biome-ignore lint/suspicious/noExplicitAny: the shape is Playwright's, which is not a dependency here
244
+ // `any`: the shape is Playwright's, which is not a dependency here.
245
245
  settle?: (page: any) => Promise<void> | void;
246
246
  /** Passed to every `checkA11y`. `failOn: false` (the default here) records without
247
247
  * asserting: the durable output of a sweep is the snapshot, not a red test. */
@@ -80,7 +80,7 @@ function bool(v: unknown): boolean {
80
80
 
81
81
  function strArray(v: unknown): string[] | undefined {
82
82
  const a = Array.isArray(v) && v.every((x) => typeof x === "string") ? (v as string[]) : undefined;
83
- return a && a.length ? a : undefined;
83
+ return a?.length ? a : undefined;
84
84
  }
85
85
 
86
86
  function positive(v: unknown, key: string): number | undefined {
@@ -260,7 +260,10 @@ Then fold: \`ultra11y verify --apply ${join(runAbs, "VERIFY.todo.json")} --repor
260
260
  `;
261
261
  }
262
262
 
263
- export function agentContracts(runAbs: string, engineAbs: string, opts: { eco?: boolean } = {}): Record<string, string> {
263
+ // `engineAbs` is deliberately NOT a parameter: the emitted workflow injects `ENGINE` as a
264
+ // constant and the contract refers to it by name, so a second copy of the path here could only
265
+ // ever disagree with the one the agent is actually handed.
266
+ export function agentContracts(runAbs: string, opts: { eco?: boolean } = {}): Record<string, string> {
264
267
  const footer = ONE_WRITER_FOOTER.replaceAll("<RUN>", runAbs);
265
268
  if (opts.eco) return { adjudicator: ecoAdjudicatorContract(runAbs), refuter: ecoRefuterContract(runAbs) };
266
269
  return {
@@ -165,7 +165,7 @@ export function orchestrateRun(runDir: string, engineAbs: string, opts: Orchestr
165
165
  // exists on the sequential path — least of all in CI, where the adjudicator has no shell and
166
166
  // writes a file. Emitting the fan-out text there and then telling an agent to obey it VERBATIM
167
167
  // is two contradictory sets of instructions, which is what a small model breaks on.
168
- for (const [name, content] of Object.entries(agentContracts(run, engineAbs, { eco: opts.eco === true }))) {
168
+ for (const [name, content] of Object.entries(agentContracts(run, { eco: opts.eco === true }))) {
169
169
  const p = join(agentsDir, `${name}.md`);
170
170
  writeFileSync(p, content);
171
171
  written.push(p);
@@ -1,6 +1,6 @@
1
1
  // Theme 1 — Images.
2
2
  import type { Doc, El } from "../parse/html.js";
3
- import { attr, hasAttr, hasBoundAttr, boundAttr, hasDynamicSpread, visibleText, ancestors, descendants } from "../parse/html.js";
3
+ import { attr, hasBoundAttr, boundAttr, hasDynamicSpread, visibleText, ancestors, descendants } from "../parse/html.js";
4
4
  import { accessibleName, isNameExempt } from "../name.js";
5
5
  import type { Rule, RuleFinding } from "./rule.js";
6
6
 
@@ -181,7 +181,7 @@ const tableEmptyDataCell: Rule = {
181
181
  };
182
182
 
183
183
  /** The <table> a cell belongs to (nearest table ancestor), or undefined. */
184
- function ownerTable(el: El, doc: Doc): El | undefined {
184
+ function ownerTable(el: El): El | undefined {
185
185
  return ancestors(el).find((a) => a.tag === "table");
186
186
  }
187
187
 
@@ -198,7 +198,7 @@ const headersAttrDangling: Rule = {
198
198
  if (el.tag !== "td" && el.tag !== "th") continue;
199
199
  const raw = (attr(el, "headers") ?? "").trim();
200
200
  if (!raw || raw.includes("{")) continue;
201
- const table = ownerTable(el, doc);
201
+ const table = ownerTable(el);
202
202
  if (!table) continue;
203
203
  const inTable = new Map(
204
204
  descendants(table)
package/src/scan.ts CHANGED
@@ -852,7 +852,7 @@ export function mergeSnapshotAudit(base: AuditResult, snap: AuditResult): AuditR
852
852
  const measured = snapById.get(c.id);
853
853
  // Only a snapshot that reached `C` WITHOUT the flag has falsified "nothing of that kind
854
854
  // here" — that one looked at the rendered page and found the subject.
855
- if (!measured || measured.status !== "C" || measured.inapplicable) continue;
855
+ if (measured?.status !== "C" || measured.inapplicable) continue;
856
856
  c.status = "C";
857
857
  delete c.inapplicable;
858
858
  // Keep the claim citable: adopt what the measuring half said, rather than leaving a `C`
@@ -60,7 +60,7 @@ function matchNode(el: El, node: MatchNode, depth: number): boolean {
60
60
  const desc = descendants(el);
61
61
  if (node.has && !node.has.every((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
62
62
  // `lacks`: NO listed sub-condition may match ANY descendant.
63
- if (node.lacks && node.lacks.some((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
63
+ if (node.lacks?.some((sub) => desc.some((d) => matchNode(d, sub, depth + 1)))) return false;
64
64
  }
65
65
  return true;
66
66
  }
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.25.0";
3
+ export const VERSION = "5.26.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,