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.
- package/LICENSE +21 -0
- package/README.md +223 -0
- package/docker/Dockerfile +7 -0
- package/docker/package.json +9 -0
- package/docker/runner.mjs +23 -0
- package/package.json +73 -0
- package/scripts/bench.mjs +151 -0
- package/scripts/build-act-corpus.mjs +143 -0
- package/scripts/build-adjudication.mjs +503 -0
- package/scripts/build-pack-rgaa.mjs +397 -0
- package/scripts/build-standards.mjs +548 -0
- package/scripts/copy-bundle.mjs +39 -0
- package/scripts/import-pack.mjs +107 -0
- package/scripts/sync-engine.mjs +84 -0
- package/scripts/sync-version.mjs +43 -0
- package/scripts/ultra11y.mjs +52739 -0
- package/scripts/vendor/act-testcases.json +12297 -0
- package/scripts/vendor/rgaa/SOURCE.json +8 -0
- package/scripts/vendor/rgaa/criteres.json +4144 -0
- package/scripts/vendor/rgaa/glossaire.json +480 -0
- package/scripts/vendor/wcag-2.2-fr.json +82 -0
- package/scripts/vendor/wcag-2.2-sc-universe.json +950 -0
- package/scripts/vendor/wcag-2.2-sc.json +574 -0
- package/scripts/verify-rgaa-source.mjs +126 -0
- package/scripts/verify-skill-bundle.mjs +91 -0
- package/skills/review-a11y/SKILL.md +123 -0
- package/skills/review-a11y/scripts/ultra11y.mjs +52739 -0
- package/skills/ultra11y/SKILL.md +292 -0
- package/skills/ultra11y/references/act.md +163 -0
- package/skills/ultra11y/references/adjudication.md +599 -0
- package/skills/ultra11y/references/audit.md +100 -0
- package/skills/ultra11y/references/authoring.md +37 -0
- package/skills/ultra11y/references/automation.md +113 -0
- package/skills/ultra11y/references/correction.md +41 -0
- package/skills/ultra11y/references/criteria.md +129 -0
- package/skills/ultra11y/references/cross-file.md +82 -0
- package/skills/ultra11y/references/dynamic.md +152 -0
- package/skills/ultra11y/references/false-positives.md +81 -0
- package/skills/ultra11y/references/fix.md +46 -0
- package/skills/ultra11y/references/focus-and-logic.md +64 -0
- package/skills/ultra11y/references/forbidden-patterns.md +61 -0
- package/skills/ultra11y/references/forms-and-errors.md +147 -0
- package/skills/ultra11y/references/guidance.md +55 -0
- package/skills/ultra11y/references/judgment.md +90 -0
- package/skills/ultra11y/references/media-and-motion.md +116 -0
- package/skills/ultra11y/references/methodology.md +72 -0
- package/skills/ultra11y/references/naming.md +117 -0
- package/skills/ultra11y/references/orchestration.md +44 -0
- package/skills/ultra11y/references/packs.md +193 -0
- package/skills/ultra11y/references/prd.md +104 -0
- package/skills/ultra11y/references/rendered.md +110 -0
- package/skills/ultra11y/references/scale.md +49 -0
- package/skills/ultra11y/references/standards.md +97 -0
- package/skills/ultra11y/references/structure.md +138 -0
- package/skills/ultra11y/references/verify.md +88 -0
- package/skills/ultra11y/references/widgets.md +106 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +52739 -0
- package/src/act.ts +441 -0
- package/src/adjudicate.ts +630 -0
- package/src/aria.ts +230 -0
- package/src/audit.ts +396 -0
- package/src/auditor.ts +399 -0
- package/src/autofill.ts +102 -0
- package/src/axe-map.ts +294 -0
- package/src/baseline.ts +75 -0
- package/src/capture.ts +182 -0
- package/src/check.ts +243 -0
- package/src/cli.ts +1927 -0
- package/src/color.ts +102 -0
- package/src/config.ts +182 -0
- package/src/crawl.ts +75 -0
- package/src/criteria.ts +183 -0
- package/src/data/adjudication.json +770 -0
- package/src/data/guidance/rgaa.json +2395 -0
- package/src/data/standards/rgaa.glossary.json +478 -0
- package/src/data/standards/rgaa.json +3454 -0
- package/src/data/wcag-universe.json +530 -0
- package/src/data/wcag.json +1178 -0
- package/src/discover.ts +203 -0
- package/src/fix/codemods.ts +76 -0
- package/src/fix/diff.ts +64 -0
- package/src/fix/edits.ts +126 -0
- package/src/fix.ts +215 -0
- package/src/gh.ts +214 -0
- package/src/glob.ts +206 -0
- package/src/graph/build.ts +114 -0
- package/src/graph/engine-resolve.ts +0 -0
- package/src/graph/graph.ts +126 -0
- package/src/graph/imports.ts +359 -0
- package/src/graph/resolve.ts +73 -0
- package/src/graph/tsconfig.ts +113 -0
- package/src/grounding.ts +119 -0
- package/src/guidance/index.ts +45 -0
- package/src/guidance/types.ts +36 -0
- package/src/init.ts +113 -0
- package/src/mcp/handlers.ts +388 -0
- package/src/mcp/http.ts +260 -0
- package/src/mcp/prompts.ts +183 -0
- package/src/mcp/protocol.ts +187 -0
- package/src/mcp/resources.ts +136 -0
- package/src/mcp/server.ts +228 -0
- package/src/mcp/stdio.ts +119 -0
- package/src/mcp/tools.ts +347 -0
- package/src/messages.ts +1011 -0
- package/src/name.ts +174 -0
- package/src/orchestrate-templates.ts +231 -0
- package/src/orchestrate.ts +186 -0
- package/src/output.ts +108 -0
- package/src/pack.ts +187 -0
- package/src/parse/html.ts +258 -0
- package/src/parse/jsx-ast.ts +67 -0
- package/src/parse/jsx-bridge.ts +247 -0
- package/src/parse/jsx.ts +19 -0
- package/src/parse/source.ts +75 -0
- package/src/prd.ts +406 -0
- package/src/project-lock.ts +42 -0
- package/src/render.ts +307 -0
- package/src/report.ts +399 -0
- package/src/rules/colors.ts +125 -0
- package/src/rules/cross-registry.ts +264 -0
- package/src/rules/cross-rule.ts +83 -0
- package/src/rules/forms.ts +590 -0
- package/src/rules/frames.ts +33 -0
- package/src/rules/headings.ts +227 -0
- package/src/rules/images.ts +200 -0
- package/src/rules/links.ts +193 -0
- package/src/rules/mandatory.ts +173 -0
- package/src/rules/multimedia.ts +77 -0
- package/src/rules/navigation.ts +190 -0
- package/src/rules/presentation.ts +75 -0
- package/src/rules/registry.ts +57 -0
- package/src/rules/rule.ts +122 -0
- package/src/rules/scripts-aria.ts +636 -0
- package/src/rules/tables.ts +264 -0
- package/src/rules/timing.ts +55 -0
- package/src/sample.ts +176 -0
- package/src/scan-local.ts +866 -0
- package/src/scan.ts +538 -0
- package/src/standards/derive.ts +195 -0
- package/src/standards/index.ts +31 -0
- package/src/standards/pack-rules.ts +111 -0
- package/src/standards/pack.ts +81 -0
- package/src/standards/registry.ts +100 -0
- package/src/standards/types.ts +202 -0
- package/src/standards/validate.ts +544 -0
- package/src/standards/vocabulary.ts +75 -0
- package/src/types.ts +355 -0
- package/src/util.ts +32 -0
- package/src/vendor/codeindex-engine.d.mts +1009 -0
- package/src/vendor/codeindex-engine.mjs +13701 -0
- package/src/vendor/engine.meta.json +9 -0
- package/src/verify.ts +275 -0
- package/src/wcag.ts +128 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Install-bundle gate: prove the repo is shaped so that `npx skills add
|
|
3
|
+
// maxgfr/<name>` installs WORKING skills — engine + references included, not
|
|
4
|
+
// just a lone SKILL.md.
|
|
5
|
+
//
|
|
6
|
+
// The `skills` CLI (skills.sh) early-returns the moment it sees a SKILL.md at
|
|
7
|
+
// the repository ROOT and then installs that file ALONE — the sibling
|
|
8
|
+
// scripts/ and references/ are dropped. A skill is only bundled whole when its
|
|
9
|
+
// SKILL.md lives in a SUBDIRECTORY (skills/<name>/). This script asserts that
|
|
10
|
+
// shape for EVERY skill under skills/ and that each embedded engine is
|
|
11
|
+
// byte-identical to the tested bundle.
|
|
12
|
+
//
|
|
13
|
+
// Run by CI and by `pnpm run verify:bundle`. Pure Node, no deps, no network.
|
|
14
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
|
|
18
|
+
// Claude Code matches skill descriptions at <=1024 chars; 1000 leaves a safety
|
|
19
|
+
// margin so a future edit can't silently cross the cap.
|
|
20
|
+
const DESC_MAX = 1000;
|
|
21
|
+
|
|
22
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
23
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
24
|
+
const engine = `scripts/${pkg.name}.mjs`;
|
|
25
|
+
const errors = [];
|
|
26
|
+
const ok = (m) => console.log(` ok ${m}`);
|
|
27
|
+
const bad = (m) => {
|
|
28
|
+
errors.push(m);
|
|
29
|
+
console.log(` FAIL ${m}`);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// 1. No SKILL.md at the repo root (would make `skills add` install it alone).
|
|
33
|
+
existsSync(join(root, "SKILL.md"))
|
|
34
|
+
? bad("a SKILL.md exists at the repo ROOT — `skills add` would install it alone, dropping the engine. Move it under skills/<name>/SKILL.md")
|
|
35
|
+
: ok("no root SKILL.md");
|
|
36
|
+
|
|
37
|
+
const rootEngine = join(root, engine);
|
|
38
|
+
if (!existsSync(rootEngine)) bad(`missing ${engine} at repo root — run \`pnpm run build\``);
|
|
39
|
+
|
|
40
|
+
const skillNames = readdirSync(join(root, "skills")).filter((d) => statSync(join(root, "skills", d)).isDirectory());
|
|
41
|
+
if (skillNames.length === 0) bad("skills/ contains no skill directory");
|
|
42
|
+
|
|
43
|
+
for (const name of skillNames) {
|
|
44
|
+
const skillDir = join(root, "skills", name);
|
|
45
|
+
console.log(`skill ${name}:`);
|
|
46
|
+
|
|
47
|
+
// 2. The packaged SKILL.md exists with valid, installable frontmatter.
|
|
48
|
+
const skillMd = join(skillDir, "SKILL.md");
|
|
49
|
+
if (!existsSync(skillMd)) {
|
|
50
|
+
bad(`missing skills/${name}/SKILL.md — the skill package has no SKILL.md`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const raw = readFileSync(skillMd, "utf8");
|
|
54
|
+
const fm = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
55
|
+
if (!fm) bad(`skills/${name}/SKILL.md has no frontmatter block`);
|
|
56
|
+
else {
|
|
57
|
+
ok("packaged SKILL.md present with frontmatter");
|
|
58
|
+
const nameLine = fm[1].match(/^name:\s*(.+)$/m)?.[1]?.trim();
|
|
59
|
+
nameLine === name ? ok(`frontmatter name "${name}" matches its directory`) : bad(`frontmatter name "${nameLine}" != skill directory "${name}"`);
|
|
60
|
+
const desc = fm[1].match(/^description:\s*(.+)$/m)?.[1]?.trim();
|
|
61
|
+
if (!desc) bad(`skills/${name}: frontmatter has no description`);
|
|
62
|
+
else {
|
|
63
|
+
const len = desc.replace(/^["']|["']$/g, "").length;
|
|
64
|
+
len <= DESC_MAX ? ok(`description ${len} chars (<= ${DESC_MAX} safety cap)`) : bad(`skills/${name}: description ${len} chars exceeds the ${DESC_MAX}-char safety cap (matcher limit is 1024)`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 3. Every references/*.md mentioned exists, and every file is mentioned.
|
|
69
|
+
const refsDir = join(skillDir, "references");
|
|
70
|
+
if (existsSync(refsDir)) {
|
|
71
|
+
const mentioned = new Set(raw.match(/references\/[a-z0-9-]+\.md/g) ?? []);
|
|
72
|
+
for (const ref of mentioned) existsSync(join(skillDir, ref)) ? ok(`mentioned ${ref} exists`) : bad(`skills/${name}: ${ref} is mentioned in SKILL.md but missing from the package`);
|
|
73
|
+
for (const f of readdirSync(refsDir).filter((f) => f.endsWith(".md"))) raw.includes(`references/${f}`) ? null : bad(`skills/${name}: references/${f} exists but SKILL.md never mentions it`);
|
|
74
|
+
ok(`references/ present (${readdirSync(refsDir).filter((f) => f.endsWith(".md")).length} playbooks)`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 4. The embedded engine is byte-identical to the committed root bundle.
|
|
78
|
+
const pkgEngine = join(skillDir, engine);
|
|
79
|
+
if (!existsSync(rootEngine)) continue; // already reported above
|
|
80
|
+
if (!existsSync(pkgEngine)) bad(`missing skills/${name}/${engine} — run \`node scripts/copy-bundle.mjs\``);
|
|
81
|
+
else
|
|
82
|
+
readFileSync(rootEngine).equals(readFileSync(pkgEngine))
|
|
83
|
+
? ok(`embedded engine skills/${name}/${engine} is byte-identical to ${engine}`)
|
|
84
|
+
: bad(`skills/${name}/${engine} differs from ${engine} — run \`node scripts/copy-bundle.mjs\` and commit`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (errors.length) {
|
|
88
|
+
console.error(`\nverify-skill-bundle: ${errors.length} problem(s) — a published skill would not install correctly.`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
console.log(`\nverify-skill-bundle: ok — ${skillNames.map((n) => `skills/${n}/`).join(", ")} install as complete skills.`);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review-a11y
|
|
3
|
+
description: "Use to REVIEW code changes for accessibility — '/review-a11y', 'review my changes/PR/branch for a11y', 'accessibility review of this diff'. Scopes to exactly the code under change (staged files, working diff, or branch vs merge-base), runs the bundled install-free ultra11y engine (node scripts/ultra11y.mjs, no keys; static WCAG 2.2 AA checks tied to success criteria, cross-file JSX/TSX via --graph), then adds YOUR judgment: refute false positives (SFC/library-source findings are preliminary), decide alt relevance and link purpose, name the rendering criteria (contrast, focus, zoom) as residual risks — never invent a non-conformity. Output: a severity-ranked review (blocking → major → minor) with file:line, concrete fixes, residual risks and a one-line verdict, in the conversation's language (technical tokens stay in English). Companion of the ultra11y skill (full audits, reports, PRD, RGAA packs). Triggers: 'review a11y', 'a11y review', 'accessibility review'."
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
version: 2.22.0
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# review-a11y — accessibility review of changed code
|
|
10
|
+
|
|
11
|
+
The review counterpart of the `ultra11y` skill: where `ultra11y` produces full audits and
|
|
12
|
+
compliance deliverables, **this skill reviews exactly the code under change** — a diff, a
|
|
13
|
+
branch, a PR — and reports like a code reviewer. Same division of labour: the
|
|
14
|
+
deterministic, install-free engine bundled with this skill does the *mechanical* work
|
|
15
|
+
(machine-checkable WCAG 2.2 AA non-conformities, each tied to a success criterion, cited
|
|
16
|
+
`file:line`), and **the AI agent** *adjudicates the judgment* — refute false positives, decide
|
|
17
|
+
alt relevance/link purpose/focus logic statically from the code, and route what needs a rendered
|
|
18
|
+
DOM to `scan`. A human is at most optional oversight.
|
|
19
|
+
|
|
20
|
+
**Engine resolution:** every command below is `node scripts/ultra11y.mjs …` where
|
|
21
|
+
`scripts/ultra11y.mjs` lives **next to this SKILL.md** (the engine ships inside the skill;
|
|
22
|
+
no install, no key). Run it with the reviewed project as the working directory. In the
|
|
23
|
+
ultra11y repository itself, the repo root's `scripts/ultra11y.mjs` is the same file.
|
|
24
|
+
|
|
25
|
+
> **Core rules** (shared with `ultra11y`):
|
|
26
|
+
> 1. **Never invent a non-conformity**: report only what the engine found or what you can
|
|
27
|
+
> see and cite in the code under review.
|
|
28
|
+
> 2. **Review the change, not the world**: findings outside the reviewed scope are noted in
|
|
29
|
+
> one line at most ("pre-existing, out of scope"), never mixed into the verdict.
|
|
30
|
+
> 3. **Residual is explicit, never silently conforming**: the *rendering* criteria (contrast,
|
|
31
|
+
> focus visibility, zoom/reflow, content-on-hover) are named as residual risks for the changed
|
|
32
|
+
> components — never asserted from source, resolved in the rendered page (`scan`). The
|
|
33
|
+
> *judgment* criteria (alt relevance, link purpose, reading order) the AI agent adjudicates
|
|
34
|
+
> from the code itself, recording a reason — no status without a justified verdict.
|
|
35
|
+
> 4. **Language**: write the review in the conversation's language, but technical tokens
|
|
36
|
+
> stay in English even in French prose — `aria-live` stays `aria-live` (never
|
|
37
|
+
> « région live »), same for `tabindex`, `alt`, `role="alert"`, landmark role names.
|
|
38
|
+
|
|
39
|
+
## 1. Scope — review what changed
|
|
40
|
+
|
|
41
|
+
Pick the FIRST that applies (ask only when genuinely ambiguous):
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
node scripts/ultra11y.mjs audit <files the user named> --jsx --json # explicit files
|
|
45
|
+
node scripts/ultra11y.mjs audit --staged --graph --json # staged snapshot (pre-commit review)
|
|
46
|
+
node scripts/ultra11y.mjs audit --changed --graph --json # working diff
|
|
47
|
+
node scripts/ultra11y.mjs audit --since origin/main --graph --json # the whole branch (PR review) — use the repo's default branch
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- Always pass `--graph` for JSX/TSX (real AST + cross-file rules: icon-only component used
|
|
51
|
+
without a name, anchor target defined in another file…); it also suppresses a class of
|
|
52
|
+
single-file false positives.
|
|
53
|
+
- Pass `--lang` matching the conversation so the engine's message/remediation strings are
|
|
54
|
+
in the review's language (unset, the CLI auto-detects — a fallback, not a substitute).
|
|
55
|
+
- Whole-repo review is an **audit**, not a review — offer the `ultra11y` skill instead.
|
|
56
|
+
- Test/story markup (`*.test.*`, `*.stories.*`, `__tests__/`) is excluded by default; name
|
|
57
|
+
such a file explicitly to include it, or pass `--no-default-excludes`.
|
|
58
|
+
|
|
59
|
+
## 2. Judge — the engine's findings are candidates, not verdicts
|
|
60
|
+
|
|
61
|
+
For each finding (`--json` gives `criteria`, `severity`, `file:line`, `message`,
|
|
62
|
+
`remediation`):
|
|
63
|
+
|
|
64
|
+
- **Confirm the occurrence**: open the cited line; the offending element must really be
|
|
65
|
+
there in the change under review.
|
|
66
|
+
- **`preliminary: true` findings** (`.vue`/`.svelte`/`.astro` source, library components):
|
|
67
|
+
the template parser cannot see slots, conditionals or provide/inject wiring. Confirm
|
|
68
|
+
against the rendered DOM if captures exist (`.ultra11y/captures`, from the `ultra11y`
|
|
69
|
+
skill's `render --setup` harvester), otherwise report them as **provisional** with that
|
|
70
|
+
caveat — or refute them with evidence from the code.
|
|
71
|
+
- **Library-rendered JSX (DSFR, MUI…)**: a clean source review can still ship broken HTML
|
|
72
|
+
(false negatives) — say so explicitly when the change touches such components, and
|
|
73
|
+
recommend a rendered-DOM check (`scan`, or the `ultra11y` capture pipeline).
|
|
74
|
+
- **The judgment criteria are the AI agent's to adjudicate**: alt text relevance, link purpose
|
|
75
|
+
in context, visible label vs accessible name, keyboard/focus logic of the changed component
|
|
76
|
+
(read the full component source, not just the flagged line), recording a reason for each call.
|
|
77
|
+
- Refuting a finding requires **cited evidence** (the code that disproves it); silently
|
|
78
|
+
dropping findings is forbidden.
|
|
79
|
+
|
|
80
|
+
## 3. Report — a review, not a compliance document
|
|
81
|
+
|
|
82
|
+
Render in the conversation's language (Core rule 4), grouped by severity:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
## Accessibility review — <scope, e.g. branch feat/x vs main, 12 files>
|
|
86
|
+
|
|
87
|
+
### 🔴 Blocking
|
|
88
|
+
- `src/components/Form.tsx:42` — **3.3.2 Labels or Instructions** — input has no
|
|
89
|
+
label and placeholder is its only hint → associate a real <label for>, keep the
|
|
90
|
+
placeholder as an example only.
|
|
91
|
+
|
|
92
|
+
### 🟠 Major / 🟡 Minor
|
|
93
|
+
- …same shape…
|
|
94
|
+
|
|
95
|
+
### Residual risks (rendering-dependent — resolve in the rendered page via scan)
|
|
96
|
+
- contrast of the new button variants (1.4.3), focus visibility on the custom menu (2.4.7)
|
|
97
|
+
|
|
98
|
+
### Verdict
|
|
99
|
+
1 blocking, 2 major to fix before merge; 2 provisional SFC findings to confirm on the
|
|
100
|
+
rendered DOM; residual risks listed above.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- Keep the engine's `remediation` text as the base of each fix — it is already localized
|
|
104
|
+
and criterion-grounded.
|
|
105
|
+
- Pre-existing issues spotted in passing go in ONE final line, out of the verdict.
|
|
106
|
+
|
|
107
|
+
## 4. After the review (opt-in)
|
|
108
|
+
|
|
109
|
+
- **Safe mechanical fixes**: `fix --staged --write --safe` (auto-applies the safe codemods
|
|
110
|
+
to staged files and re-stages them) or `node scripts/ultra11y.mjs fix <files> --write`;
|
|
111
|
+
dry-run without `--write`.
|
|
112
|
+
- **Formal deliverables** (dated report, auditor conformance blocks, PRD backlog, GitHub
|
|
113
|
+
issues, RGAA/country-standard vocabulary): that is the `ultra11y` skill — same engine,
|
|
114
|
+
same findings (`report`, `prd`, `check`/`verify` gates).
|
|
115
|
+
- **Make it automatic**: the `ultra11y` skill's `init --hook` installs a pre-commit gate
|
|
116
|
+
running this same staged review mechanically.
|
|
117
|
+
|
|
118
|
+
## Do not
|
|
119
|
+
|
|
120
|
+
- Invent a non-conformity the engine did not find and you cannot cite.
|
|
121
|
+
- Assert contrast/focus/zoom from source — they are residual risks until rendered (resolve via `scan`).
|
|
122
|
+
- Mix pre-existing repo issues into the verdict on the change.
|
|
123
|
+
- Translate technical tokens in French prose (`aria-live`, never « région live »).
|