Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

## Table of Contents

- [What's new (2026-06-19) — WCAG 2.2 Audit](#whats-new-2026-06-19--wcag-22-audit)
- [What's new (2026-06-19) — Memory & Determinism](#whats-new-2026-06-19--memory--determinism)
- [What's new (2026-06-19) — Office I/O](#whats-new-2026-06-19--office-io)
- [What's new (2026-06-19) — Agent Toolkit](#whats-new-2026-06-19--agent-toolkit)
Expand Down Expand Up @@ -68,6 +69,13 @@

---

## What's new (2026-06-19) — WCAG 2.2 Audit

The accessibility audit gains a WCAG 2.2 / EN 301 549 success-criterion layer, full stack (facade, `AC_*`, MCP, Script Builder). Full reference: [`docs/source/Eng/doc/new_features/v16_features_doc.rst`](docs/source/Eng/doc/new_features/v16_features_doc.rst).

- **WCAG-tagged conformance audit** — `wcag_audit(level="AA")` (`AC_wcag_audit`, `ac_wcag_audit`): tags every defect with its WCAG success-criterion id/level/impact (4.1.2, 1.4.3, 1.4.10) and returns a conformance report with `by_criterion`/`by_impact` counts, filtered to A/AA/AAA — mappable to EN 301 549 for EAA compliance evidence.
- **Target Size (SC 2.5.8)** — `audit_target_size(elements, min_px=24)`: new WCAG 2.2 rule flagging interactive targets smaller than 24×24 px, computed from element bounds; `tag_issue` adds SC tagging to any existing audit issue.

## What's new (2026-06-19) — Memory & Determinism

Two pure-stdlib tools from the agent/QA research round, full stack (facade, `AC_*`, MCP, Script Builder). Full reference: [`docs/source/Eng/doc/new_features/v15_features_doc.rst`](docs/source/Eng/doc/new_features/v15_features_doc.rst).
Expand Down
8 changes: 8 additions & 0 deletions README/README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

## 目录

- [本次更新 (2026-06-19) — WCAG 2.2 审计](#本次更新-2026-06-19--wcag-22-审计)
- [本次更新 (2026-06-19) — 记忆与确定性](#本次更新-2026-06-19--记忆与确定性)
- [本次更新 (2026-06-19) — Office 读写](#本次更新-2026-06-19--office-读写)
- [本次更新 (2026-06-19) — Agent 工具组](#本次更新-2026-06-19--agent-工具组)
Expand Down Expand Up @@ -67,6 +68,13 @@

---

## 本次更新 (2026-06-19) — WCAG 2.2 审计

无障碍审计新增 WCAG 2.2 / EN 301 549 成功准则层,走完整五层(facade、`AC_*`、MCP、Script Builder)。完整参考:[`docs/source/Zh/doc/new_features/v16_features_doc.rst`](../docs/source/Zh/doc/new_features/v16_features_doc.rst)。

- **WCAG 标注合规审计** — `wcag_audit(level="AA")`(`AC_wcag_audit`、`ac_wcag_audit`):为每个缺陷标注 WCAG 成功准则编号/等级/影响(4.1.2、1.4.3、1.4.10),返回含 `by_criterion`/`by_impact` 计数的合规报告,按 A/AA/AAA 过滤——可对应 EN 301 549 作为 EAA 合规证据。
- **目标尺寸(SC 2.5.8)** — `audit_target_size(elements, min_px=24)`:WCAG 2.2 新规则,由元素 bounds 标记小于 24×24 px 的交互目标;`tag_issue` 可为任何既有审计问题加上 SC 标注。

## 本次更新 (2026-06-19) — 记忆与确定性

由 agent/QA 研究轮找出的两项纯标准库工具,走完整五层(facade、`AC_*`、MCP、Script Builder)。完整参考:[`docs/source/Zh/doc/new_features/v15_features_doc.rst`](../docs/source/Zh/doc/new_features/v15_features_doc.rst)。
Expand Down
8 changes: 8 additions & 0 deletions README/README_zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

## 目錄

- [本次更新 (2026-06-19) — WCAG 2.2 稽核](#本次更新-2026-06-19--wcag-22-稽核)
- [本次更新 (2026-06-19) — 記憶與決定性](#本次更新-2026-06-19--記憶與決定性)
- [本次更新 (2026-06-19) — Office 讀寫](#本次更新-2026-06-19--office-讀寫)
- [本次更新 (2026-06-19) — Agent 工具組](#本次更新-2026-06-19--agent-工具組)
Expand Down Expand Up @@ -67,6 +68,13 @@

---

## 本次更新 (2026-06-19) — WCAG 2.2 稽核

無障礙稽核新增 WCAG 2.2 / EN 301 549 成功準則層,走完整五層(facade、`AC_*`、MCP、Script Builder)。完整參考:[`docs/source/Zh/doc/new_features/v16_features_doc.rst`](../docs/source/Zh/doc/new_features/v16_features_doc.rst)。

- **WCAG 標註符合度稽核** — `wcag_audit(level="AA")`(`AC_wcag_audit`、`ac_wcag_audit`):為每個缺陷標註 WCAG 成功準則編號/等級/影響(4.1.2、1.4.3、1.4.10),回傳含 `by_criterion`/`by_impact` 計數的符合度報告,依 A/AA/AAA 過濾——可對應 EN 301 549 作為 EAA 合規證據。
- **目標尺寸(SC 2.5.8)** — `audit_target_size(elements, min_px=24)`:WCAG 2.2 新規則,由元素 bounds 標記小於 24×24 px 的互動目標;`tag_issue` 可為任何既有稽核問題加上 SC 標註。

## 本次更新 (2026-06-19) — 記憶與決定性

由 agent/QA 研究輪找出的兩項純標準庫工具,走完整五層(facade、`AC_*`、MCP、Script Builder)。完整參考:[`docs/source/Zh/doc/new_features/v15_features_doc.rst`](../docs/source/Zh/doc/new_features/v15_features_doc.rst)。
Expand Down
52 changes: 52 additions & 0 deletions docs/source/Eng/doc/new_features/v16_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
==================================================
New Features (2026-06-19) — WCAG 2.2 Audit Engine
==================================================

The accessibility audit gains a **WCAG 2.2 / EN 301 549 success-criterion
layer**: each defect is tagged with the WCAG criterion it violates (id +
name + conformance level + impact), and a new WCAG 2.2 rule — **Target
Size (Minimum), SC 2.5.8** — is computed from element bounds. The result is
a conformance-style report you can map to EN 301 549 for accessibility
compliance evidence (the European Accessibility Act is enforceable since
June 2025). Pure standard library; wired through the full stack.

.. contents::
:local:
:depth: 2


Conformance audit
================

::

from je_auto_control import wcag_audit

report = wcag_audit(level="AA") # live a11y tree
report = wcag_audit(elements=els, # or supply elements/colours/text
contrast_pairs=pairs, texts=ocr_texts, level="AA")

report["conformant"] # True when no findings at the requested level
report["by_criterion"] # {"1.4.3 Contrast (Minimum)": 2, ...}
report["findings"] # each tagged {sc, criterion, level, impact, ...}

Findings are filtered to the requested conformance ``level`` (``A`` /
``AA`` / ``AAA``). Mapped success criteria:

* **1.1.1 / 4.1.2** — interactive element with no accessible name.
* **1.4.3 Contrast (Minimum)** — foreground/background below the ratio.
* **1.4.10 Reflow** — clipped / truncated text.
* **2.5.8 Target Size (Minimum)** — *new in 2.2*: pointer targets smaller
than 24x24 px.


Target Size rule
===============

``audit_target_size(elements, min_px=24)`` flags interactive elements whose
bounds are smaller than ``min_px`` on either side (elements with unknown
size are skipped). ``tag_issue(issue)`` annotates any base ``AuditIssue``
with its success criterion, so existing audits gain SC tagging too.

Exposed as ``AC_wcag_audit`` / ``ac_wcag_audit`` (and ``wcag_audit`` /
``audit_target_size`` on the package facade).
1 change: 1 addition & 0 deletions docs/source/Eng/eng_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Comprehensive guides for all AutoControl features.
doc/new_features/v13_features_doc
doc/new_features/v14_features_doc
doc/new_features/v15_features_doc
doc/new_features/v16_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
47 changes: 47 additions & 0 deletions docs/source/Zh/doc/new_features/v16_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
==========================================
新功能 (2026-06-19) — WCAG 2.2 稽核引擎
==========================================

無障礙稽核新增 **WCAG 2.2 / EN 301 549 成功準則層**:每個缺陷都會標註其
違反的 WCAG 準則(編號 + 名稱 + 符合等級 + 影響程度),並新增一條 WCAG
2.2 規則——**目標尺寸(最小),SC 2.5.8**——由元素 bounds 計算。產出的
符合度報告可對應 EN 301 549,作為無障礙合規證據(歐洲無障礙法
EAA 自 2025 年 6 月起強制)。純標準庫;走完整五層。

.. contents::
:local:
:depth: 2


符合度稽核
==========

::

from je_auto_control import wcag_audit

report = wcag_audit(level="AA") # 即時 a11y 樹
report = wcag_audit(elements=els, # 或提供 元素/顏色/文字
contrast_pairs=pairs, texts=ocr_texts, level="AA")

report["conformant"] # 在要求等級下無任何發現時為 True
report["by_criterion"] # {"1.4.3 Contrast (Minimum)": 2, ...}
report["findings"] # 每筆標註 {sc, criterion, level, impact, ...}

發現會依要求的符合等級(``A`` / ``AA`` / ``AAA``)過濾。對應的成功準則:

* **1.1.1 / 4.1.2** — 互動元素沒有可存取名稱。
* **1.4.3 Contrast (Minimum)** — 前景/背景對比低於門檻。
* **1.4.10 Reflow** — 文字被裁切 / 截斷。
* **2.5.8 Target Size (Minimum)** — *2.2 新增*:指標目標小於 24x24 px。


目標尺寸規則
============

``audit_target_size(elements, min_px=24)`` 會標記 bounds 任一邊小於
``min_px`` 的互動元素(尺寸未知者略過)。``tag_issue(issue)`` 會把任何
基礎 ``AuditIssue`` 標註其成功準則,因此既有稽核也能取得 SC 標註。

對應 ``AC_wcag_audit`` / ``ac_wcag_audit``(以及 facade 上的 ``wcag_audit``
/ ``audit_target_size``)。
1 change: 1 addition & 0 deletions docs/source/Zh/zh_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ AutoControl 所有功能的完整使用指南。
doc/new_features/v13_features_doc
doc/new_features/v14_features_doc
doc/new_features/v15_features_doc
doc/new_features/v16_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
6 changes: 4 additions & 2 deletions je_auto_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@
# Accessibility / i18n audit (missing labels, WCAG contrast, truncation)
from je_auto_control.utils.a11y_audit import (
AuditIssue, AuditReport, audit_contrast, audit_missing_labels,
contrast_ratio, detect_truncation, run_audit,
audit_target_size, contrast_ratio, detect_truncation, run_audit,
wcag_audit,
)
# Mobile device matrix (parallel script execution across devices)
from je_auto_control.utils.device_matrix import (
Expand Down Expand Up @@ -676,7 +677,8 @@ def start_autocontrol_gui(*args, **kwargs):
"auto_quarantine_from_flakiness", "default_quarantine_store",
# Accessibility / i18n audit
"AuditIssue", "AuditReport", "audit_contrast", "audit_missing_labels",
"contrast_ratio", "detect_truncation", "run_audit",
"audit_target_size", "contrast_ratio", "detect_truncation", "run_audit",
"wcag_audit",
# Mobile device matrix
"DeviceResult", "MatrixReport", "run_on_devices",
# Media assertions
Expand Down
11 changes: 11 additions & 0 deletions je_auto_control/gui/script_builder/command_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,17 @@ def _add_misc_specs(specs: List[CommandSpec]) -> None:
_add_agent_specs(specs)
_add_office_specs(specs)
_add_memory_specs(specs)
specs.append(CommandSpec(
"AC_wcag_audit", "Accessibility", "WCAG 2.2 Conformance Audit",
fields=(
FieldSpec("app_name", FieldType.STRING, optional=True),
FieldSpec("level", FieldType.ENUM, choices=("A", "AA", "AAA"),
optional=True, default="AA"),
FieldSpec("min_target_px", FieldType.INT, optional=True,
default=24),
),
description="WCAG 2.2 audit: SC-tagged findings + Target Size 2.5.8.",
))


def _add_memory_specs(specs: List[CommandSpec]) -> None:
Expand Down
8 changes: 8 additions & 0 deletions je_auto_control/utils/a11y_audit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,24 @@
relative_luminance,
run_audit,
)
from je_auto_control.utils.a11y_audit.wcag import (
audit_target_size,
tag_issue,
wcag_audit,
)


__all__ = [
"AuditIssue",
"AuditReport",
"audit_contrast",
"audit_missing_labels",
"audit_target_size",
"contrast_ratio",
"detect_truncation",
"is_interactive",
"relative_luminance",
"run_audit",
"tag_issue",
"wcag_audit",
]
129 changes: 129 additions & 0 deletions je_auto_control/utils/a11y_audit/wcag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""WCAG 2.2 / EN 301 549 success-criterion tagging for a11y audits.

The base :mod:`je_auto_control.utils.a11y_audit` checks find *defects*; this
layer tags each defect with the WCAG **success criterion** it violates
(id + name + conformance level + impact) and adds a new WCAG 2.2 rule —
**Target Size (Minimum), SC 2.5.8** — computable from element bounds. The
result is a conformance-style report you can map to EN 301 549 for
accessibility compliance evidence (EAA enforceable since June 2025).

Pure standard library; imports no ``PySide6``. Reuses the base audit's
pure functions, so it is fully unit-testable with supplied elements.
"""
from typing import Any, Dict, Iterable, List, Optional

from je_auto_control.utils.a11y_audit.audit import (
AuditIssue, SEVERITY_ERROR, SEVERITY_WARNING, WCAG_AA_NORMAL,
audit_contrast, audit_missing_labels, detect_truncation, is_interactive,
)

# kind -> (SC id, criterion name, conformance level)
_SC_BY_KIND = {
"missing_label": ("4.1.2", "Name, Role, Value", "A"),
"contrast": ("1.4.3", "Contrast (Minimum)", "AA"),
"truncation": ("1.4.10", "Reflow", "AA"),
"target_size": ("2.5.8", "Target Size (Minimum)", "AA"),
}
_IMPACT_BY_SEVERITY = {SEVERITY_ERROR: "serious", SEVERITY_WARNING: "moderate"}
_LEVEL_ORDER = {"A": 1, "AA": 2, "AAA": 3}
_MIN_TARGET_PX = 24


def audit_target_size(elements: Iterable[Any],
min_px: int = _MIN_TARGET_PX) -> List[AuditIssue]:
"""Flag interactive elements smaller than ``min_px`` on either side.

WCAG 2.2 SC 2.5.8 (Target Size, Minimum) — pointer targets should be at
least 24x24 CSS px. Elements with unknown (zero) size are skipped.
"""
issues: List[AuditIssue] = []
for element in elements:
role = getattr(element, "role", "") or ""
bounds = list(getattr(element, "bounds", []) or [])
if not is_interactive(role) or len(bounds) < 4:
continue
width, height = int(bounds[2]), int(bounds[3])
if width <= 0 or height <= 0 or min(width, height) >= int(min_px):
continue
issues.append(AuditIssue(
kind="target_size", severity=SEVERITY_WARNING,
message=f"target {width}x{height}px below {min_px}px minimum",
target=role,
detail={"width": width, "height": height, "min_px": int(min_px)}))
return issues


def tag_issue(issue: AuditIssue) -> Dict[str, Any]:
"""Return an issue annotated with its WCAG success criterion."""
sc, criterion, level = _SC_BY_KIND.get(issue.kind, ("", "", ""))
return {
"sc": sc, "criterion": criterion, "level": level,
"impact": _IMPACT_BY_SEVERITY.get(issue.severity, "minor"),
"kind": issue.kind, "severity": issue.severity,
"message": issue.message, "target": issue.target,
"detail": issue.detail,
}


def _level_ok(found_level: str, target_level: str) -> bool:
return (_LEVEL_ORDER.get(found_level, 99)
<= _LEVEL_ORDER.get(target_level, 2))


def _fetch_elements(app_name: Optional[str], elements: Optional[Iterable[Any]],
max_results: int) -> List[Any]:
if elements is not None:
return list(elements)
from je_auto_control.utils.accessibility.accessibility_api import (
list_accessibility_elements)
return list_accessibility_elements(app_name=app_name,
max_results=int(max_results))


def _collect_issues(app_name: Optional[str], elements: Optional[Iterable[Any]],
contrast_pairs: Optional[Iterable[Dict[str, Any]]],
texts: Optional[Iterable[str]], min_ratio: float,
min_target_px: int, max_results: int) -> List[AuditIssue]:
els = _fetch_elements(app_name, elements, max_results)
issues = list(audit_missing_labels(els))
issues.extend(audit_target_size(els, min_target_px))
if contrast_pairs is not None:
issues.extend(audit_contrast(contrast_pairs, min_ratio))
if texts is not None:
issues.extend(detect_truncation(texts))
return issues


def _summary(findings: List[Dict[str, Any]], level: str) -> Dict[str, Any]:
by_criterion: Dict[str, int] = {}
by_impact: Dict[str, int] = {}
for finding in findings:
key = f"{finding['sc']} {finding['criterion']}".strip()
by_criterion[key] = by_criterion.get(key, 0) + 1
by_impact[finding["impact"]] = by_impact.get(finding["impact"], 0) + 1
return {
"level": level, "total": len(findings),
"conformant": len(findings) == 0,
"by_criterion": by_criterion, "by_impact": by_impact,
"findings": findings,
}


def wcag_audit(*, app_name: Optional[str] = None,
elements: Optional[Iterable[Any]] = None,
contrast_pairs: Optional[Iterable[Dict[str, Any]]] = None,
texts: Optional[Iterable[str]] = None,
min_ratio: float = WCAG_AA_NORMAL,
min_target_px: int = _MIN_TARGET_PX,
level: str = "AA", max_results: int = 500) -> Dict[str, Any]:
"""Run WCAG-tagged audits and return a conformance report.

Findings are tagged with WCAG SC id / level / impact and filtered to the
requested conformance ``level`` (A, AA, or AAA). When ``elements`` is
omitted the live accessibility tree is queried.
"""
issues = _collect_issues(app_name, elements, contrast_pairs, texts,
min_ratio, min_target_px, max_results)
findings = [tag_issue(issue) for issue in issues]
findings = [f for f in findings if _level_ok(f["level"], level)]
return _summary(findings, level)
Loading
Loading