ultra11y 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +223 -0
  3. package/docker/Dockerfile +7 -0
  4. package/docker/package.json +9 -0
  5. package/docker/runner.mjs +23 -0
  6. package/package.json +73 -0
  7. package/scripts/bench.mjs +151 -0
  8. package/scripts/build-act-corpus.mjs +143 -0
  9. package/scripts/build-adjudication.mjs +503 -0
  10. package/scripts/build-pack-rgaa.mjs +397 -0
  11. package/scripts/build-standards.mjs +548 -0
  12. package/scripts/copy-bundle.mjs +39 -0
  13. package/scripts/import-pack.mjs +107 -0
  14. package/scripts/sync-engine.mjs +84 -0
  15. package/scripts/sync-version.mjs +43 -0
  16. package/scripts/ultra11y.mjs +52739 -0
  17. package/scripts/vendor/act-testcases.json +12297 -0
  18. package/scripts/vendor/rgaa/SOURCE.json +8 -0
  19. package/scripts/vendor/rgaa/criteres.json +4144 -0
  20. package/scripts/vendor/rgaa/glossaire.json +480 -0
  21. package/scripts/vendor/wcag-2.2-fr.json +82 -0
  22. package/scripts/vendor/wcag-2.2-sc-universe.json +950 -0
  23. package/scripts/vendor/wcag-2.2-sc.json +574 -0
  24. package/scripts/verify-rgaa-source.mjs +126 -0
  25. package/scripts/verify-skill-bundle.mjs +91 -0
  26. package/skills/review-a11y/SKILL.md +123 -0
  27. package/skills/review-a11y/scripts/ultra11y.mjs +52739 -0
  28. package/skills/ultra11y/SKILL.md +292 -0
  29. package/skills/ultra11y/references/act.md +163 -0
  30. package/skills/ultra11y/references/adjudication.md +599 -0
  31. package/skills/ultra11y/references/audit.md +100 -0
  32. package/skills/ultra11y/references/authoring.md +37 -0
  33. package/skills/ultra11y/references/automation.md +113 -0
  34. package/skills/ultra11y/references/correction.md +41 -0
  35. package/skills/ultra11y/references/criteria.md +129 -0
  36. package/skills/ultra11y/references/cross-file.md +82 -0
  37. package/skills/ultra11y/references/dynamic.md +152 -0
  38. package/skills/ultra11y/references/false-positives.md +81 -0
  39. package/skills/ultra11y/references/fix.md +46 -0
  40. package/skills/ultra11y/references/focus-and-logic.md +64 -0
  41. package/skills/ultra11y/references/forbidden-patterns.md +61 -0
  42. package/skills/ultra11y/references/forms-and-errors.md +147 -0
  43. package/skills/ultra11y/references/guidance.md +55 -0
  44. package/skills/ultra11y/references/judgment.md +90 -0
  45. package/skills/ultra11y/references/media-and-motion.md +116 -0
  46. package/skills/ultra11y/references/methodology.md +72 -0
  47. package/skills/ultra11y/references/naming.md +117 -0
  48. package/skills/ultra11y/references/orchestration.md +44 -0
  49. package/skills/ultra11y/references/packs.md +193 -0
  50. package/skills/ultra11y/references/prd.md +104 -0
  51. package/skills/ultra11y/references/rendered.md +110 -0
  52. package/skills/ultra11y/references/scale.md +49 -0
  53. package/skills/ultra11y/references/standards.md +97 -0
  54. package/skills/ultra11y/references/structure.md +138 -0
  55. package/skills/ultra11y/references/verify.md +88 -0
  56. package/skills/ultra11y/references/widgets.md +106 -0
  57. package/skills/ultra11y/scripts/ultra11y.mjs +52739 -0
  58. package/src/act.ts +441 -0
  59. package/src/adjudicate.ts +630 -0
  60. package/src/aria.ts +230 -0
  61. package/src/audit.ts +396 -0
  62. package/src/auditor.ts +399 -0
  63. package/src/autofill.ts +102 -0
  64. package/src/axe-map.ts +294 -0
  65. package/src/baseline.ts +75 -0
  66. package/src/capture.ts +182 -0
  67. package/src/check.ts +243 -0
  68. package/src/cli.ts +1927 -0
  69. package/src/color.ts +102 -0
  70. package/src/config.ts +182 -0
  71. package/src/crawl.ts +75 -0
  72. package/src/criteria.ts +183 -0
  73. package/src/data/adjudication.json +770 -0
  74. package/src/data/guidance/rgaa.json +2395 -0
  75. package/src/data/standards/rgaa.glossary.json +478 -0
  76. package/src/data/standards/rgaa.json +3454 -0
  77. package/src/data/wcag-universe.json +530 -0
  78. package/src/data/wcag.json +1178 -0
  79. package/src/discover.ts +203 -0
  80. package/src/fix/codemods.ts +76 -0
  81. package/src/fix/diff.ts +64 -0
  82. package/src/fix/edits.ts +126 -0
  83. package/src/fix.ts +215 -0
  84. package/src/gh.ts +214 -0
  85. package/src/glob.ts +206 -0
  86. package/src/graph/build.ts +114 -0
  87. package/src/graph/engine-resolve.ts +0 -0
  88. package/src/graph/graph.ts +126 -0
  89. package/src/graph/imports.ts +359 -0
  90. package/src/graph/resolve.ts +73 -0
  91. package/src/graph/tsconfig.ts +113 -0
  92. package/src/grounding.ts +119 -0
  93. package/src/guidance/index.ts +45 -0
  94. package/src/guidance/types.ts +36 -0
  95. package/src/init.ts +113 -0
  96. package/src/mcp/handlers.ts +388 -0
  97. package/src/mcp/http.ts +260 -0
  98. package/src/mcp/prompts.ts +183 -0
  99. package/src/mcp/protocol.ts +187 -0
  100. package/src/mcp/resources.ts +136 -0
  101. package/src/mcp/server.ts +228 -0
  102. package/src/mcp/stdio.ts +119 -0
  103. package/src/mcp/tools.ts +347 -0
  104. package/src/messages.ts +1011 -0
  105. package/src/name.ts +174 -0
  106. package/src/orchestrate-templates.ts +231 -0
  107. package/src/orchestrate.ts +186 -0
  108. package/src/output.ts +108 -0
  109. package/src/pack.ts +187 -0
  110. package/src/parse/html.ts +258 -0
  111. package/src/parse/jsx-ast.ts +67 -0
  112. package/src/parse/jsx-bridge.ts +247 -0
  113. package/src/parse/jsx.ts +19 -0
  114. package/src/parse/source.ts +75 -0
  115. package/src/prd.ts +406 -0
  116. package/src/project-lock.ts +42 -0
  117. package/src/render.ts +307 -0
  118. package/src/report.ts +399 -0
  119. package/src/rules/colors.ts +125 -0
  120. package/src/rules/cross-registry.ts +264 -0
  121. package/src/rules/cross-rule.ts +83 -0
  122. package/src/rules/forms.ts +590 -0
  123. package/src/rules/frames.ts +33 -0
  124. package/src/rules/headings.ts +227 -0
  125. package/src/rules/images.ts +200 -0
  126. package/src/rules/links.ts +193 -0
  127. package/src/rules/mandatory.ts +173 -0
  128. package/src/rules/multimedia.ts +77 -0
  129. package/src/rules/navigation.ts +190 -0
  130. package/src/rules/presentation.ts +75 -0
  131. package/src/rules/registry.ts +57 -0
  132. package/src/rules/rule.ts +122 -0
  133. package/src/rules/scripts-aria.ts +636 -0
  134. package/src/rules/tables.ts +264 -0
  135. package/src/rules/timing.ts +55 -0
  136. package/src/sample.ts +176 -0
  137. package/src/scan-local.ts +866 -0
  138. package/src/scan.ts +538 -0
  139. package/src/standards/derive.ts +195 -0
  140. package/src/standards/index.ts +31 -0
  141. package/src/standards/pack-rules.ts +111 -0
  142. package/src/standards/pack.ts +81 -0
  143. package/src/standards/registry.ts +100 -0
  144. package/src/standards/types.ts +202 -0
  145. package/src/standards/validate.ts +544 -0
  146. package/src/standards/vocabulary.ts +75 -0
  147. package/src/types.ts +355 -0
  148. package/src/util.ts +32 -0
  149. package/src/vendor/codeindex-engine.d.mts +1009 -0
  150. package/src/vendor/codeindex-engine.mjs +13701 -0
  151. package/src/vendor/engine.meta.json +9 -0
  152. package/src/verify.ts +275 -0
  153. package/src/wcag.ts +128 -0
@@ -0,0 +1,122 @@
1
+ // Rule contract + helpers shared by every rule module and the registry.
2
+ // Kept dependency-light (no value import of the registry) to avoid cycles.
3
+ import type { Doc, El } from "../parse/html.js";
4
+ import type { Finding, Severity } from "../types.js";
5
+ import { snippet, textContent, elementsByTag } from "../parse/html.js";
6
+ import { MSG_CATALOG, type MsgParams } from "../messages.js";
7
+
8
+ // A build-time / runtime content-injection placeholder left in a framework SHELL
9
+ // template (SvelteKit `%sveltekit.body%`, Mustache/Vue `{{ }}`, EJS/ERB `<% %>`,
10
+ // Jinja/Liquid `{% %}`, Razor `@RenderBody()`). Their real markup is injected later.
11
+ const INJECT_MARKER = /%[a-zA-Z][\w.]*%|\{\{[\s\S]*?\}\}|<%[-=]?[\s\S]*?%>|\{%[\s\S]*?%\}|@RenderBody\b/;
12
+ // SPA mount-point ids: an EMPTY element with one of these ids is a shell, not content.
13
+ const MOUNT_IDS = new Set(["app", "root", "__next", "__nuxt", "svelte", "q-app", "app-root", "___gatsby"]);
14
+
15
+ /** The document body is a framework shell whose real content (incl. any <h1>) is
16
+ * injected at build/runtime — a placeholder marker, or an empty SPA mount point. */
17
+ export function shellBodyInjected(doc: Doc): boolean {
18
+ const region = elementsByTag(doc, "body")[0] ?? elementsByTag(doc, "html")[0];
19
+ if (!region) return false;
20
+ if (INJECT_MARKER.test(textContent(region))) return true;
21
+ return doc.elements.some(
22
+ (el) => MOUNT_IDS.has((el.attribs.id ?? "").toLowerCase()) && !el.children.some((c) => (c.type === "element" ? c.tag !== "script" : c.data.trim() !== "")),
23
+ );
24
+ }
25
+
26
+ /** The document <head> injects its <title> via a framework placeholder
27
+ * (e.g. SvelteKit `%sveltekit.head%`, `<%= title %>`, `{{ title }}`). */
28
+ export function shellHeadInjected(doc: Doc): boolean {
29
+ const head = elementsByTag(doc, "head")[0];
30
+ return !!head && INJECT_MARKER.test(textContent(head));
31
+ }
32
+
33
+ export interface RuleFinding {
34
+ /** the specific WCAG success criterion this finding evidences (e.g. "1.4.3") */
35
+ criteriaId: string;
36
+ /** element the finding is anchored to (for line/col/snippet/selector) */
37
+ el: El;
38
+ /** catalog key into MSG_CATALOG (src/messages.ts) — a rule id, or "<ruleId>.<variant>"
39
+ * when the rule has more than one distinct message shape. */
40
+ msgId: string;
41
+ /** language-neutral data interpolated into the catalog templates (tag names, attribute
42
+ * values, counts, ids, small enums…) — never a pre-rendered phrase. */
43
+ params?: MsgParams;
44
+ severity?: Severity; // override the rule default
45
+ selectorHint?: string; // override the derived selector
46
+ preliminary?: boolean; // provisional finding (target/name may resolve at composition/runtime)
47
+ // Per-finding override of the rule-level `advisory` flag (finding-level wins). A rule
48
+ // may be normative overall yet mark an individual finding as a non-normative
49
+ // recommendation, or vice-versa. Absent ⇒ inherit the rule's `advisory`.
50
+ advisory?: boolean;
51
+ }
52
+
53
+ export interface Rule {
54
+ id: string;
55
+ /** every WCAG success criterion this rule may contribute to (registry cross-check) */
56
+ criteria: string[];
57
+ severity: Severity; // default severity for findings
58
+ /** "page" rules only run on a full document (skip fragments/components) */
59
+ scope?: "page" | "any";
60
+ // When true, EVERY finding this rule raises is a non-normative recommendation
61
+ // (Finding.advisory) unless a RuleFinding overrides it — it can never flip a criterion
62
+ // to NC. Absent ⇒ normative. See src/rules/headings.ts (h1-missing / h1-multiple).
63
+ advisory?: boolean;
64
+ run(doc: Doc): RuleFinding[];
65
+ }
66
+
67
+ /** A full document has an <html> element; fragments/JSX components do not. */
68
+ export function isFullDocument(doc: Doc): boolean {
69
+ return doc.elements.some((e) => e.tag === "html");
70
+ }
71
+
72
+ /** A short, human CSS-ish selector to locate the element in source. */
73
+ export function selectorOf(el: El): string {
74
+ const id = el.attribs.id;
75
+ if (id) return `${el.tag}#${id}`;
76
+ const type = el.attribs.type;
77
+ if (type && (el.tag === "input" || el.tag === "button")) return `${el.tag}[type=${type}]`;
78
+ const cls = el.attribs.class;
79
+ if (cls) return `${el.tag}.${cls.trim().split(/\s+/)[0]}`;
80
+ if (el.tag === "a" && el.attribs.href) {
81
+ const h = el.attribs.href!;
82
+ return `a[href=${h.length > 30 ? h.slice(0, 30) + "…" : h}]`;
83
+ }
84
+ return el.tag;
85
+ }
86
+
87
+ export function toFinding(doc: Doc, ruleId: string, def: Severity, rf: RuleFinding, ruleAdvisory?: boolean): Finding {
88
+ const entry = MSG_CATALOG[rf.msgId];
89
+ if (!entry) {
90
+ throw new Error(`toFinding: msgId "${rf.msgId}" (rule "${ruleId}") is not in MSG_CATALOG — add it to src/messages.ts.`);
91
+ }
92
+ const params = rf.params ?? {};
93
+ return {
94
+ ruleId,
95
+ criteriaId: rf.criteriaId,
96
+ file: doc.file,
97
+ line: rf.el.line,
98
+ col: rf.el.col,
99
+ selectorHint: rf.selectorHint ?? selectorOf(rf.el),
100
+ severity: rf.severity ?? def,
101
+ message: entry.message.en(params),
102
+ remediation: entry.remediation.en(params),
103
+ msg: rf.params ? { id: rf.msgId, params: rf.params } : { id: rf.msgId },
104
+ snippet: snippet(doc, rf.el),
105
+ // Only carry source offsets when they index into the *real* file. For lossy
106
+ // JSX/TSX the offsets are into the transformed HTML string, so `fix` must not
107
+ // edit by range and baseline diffing falls back to line/selector identity.
108
+ ...(doc.lossy ? {} : { sourceStart: rf.el.start, sourceEnd: rf.el.end }),
109
+ // SFC-source findings are preliminary (slot/dynamic content unseen); lossy-JSX findings
110
+ // are too (regex fallback — offsets into transformed HTML, may be fabricated from string
111
+ // literals) — flag both for AI/human verification. A rule may also mark an individual
112
+ // finding preliminary (e.g. a skip-link whose target lives in another component).
113
+ ...(doc.kind === "sfc" || doc.kind === "jsx-lossy" || rf.preliminary ? { preliminary: true } : {}),
114
+ // Capture findings are rendered ground truth (NOT preliminary): re-attribute them
115
+ // to the source component recorded in the capture's provenance.
116
+ ...(doc.capture ? { origin: { capture: doc.file, sourceFile: doc.capture.sourceFile, component: doc.capture.component } } : {}),
117
+ // Non-normative recommendation: the per-finding override (rf.advisory) wins over the
118
+ // rule-level default (ruleAdvisory); only stamp the flag when it is actually true, so
119
+ // a normative finding stays byte-identical (no `advisory: false` noise in the JSON).
120
+ ...((rf.advisory ?? ruleAdvisory) ? { advisory: true } : {}),
121
+ };
122
+ }