ultra11y 5.5.3 → 5.5.4

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.5.3",
13
+ "version": "5.5.4",
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.5.3",
4
+ "version": "5.5.4",
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.5.3",
4
+ "version": "5.5.4",
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.5.3";
30
+ const ULTRA11Y_PLUGIN_VERSION = "5.5.4";
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.5.3",
3
+ "version": "5.5.4",
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.5.3";
9
+ var VERSION = "5.5.4";
10
10
  var SCHEMA_VERSION = 2;
11
11
 
12
12
  // src/audit.ts
@@ -57288,6 +57288,14 @@ function buildAdjudicationWorklist(audit2, opts = {}) {
57288
57288
  (r) => blankItem(r.criteriaId, r.automatability, scTitle(r.criteriaId) ?? void 0, harvestSubjects(subjectsForSc(r.criteriaId), docs))
57289
57289
  );
57290
57290
  }
57291
+ function readCitation(c2) {
57292
+ if (typeof c2 !== "string") return c2;
57293
+ const at = c2.lastIndexOf(":");
57294
+ if (at <= 0) return null;
57295
+ const line = Number.parseInt(c2.slice(at + 1), 10);
57296
+ if (!Number.isFinite(line)) return null;
57297
+ return { file: c2.slice(0, at), line, selector: "", snippet: "" };
57298
+ }
57291
57299
  function auditFiles(audit2, cwd) {
57292
57300
  const inputs = audit2.scope.inputs.filter((i2) => i2 !== "-" && i2 !== "<stdin>");
57293
57301
  if (!inputs.length) return /* @__PURE__ */ new Set();
@@ -57408,7 +57416,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
57408
57416
  blame(it.criteriaId, `criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
57409
57417
  } else if (v === "C" || v === "NA") {
57410
57418
  if (!it.justification || !it.justification.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
57411
- const cites = it.citations ?? [];
57419
+ const cites = (it.citations ?? []).map(readCitation).filter((c2) => c2 !== null);
57412
57420
  if (v === "C" && it.evidenceComplete === false) {
57413
57421
  blame(
57414
57422
  it.criteriaId,
@@ -57843,7 +57851,9 @@ function entriesFrom(adj, accepted, date) {
57843
57851
  verdict: it.verdict,
57844
57852
  ...it.justification?.trim() ? { justification: it.justification.trim() } : {},
57845
57853
  ...it.reason ? { reason: it.reason } : {},
57846
- ...it.citations?.length ? { citations: it.citations } : {},
57854
+ // Normalised here too: a citation may have arrived as a bare "file:line" string, and a
57855
+ // ledger entry has to hold the object form so a replay can re-anchor and re-ground it.
57856
+ ...it.citations?.length ? { citations: it.citations.map(readCitation).filter((c2) => c2 !== null) } : {},
57847
57857
  ...it.findings?.length ? { findings: it.findings } : {},
57848
57858
  ...it.recommendations?.length ? { recommendations: it.recommendations } : {},
57849
57859
  evidenceFingerprint: evidenceFingerprint(it.evidence),
@@ -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.5.3
8
+ version: 5.5.4
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.5.3";
9
+ var VERSION = "5.5.4";
10
10
  var SCHEMA_VERSION = 2;
11
11
 
12
12
  // src/audit.ts
@@ -57288,6 +57288,14 @@ function buildAdjudicationWorklist(audit2, opts = {}) {
57288
57288
  (r) => blankItem(r.criteriaId, r.automatability, scTitle(r.criteriaId) ?? void 0, harvestSubjects(subjectsForSc(r.criteriaId), docs))
57289
57289
  );
57290
57290
  }
57291
+ function readCitation(c2) {
57292
+ if (typeof c2 !== "string") return c2;
57293
+ const at = c2.lastIndexOf(":");
57294
+ if (at <= 0) return null;
57295
+ const line = Number.parseInt(c2.slice(at + 1), 10);
57296
+ if (!Number.isFinite(line)) return null;
57297
+ return { file: c2.slice(0, at), line, selector: "", snippet: "" };
57298
+ }
57291
57299
  function auditFiles(audit2, cwd) {
57292
57300
  const inputs = audit2.scope.inputs.filter((i2) => i2 !== "-" && i2 !== "<stdin>");
57293
57301
  if (!inputs.length) return /* @__PURE__ */ new Set();
@@ -57408,7 +57416,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
57408
57416
  blame(it.criteriaId, `criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
57409
57417
  } else if (v === "C" || v === "NA") {
57410
57418
  if (!it.justification || !it.justification.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
57411
- const cites = it.citations ?? [];
57419
+ const cites = (it.citations ?? []).map(readCitation).filter((c2) => c2 !== null);
57412
57420
  if (v === "C" && it.evidenceComplete === false) {
57413
57421
  blame(
57414
57422
  it.criteriaId,
@@ -57843,7 +57851,9 @@ function entriesFrom(adj, accepted, date) {
57843
57851
  verdict: it.verdict,
57844
57852
  ...it.justification?.trim() ? { justification: it.justification.trim() } : {},
57845
57853
  ...it.reason ? { reason: it.reason } : {},
57846
- ...it.citations?.length ? { citations: it.citations } : {},
57854
+ // Normalised here too: a citation may have arrived as a bare "file:line" string, and a
57855
+ // ledger entry has to hold the object form so a replay can re-anchor and re-ground it.
57856
+ ...it.citations?.length ? { citations: it.citations.map(readCitation).filter((c2) => c2 !== null) } : {},
57847
57857
  ...it.findings?.length ? { findings: it.findings } : {},
57848
57858
  ...it.recommendations?.length ? { recommendations: it.recommendations } : {},
57849
57859
  evidenceFingerprint: evidenceFingerprint(it.evidence),
@@ -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.5.3
7
+ version: 5.5.4
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.5.3";
9
+ var VERSION = "5.5.4";
10
10
  var SCHEMA_VERSION = 2;
11
11
 
12
12
  // src/audit.ts
@@ -57288,6 +57288,14 @@ function buildAdjudicationWorklist(audit2, opts = {}) {
57288
57288
  (r) => blankItem(r.criteriaId, r.automatability, scTitle(r.criteriaId) ?? void 0, harvestSubjects(subjectsForSc(r.criteriaId), docs))
57289
57289
  );
57290
57290
  }
57291
+ function readCitation(c2) {
57292
+ if (typeof c2 !== "string") return c2;
57293
+ const at = c2.lastIndexOf(":");
57294
+ if (at <= 0) return null;
57295
+ const line = Number.parseInt(c2.slice(at + 1), 10);
57296
+ if (!Number.isFinite(line)) return null;
57297
+ return { file: c2.slice(0, at), line, selector: "", snippet: "" };
57298
+ }
57291
57299
  function auditFiles(audit2, cwd) {
57292
57300
  const inputs = audit2.scope.inputs.filter((i2) => i2 !== "-" && i2 !== "<stdin>");
57293
57301
  if (!inputs.length) return /* @__PURE__ */ new Set();
@@ -57408,7 +57416,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
57408
57416
  blame(it.criteriaId, `criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
57409
57417
  } else if (v === "C" || v === "NA") {
57410
57418
  if (!it.justification || !it.justification.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
57411
- const cites = it.citations ?? [];
57419
+ const cites = (it.citations ?? []).map(readCitation).filter((c2) => c2 !== null);
57412
57420
  if (v === "C" && it.evidenceComplete === false) {
57413
57421
  blame(
57414
57422
  it.criteriaId,
@@ -57843,7 +57851,9 @@ function entriesFrom(adj, accepted, date) {
57843
57851
  verdict: it.verdict,
57844
57852
  ...it.justification?.trim() ? { justification: it.justification.trim() } : {},
57845
57853
  ...it.reason ? { reason: it.reason } : {},
57846
- ...it.citations?.length ? { citations: it.citations } : {},
57854
+ // Normalised here too: a citation may have arrived as a bare "file:line" string, and a
57855
+ // ledger entry has to hold the object form so a replay can re-anchor and re-ground it.
57856
+ ...it.citations?.length ? { citations: it.citations.map(readCitation).filter((c2) => c2 !== null) } : {},
57847
57857
  ...it.findings?.length ? { findings: it.findings } : {},
57848
57858
  ...it.recommendations?.length ? { recommendations: it.recommendations } : {},
57849
57859
  evidenceFingerprint: evidenceFingerprint(it.evidence),
package/src/adjudicate.ts CHANGED
@@ -139,7 +139,10 @@ export interface AdjudicationItem {
139
139
  // is what turns "cite something real" into "cite the evidence you were shown". When the
140
140
  // harvester found no evidence at all there is nothing to clear, and the honest verdicts
141
141
  // are `manual` or `NA`, never `C`.
142
- citations?: Evidence[];
142
+ // Objects are what the contract asks for; a bare `"file:line"` string is accepted too,
143
+ // because that is the shape the worklist's own `alsoAt` uses and a real run reached for it.
144
+ // See `readCitation` — the gate is identical either way, a string just carries no snippet.
145
+ citations?: (Evidence | string)[];
143
146
  // Non-normative good practices the agent noted on this criterion — folded back into the
144
147
  // audit as ADVISORY findings (grounded exactly like an NC finding, but never affecting
145
148
  // status: they cannot flip the criterion to NC nor enter conformancePct). Optional.
@@ -334,6 +337,25 @@ export function buildAdjudicationWorklist(audit: AuditResult, opts: { cwd?: stri
334
337
  );
335
338
  }
336
339
 
340
+ /** Read a citation whichever way it was written.
341
+ *
342
+ * The contract asks for `{file, line, …}`, and a real run wrote `"src/Foo.tsx:15"` instead —
343
+ * 63 verdicts refused for a shape, not for a claim. It is an unambiguous form and the
344
+ * worklist itself is full of it (`alsoAt` is exactly `file:line`), so an adjudicator
345
+ * reaching for it is being consistent, not careless.
346
+ *
347
+ * Nothing about the gate moves: membership and grounding run on the result either way. A
348
+ * string simply carries no snippet, so grounding falls back to its selector/line checks —
349
+ * weaker evidence, and the reason the object form stays what the contract asks for. */
350
+ export function readCitation(c: Evidence | string): Evidence | null {
351
+ if (typeof c !== "string") return c;
352
+ const at = c.lastIndexOf(":");
353
+ if (at <= 0) return null;
354
+ const line = Number.parseInt(c.slice(at + 1), 10);
355
+ if (!Number.isFinite(line)) return null;
356
+ return { file: c.slice(0, at), line, selector: "", snippet: "" };
357
+ }
358
+
337
359
  /** The files this audit actually read. Computed from the audit's own scope inputs, so it is
338
360
  * the same set the harvest walked — `discover` only globs, it does not parse, so this costs
339
361
  * little and is memoised per fold. */
@@ -580,7 +602,7 @@ export function applyAdjudication(
580
602
  // A clearing verdict is gated exactly like an accusing one. Before this, the only
581
603
  // check was "the justification is a non-empty string", so `"x"` cleared a criterion —
582
604
  // and a model answering C to everything published a conformance nobody had assessed.
583
- const cites = it.citations ?? [];
605
+ const cites = (it.citations ?? []).map(readCitation).filter((c): c is Evidence => c !== null);
584
606
  // An INCOMPLETE reading may report a failure it saw; it may never clear what it never
585
607
  // looked at. Before class-based harvesting the evidence was a 30-anchor sample of a
586
608
  // population that ran to thousands, so a `C` was routinely a conformity claim over
package/src/ledger.ts CHANGED
@@ -30,7 +30,7 @@ import { createHash } from "node:crypto";
30
30
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
31
31
  import { dirname, join } from "node:path";
32
32
 
33
- import { type AdjudicationFile, type AdjudicationItem, type AgentFinding, buildAdjudicationWorklist, type Evidence } from "./adjudicate.js";
33
+ import { type AdjudicationFile, type AdjudicationItem, type AgentFinding, buildAdjudicationWorklist, type Evidence, readCitation } from "./adjudicate.js";
34
34
  import { CORE, type StandardId } from "./standards/index.js";
35
35
  import type { AuditResult } from "./types.js";
36
36
  import { SCHEMA_VERSION } from "./types.js";
@@ -148,7 +148,9 @@ export function entriesFrom(adj: AdjudicationFile, accepted: ReadonlySet<string>
148
148
  verdict: it.verdict,
149
149
  ...(it.justification?.trim() ? { justification: it.justification.trim() } : {}),
150
150
  ...(it.reason ? { reason: it.reason } : {}),
151
- ...(it.citations?.length ? { citations: it.citations } : {}),
151
+ // Normalised here too: a citation may have arrived as a bare "file:line" string, and a
152
+ // ledger entry has to hold the object form so a replay can re-anchor and re-ground it.
153
+ ...(it.citations?.length ? { citations: it.citations.map(readCitation).filter((c): c is Evidence => c !== null) } : {}),
152
154
  ...(it.findings?.length ? { findings: it.findings } : {}),
153
155
  ...(it.recommendations?.length ? { recommendations: it.recommendations } : {}),
154
156
  evidenceFingerprint: evidenceFingerprint(it.evidence),
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.5.3";
3
+ export const VERSION = "5.5.4";
4
4
  export const SCHEMA_VERSION = 2;
5
5
 
6
6
  // Lang = the UI FRAME's language — the `L` localization tables each module (report,