iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
Claude AI

把 Claude 練成專家:30 天打造可驗證的 Agent Skills系列 第 16 篇

Day 17|把 Skill 打包成可重現的發行物:版本、清單與安裝後驗證

  • 分享至 

  • xImage
  •  

昨日回顧

Day 16 將核心決策留在 SKILL.md,詳細規格放進 references/,可重複的機械步驟放進 scripts/。檔案拆開後,下一個問題不是「能不能壓縮成 ZIP」,而是別人拿到這一包,能否確認內容完整、安裝方式清楚,而且安裝後的行為與測試時相同。

今天以 ticket-source-guard 為例,做一份可重現的發行物。它只用中立的購票來源查核情境示範,不帶入私人資料或特定粉絲社群。

先定義發行物的邊界

一份 Skill 發行包至少需要入口、規格、執行檔、測試與授權說明。發行前先把「哪些檔案應該存在」列成清單,而不是把整個工作目錄直接打包。

ticket-source-guard/
├── SKILL.md
├── VERSION
├── MANIFEST.sha256
├── README.md
├── LICENSE
├── references/
│   ├── source-registry.md
│   └── eval-cases.md
├── scripts/
│   ├── normalize_host.py
│   └── classify_source.py
└── tests/
    ├── test_classifier.py
    └── fixtures/
        └── registry.example.json

這裡的檔名是示意,不是任何平台強制的封裝標準。若實際專案有其他必要檔案,應先補進明確的允許清單,再發行。反過來說,草稿、憑證、快取、測試輸出及本機設定,不應因為它們剛好在目錄裡就進包。

版本標示要對應行為變更

把 VERSION 寫成單一、可讀取的版本號,例如 0.3.0。不要只靠 ZIP 檔名或 Git 分支稱呼版本。版本規則可以很簡單,但必須對使用者有意義:

  • 修正說明文字、不改輸入輸出與判斷結果:修補版。
  • 新增相容的來源欄位或額外診斷資訊:次版本。
  • 改變狀態語義、必要欄位,或使舊呼叫方式失效:主版本。

這是本專案的發版約定,不是保證所有 Skill 都使用同一套規則。尤其 Day 13 的 OFFICIAL、UNCONFIRMED、INSUFFICIENT_INPUT、CONFIGURATION_ERROR、CONFLICT,以及 Day 14 的 freshness 判斷,若語義改變,就不能偽裝成純文件修訂。README 應列出從上一版升級時需要改動的輸入、schema 或測試。

建立允許清單與雜湊清單

打包程式從明確的檔名清單讀取內容,先拒絕缺檔、符號連結、絕對路徑與 .. 路徑,再建立壓縮檔。下列範例只展示清單與雜湊的核心,不宣稱涵蓋完整的 ZIP 安全實作。

from hashlib import sha256
from pathlib import Path

root = Path("ticket-source-guard").resolve()
allowed = [
    "SKILL.md", "VERSION", "README.md", "LICENSE",
    "references/source-registry.md", "references/eval-cases.md",
    "scripts/normalize_host.py", "scripts/classify_source.py",
    "tests/test_classifier.py", "tests/fixtures/registry.example.json",
]

lines = []
for rel in sorted(allowed):
    path = root / rel
    if path.is_symlink() or not path.is_file() or path.resolve().is_relative_to(root) is False:
        raise ValueError(f"unsafe or missing file: {rel}")
    digest = sha256(path.read_bytes()).hexdigest()
    lines.append(f"{digest}  {rel}")
(root / "MANIFEST.sha256").write_text("\n".join(lines) + "\n", encoding="utf-8")

這段示意碼需要 Python 3.9 以上的 Path.is_relative_to。雜湊清單幫我們發現檔案被改過,卻不能單獨證明發行者身分:攻擊者若能同時改檔案與清單,兩者仍會一致。發行時還需要由可信管道公布版本、壓縮檔雜湊或簽章;驗證者必須先信任那個管道,不能從包內自行證明包的可信性。

可重現不只是一樣的原始檔

若要兩次建置產出相同的壓縮檔位元組,還要固定排序、壓縮參數、檔案權限與封存中的時間戳。否則 MANIFEST 相同,ZIP 雜湊仍可能不同。最基本的 CI 驗收是從同一個 commit 在乾淨環境建置兩次,比對兩個壓縮檔的 SHA-256;若不同,先檢查時間戳與工具版本,而不是把差異忽略掉。

不要把真實 registry 的私人備註、登入權杖或本機憑證放入 fixtures。示例來源紀錄應使用公開、可分享或完全虛構的資料,並在 README 標明它不代表即時官方來源判斷。

安裝後跑 smoke test

建置成功不等於安裝成功。把封包解壓到乾淨的暫存目錄,從那裡執行三組最小測試:

1. 結構:SKILL.md、references/、scripts/、VERSION 均存在;指針不越界。
2. 機械判斷:示例 registry 的有效、過期、衝突案例各回傳預期狀態。
3. 載入路徑:驗證網址的任務會讀來源規格與 classifier;
   修改 schema 的任務會讀 schema 規格與測試,不會靠記憶猜欄位。

測試時要用封包內的檔案,不要偷偷引用開發機上的同名模組。如果 classifier 找不到 registry 或日期,應顯示明確錯誤,不可降級成 OFFICIAL。如果 smoke test 需要網路,請把它標成另一組整合測試;離線的基本測試應能在沒有帳號、cookie 或即時頁面的環境執行。

驗收與回退

發版前把結果記到一筆 release record:版本、來源 commit、建置工具版本、壓縮檔雜湊、MANIFEST 驗證結果、兩次建置比對結果、smoke test 結果和驗證時間。任一項失敗就停止發行,回到上個已驗證版本;不要用「大致可用」替代可追查的驗收。發行後若發現狀態判斷錯誤,保留舊版識別資訊,發布修正版與變更說明,而非悄悄替換同版本的檔案。

今天完成的驗收標準

[ ] 允許清單只包含要發行的檔案,無憑證、快取與私人資料
[ ] VERSION 與變更說明對應到實際行為
[ ] MANIFEST.sha256 覆蓋發行內容且驗證通過
[ ] 同一 commit 的兩次乾淨建置產出相同封包雜湊
[ ] 從安裝後目錄跑結構、判斷與載入路徑 smoke test
[ ] 缺資料、過期、衝突與 script failure 不會變成 OFFICIAL
[ ] release record 記下版本、來源、雜湊、測試與時間

明天預告

Day 18 把發行物交給陌生環境:設計安裝步驟、相容性檢查與失敗時的安全回退,避免「我這台可以跑」成為唯一驗收證據。


上一篇
Day 16|讓 Skill 漸進式揭露:SKILL.md 保持短,把細節放到 references 與 scripts
下一篇
Day 18|在陌生環境安裝 Skill:相容性檢查、smoke test 與安全回退
系列文
把 Claude 練成專家:30 天打造可驗證的 Agent Skills 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言