iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
Modern Web

前端不寫 Python,照樣 ship 一把網頁無障礙 CLI系列 第 3

Day 03:一個晚上,AAA 自評覆蓋率從 12/20 跑到 20/20

  • 分享至 

  • xImage
  •  

Day 03 · W1 · AI 線 · 難度 ★★★☆☆

本系列由 AI 協作撰寫。 內容、技術判斷、程式碼由 light-design 數位顧問團隊與 Claude 共同產出,最終由作者驗證後 publish。完整協作模式與把關方式見 Day 01

Day 1 說「AAA 自評的 20 題我們全部做出了程式判斷」,Day 2 說「工具有規則不等於抓得到」。

今天回答中間那段:那些規則是怎麼長出來的。

先看一段真的 git log。這是 2026 年 4 月 30 日晚上,同一台電腦上發生的事:

18:43  chore: initial commit
20:39  feat(rules): automate 5 AAA self-eval rules (Q2/Q6/Q7/Q8/Q13)
       Coverage of MODA AAA self-eval form: 12/20 → 17/20
21:49  feat(rules): automate final 3 AAA self-eval rules (Q3/Q19/Q20)
       Coverage of MODA AAA self-eval form: 17/20 → 20/20 (100%)
22:07  feat(form_probe): click likely modal triggers when no usable form found
23:00  feat(form_probe): two-stage triggers + Q19 modal-aware
23:07  fix(GN1210101E): exempt WAI-ARIA roving-tabindex roles
23:48  docs(README): document AAA self-eval coverage with per-rule mechanism

5 小時 5 分鐘,8 個 commit,自評表覆蓋率從 12/20 到 20/20。

我不寫 Python。這段時間我做的事是讀規範、判斷規則對不對、跑掃描、看結果、決定哪條要修。程式碼是 AI 寫的。

那 5 小時之前,已經有 121 條規則

先講清楚一件事,免得這篇讀起來像「一個晚上生出一套工具」。

18:43 那個 initial commit 推上去的時候,專案裡已經有 121 條規則檔。那些是更早之前用同一套流程長出來的,只是還沒進版控。

那晚在做的是一件範圍明確的事:把 AAA 自評表那 20 題補完。 送件在即,自評表上還有 8 題沒有對應的程式檢查,得靠人一頁一頁看。

四月三十日晚間的工作時間軸:18 點 43 分初始提交時已有 121 條規則、自評覆蓋 12/20;20 點 39 分新增 5 條規則使覆蓋達 17/20;21 點 49 分再補 3 條達到 20/20;隨後兩次強化表單探測;23 點 07 分修正一條偽陽性規則;23 點 48 分補文件

那晚的實際節奏。階梯是自評表的覆蓋數,橫軸是時間。注意 23:07 那一格是往回修,不是往前加。

所以正確的說法是:這套流程已經在轉了,那晚只是轉得比較快而已。 因為目標具體(補完 20 題)、驗收明確(自評表填得出來就算過),迴圈就跑得順。目標模糊的時候,同一套流程一樣會卡。

三個齒輪,缺一個就卡住

把這件事拆開,是一個三段迴圈:

  1. 讀規範 — 規範是文件,AI 讀得懂
  2. 生規則 — 規則是一個 Python 檔,AI 寫得出來
  3. 自驗 — 跑一次掃描,AI 看得懂輸出,知道自己哪裡錯

聽起來理所當然。但關鍵在於:這三段都必須是機器讀得懂的形式,整組齒輪才會轉。

規範如果只存在於某個人腦袋裡,第一段就斷。規則如果散在一個三千行的大檔案裡,AI 每次都要重讀全部才敢改,第二段就慢到不能用。掃描結果如果只印在畫面上、沒有結構化輸出,第三段就等於沒有。

Agent 迴圈的四個環節:規範文件經 AI 產出單一規則檔,檔案放進資料夾後被自動掛載,執行掃描產生結構化報告,發現偽陽性或漏抓後回頭修改規則,形成封閉迴圈

迴圈要能封閉,四個接點都得是機器讀得懂的東西。任何一段需要人手動搬運,整圈就停在那裡。

下面三節,一節一個齒輪。

齒輪一:讀規範,比想像中順

MODA 把規範公開成文件,每一條檢核項目都有固定欄位:檢核碼、對應的 WCAG 成功準則、等級(A/AA/AAA)、還有一句中文描述。

這四個欄位剛好就是規則檔開頭那個 RuleMeta 要填的東西。等於規範本身已經幫你把 metadata 結構化了,這一段幾乎是機械搬運。

真正需要人介入的是第五個欄位:這條規則實際上要檢查什麼。

規範寫的是「應該達成什麼」,程式要寫的是「怎麼判斷有沒有達成」。規範說「使用語意組件標記強調文字」,它不會告訴你「超過 5 個 <b> 且沒有任何 <strong> 就算違規」。那個 5 是我們定的閾值,可以被質疑,而且改一次整站結果就變。

這中間沒有機械對應,是整套流程唯一無法外包的部分。

齒輪二:生規則,關鍵在檔案怎麼放

先看一條真的規則。這是專案裡最短的一個檔,完整內容 23 行:

"""HM1130105E rule."""
from __future__ import annotations
from ....models import Level
from ...base import Rule, RuleMeta, register


@register
class SemanticEmphasisMarkup(Rule):
    """HM1130105/106E — 強調文字應使用語意元件,不只是 b/i。"""

    meta = RuleMeta(rule_id="HM1130105E", guideline="1.3.1", level=Level.A,
        desc="使用語意組件來標記強調的文字或特殊文字",
        source="extension")

    def _check(self, soup, report, *, html, url, ctx) -> None:
        b_count = len(soup.find_all(["b", "i"]))
        sem_count = len(soup.find_all(["strong", "em"]))
        if b_count >= 5 and sem_count == 0:
            report.add(self._issue(
                message=f"頁面使用 {b_count} 個 <b>/<i> 但無 <strong>/<em> 語意元件。",
                status="info"))

檔案路徑是 rules/codes/structure/HM1130105E.py一個檢核碼,一個檔,檔名就是檢核碼。

新增規則不需要去任何清單裡登記。套件啟動時會把 codes/ 底下每個資料夾裡的每個檔都 import 一遍,檔案被 import 時,@register 這個裝飾器就把規則登記進去。所以加一條規則 = 在資料夾裡丟一個檔。

這個設計有它的代價,而且我們踩過。

這個專案的巢狀結構是 rules/codes/<主題>/<檔>.py,所以檔案開頭那行 from ....models import Level 是四個點。寫成三個點,這個檔會 import 失敗,自動掛載直接跳過它,然後什麼事都不會發生。

沒有紅字,沒有警告。掃描照跑,規則總數少一條。除非你剛好記得應該有幾條,否則不會察覺。

對掃描器來說,一個 import 錯誤的檔案跟一個不存在的檔案,是同一件事。

這種失敗模式跟 Day 2 那 7 條「規則存在卻沒抓到」剛好互補:一個是規則在但判錯,一個是規則根本沒被載入。兩種都不會有東西跳出來提醒你。後來我們把規則總數當成一個要盯的數字,就是為了這個。

所以「一個檔一條規則」不只是整潔問題,是用一種可以被自動化的失敗模式,換掉另一種更難查的失敗模式。這個取捨值得整篇講,第 10 天會回來。

齒輪三:自驗,也是這個迴圈唯一會自己修正的地方

回頭看時間軸裡的 23:07 那筆:

23:07  fix(GN1210101E): exempt WAI-ARIA roving-tabindex roles

commit 訊息裡寫得很清楚:這條規則把任何帶 tabindex="-1" 的可聚焦元素都標為鍵盤無法到達,而這對複合元件是錯的。在 WAI-ARIA 的 roving tabindex 模式裡,一組元件中只有一個持有 tabindex="0"(讓你 Tab 進去),其他都是 tabindex="-1"(用方向鍵在其中移動)。

發現方式也寫在同一則訊息裡:由一個真實的偽陽性發現,自家網站 Tabs 元件裡的 <button role="tab" tabindex="-1"> 被誤判。

這就是齒輪三在轉。規則寫出來,跑自家網站,跳出一個看起來很可疑的 fail,人去看,發現是規則錯了不是網站錯了,回頭改規則。

注意這裡人做了什麼:判斷那個 fail 是真的還假的。 這件事 AI 目前做不好,因為它需要你知道 roving tabindex 是什麼、也知道那個元件實際用鍵盤操作起來是什麼感覺。AI 負責的是後半段:理解那個模式之後,把豁免清單寫成程式碼。那部分它很快。

這一環能成立,靠的是掃描結果有結構化輸出。報告可以出成 JSON,每一筆都帶著檢核碼、狀態、出問題的元素、所在網址。這代表 AI 可以直接讀掃描結果,不需要人把畫面上的東西轉述一遍。

如果工具只會在終端機印彩色文字給人看,這段就得由人念給 AI 聽。那不是迴圈,那是人在中間當傳聲筒。工具要能被 AI 使用,輸出格式是門檻,不是加分項。

那 prompt 呢?

我原本以為這套流程最難的地方是 prompt。實際跑下來,prompt 只排第三。

排第一的是齒輪一那個「第五欄位」,規範到程式碼中間的轉譯。排第二的是AI 會很有信心地寫錯方向 —— Day 2 講的那條 title 規則就是實例,它寫出了一個跑得很順、方向完全相反的檢查。程式碼品質沒問題,題目理解錯了。

那規範是 PDF,AI 讀 PDF 會不會出錯?會,但那不是主要風險。表格抽出來偶爾錯行,這種錯很明顯,一眼看得出來。真正危險的是抽得很順、格式完全正確、但漏了一整個章節。所以我們的做法是先數:規範說有幾條,抽出來就該有幾條,對不上就回去找。

今天的重點

  • Agent loop 是三段齒輪:讀規範、生規則、自驗。 三段都必須機器可讀,整組才會轉。
  • 架構的價值是換掉失敗模式。 一個檔一條規則加上自動掛載,代價是 import 錯了會靜默消失,好處是 AI 不必同時維護兩個地方。這個取捨要自己選,不是照抄。
  • 人做的是判斷,不是打字。 那晚我做的事是讀規範、判斷偽陽性、決定閾值,程式碼是 AI 寫的。
  • 能自己修正的迴圈才有意義。 23:07 那個 commit 是規則被自家網站打臉之後改的,這種修正每一次都讓工具更可信一點。

明天 Day 4,把工具真的跑一次給你看:pip install 到看見第一個 issue,30 秒。


上一篇
Day 02:我們的工具說「全綠」,人工審查回來 9 條
下一篇
Day 04:20 秒裝好,然後它抓到自己
系列文
前端不寫 Python,照樣 ship 一把網頁無障礙 CLI4
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言