Day 27 · W4 · AI 線 · 難度 ★★☆☆☆
本系列由 AI 協作撰寫。 內容、技術判斷、程式碼由 light-design 數位顧問團隊與 Claude 共同產出,最終由作者驗證後 publish。完整協作模式與把關方式見 Day 01。
今天 0.6.0 上 PyPI,這支工具的第 20 個版本。Day 01 答應的四樣東西裡有一樣是開源 CLI,今天收這一樣。
開源不是把程式碼放上去那一下。放上去之後每一次發版都要走同一份清單,九步。我漏過其中一步,連漏五個版本,而那一步管的是使用者看到這個工具的第一眼。
一句話主軸:小眾工具只有一個門面。沒有社群替你講,看你的人全部從套件頁面進來,那一頁落後,代價比少一個功能大。
發版清單放在工具的 repo 外面,刻意的:它裡面有帳號設定跟踩過的雷,是我們自己的紀律,不是套件的一部分。九步:
1. pyproject.toml 版本號
2. CHANGELOG.md [Unreleased] 搬進 [x.y.z] — 日期,照 Keep a Changelog
3. README.md 必改:CHANGELOG 每一條 Added,README 要有對應的一段
4. README.en.md 雙語同步,不能只改一邊
5. pytest 全綠才往下
6. build + twine 本地先建一次、檢查一次
7. commit chore(release): x.y.z
8. tag + push 推 tag 觸發 CI
9. 驗收 PyPI 頁面 開新環境裝一次,看頁面渲染對不對
前七步今天早上跑完:110 個測試綠、wheel 跟 sdist 都過檢查。第九步為什麼要開新環境:本機裝的是開發版,pip install --upgrade 在同一個環境裡會拿快取騙你,看起來裝好了,其實還是舊的。第八步推上去之後,事情就不在我手上了。

九步裡有八步是機器可以擋的,漏掉的偏偏是那一步機器不擋的。
推 tag 之後 CI 跑三個工作。第一個建置,先驗 tag 的版號跟 pyproject.toml 一致,不一致直接失敗;建完拿建出來的 wheel 跑一次測試,不是拿原始碼跑。第二個發布到 PyPI,第三個建 GitHub Release。
第二個工作綁了一個叫 pypi 的環境,設定裡要求審核人核准才能執行。所以流程走到這裡會停下來,等一個人到 GitHub 上按一下。這一下不能自動化,也不該自動化:PyPI 上的版本不能刪,只能標記撤回,發錯了是永久的紀錄;同一個版號的 tag 也不能重推,PyPI 會拒收,只能 bump 一個新號。
tag 的名字有格式,v 加三段數字,格式沒中的話環境政策直接拒絕部署,一兩秒就失敗,而且沒有任何提示說是名字的問題。推錯了想取消,只有建置那一分鐘可以按停,發布一開始就來不及,之後只能撤回再發下一版。這些全在清單第五節「必踩雷」那張表,每一列都是首發那次真的踩過的。
這件事跟這個系列講了 27 天的東西是同一個形狀:機器把能做的做完,把不能回頭的那一步留給人。Day 20 說 agent 不准替 caveat 決定,這裡是 CI 不准替人按發布。
5 月 7 日 0.1.0 上 PyPI。接下來兩天發了八個版本:lint 在 0.2.0 進來、rules 在 0.3.0、init 在 0.3.1,工具從「掃網站的 CLI」變成三種用法。README 從頭到尾沒動,PyPI 頁面上還在講 scan 跟 site。
5 月 9 日打開 PyPI 頁面看了一次才發現。0.3.4 那個版本沒加任何功能,只改文件:兩份 README 加起來 400 行,目錄、兩條安裝路線、30 秒上手、三種用法各一節。從 0.2.0 到 0.3.2,五個版本、三個新指令,套件頁面一個字都沒提。
為什麼會漏:CHANGELOG 每次都有寫,我以為那就是「文件更新了」。但 PyPI 不渲染 CHANGELOG,它渲染 README。CHANGELOG 是給回頭查的人看的,README 是給第一次來的人看的。這個工具的使用者是要送台灣標章的網站維護者跟承包商,他們不逛 GitHub,會看到的就是 PyPI 那一頁跟 pip install 之後的 --help。那一頁落後,等於工具落後。
所以清單的第三步寫成「必改」,判準是一句話:CHANGELOG 每一條 Added 或 Changed,README 都要有對應的段落或例子;BREAKING 要在 README 開頭加警示。這條不是這個專案的發明,Keep a Changelog 跟 SemVer 都假設版本號、CHANGELOG、README 三邊同步發版,是我當時沒照做。
那次還順手踩到另一顆:README 裡的連結用相對路徑,在 GitHub 上正常,到了 PyPI 全部斷掉,因為 PyPI 只拿到 README 的文字,沒有 repo。清單上因此多了一行:README 的連結一律絕對網址。0.3.4 是 patch 不是 minor,CLI 表面、規則、輸出都沒變,變的只有文件;但那是我目前為止對使用者影響最大的一個 patch。
清單走一遍,這版有什麼:
BREAKING 兩個檢測碼改名(碼表對帳的結果),README 開頭加了遷移指引
Added --spec 115.11|110.07|both 一次掃描給兩個判定基準(11/30 換版前後)
CS3241300E 焦點指示器對比,Day 22 那 30 個 fail 就是它報的
沒開模型時每頁一則 caveat,說明幾條規則因此沒跑
Fixed 表單空送不再送出瀏覽器不會擋的表單;每個表單都報,含對話框裡的
Changed 版本號跟規則數改由實際來源推導(Day 26)
BREAKING 那一條要多講一句。兩個檢測碼改名,是把規則對回 115.11 碼表的結果,沒有任何檢查被移除。但如果你的腳本用碼號過濾報告,舊碼號不會報錯,只會安靜地從輸出裡消失。所以 README 開頭放的是遷移指引,不是功能介紹;清單第三步那句「BREAKING 要在 README 開頭加警示」,這版是第一次真的用到。
發版前還有一條不在九步裡、但同樣不准跳的規矩:規則或探針有改動,就要對自家站真的跑一次整站掃描才能 tag。單元測試不開瀏覽器,Day 26 第一件事就是這樣漏掉的。這版的整站掃描是 9 月 7 日那份,30 頁 0 fail。
有一件事要老實講:這版的發版 commit 是 8 月 30 日寫的,日期也寫 8 月 30 日,然後放了 12 天沒推。系列寫稿期間沒空走清單,Day 22 跟 Day 26 引用的都是「0.6.0 開發版」。今天把日期改成實際發布日,補上 README 少的三段,才推。清單的價值就在這裡:沒走完的那幾步,會用「開發版」三個字一直提醒你。
發版是給人的;套件裝好之後,還有一種使用者從來不看報告,只看一個數字:
0 沒有 fail
1 有 fail(--strict 時),或找不到任何原始檔
2 用法錯誤:查不到的檢測碼、init 目標不存在、要覆蓋卻沒給 --force
lint --strict 有 fail 就回 1,這是給 CI 擋 PR 用的,整段流程在 CI 裡就是一行:a11y-moda lint src --level AA --strict --fail-only。--fail-only 把 info 跟 caveat 從輸出拿掉,機器不需要建議,只需要判決。回 0 不代表通過,代表沒有確定的錯;caveat 跟 info 都不影響 exit code,那是刻意的,機器只擋確定錯的,量不到的留給人看。三種輸出格式裡 JSON 是給它的,md 跟 html 是給人的,同一份資料。
這一節只講到這裡,因為它的前半 Day 20 跟 Day 23 講過了:給 agent 的說明書,跟 agent 怎麼讀兩份 JSON。exit code 是同一件事的最後一塊。
四樣東西攤帳的時候會用到,先擺在這:

下載數的形狀是「發版日尖峰、平日個位數」,這是機器抓取加少數人用的形狀。
PyPI 下載 3,183 次,不含鏡像。三分之二在 5 月,而 5 月最高的三天 432、411、372 全是發版當天,發版當天的下載大多是自動抓取,不是人。最近 30 天 174 次,一天五、六次。GitHub 星星 0,fork 0,目前 issue 也是 0 個。
版本的形狀也一樣偏:20 個版本裡 16 個在 5 月,6 月 3 個(其中一個是安全加固),然後兩個半月沒發,第 20 個是今天。5 月是送標章的月份,每一輪審查意見回來就修工具、發版、改站;6 月 18 日對齊 115.11;之後的力氣全進了這個系列。
這些數字我不解讀成失敗,也不解讀成成功。這個工具的受眾是要送台灣標章的人,他們裝套件、跑掃描、送件,不會回 GitHub 按星。星星量的是社群,這個工具沒有社群,有的是一份自評表跟一個 11 月 30 日的換版日。Day 07 講過開源的理由是判斷只寫在程式碼裡,那個理由今天還是一樣,跟星星無關。
明天 Day 28:工具收完,收標章。審查回了三輪,每一輪我都先改工具,再改網站。