tools/skillgen/(gen.py 54KB · platforms.toml 12KB · fragments/ · expected/)這是「技能檔結構」的生產端。核心哲學(docstring 一開頭就講):
Fragments undertools/skillgen/fragments/are the single source of truth a human edits; the files undergraphify/skill*.mdandgraphify/skills/<platform>/references/are generated, committed artifacts.
也就是:人只編輯 fragments,機器渲染出所有平台的 skill 成品。render 是冪等的(固定順序、按名排序、LF 換行、產出檔不含時間戳),並用 expected/ 當 baseline 擋住 drift。
python -m tools.skillgen # 重新產生所有平台的 artifacts
python -m tools.skillgen --platform claude
python -m tools.skillgen --check # byte-diff 渲染 vs 已提交 + expected/,有 drift 就 exit 1
python -m tools.skillgen --audit-coverage # 每個 host:驗證該 host 的 v8 body 每節都住在自己渲染裡
python -m tools.skillgen --schema-singleton # 驗證 file_type enum 到處 byte-identical
python -m tools.skillgen --monolith-roundtrip # 驗證每個 monolith == v8 modulo enum
python -m tools.skillgen --always-on-roundtrip # 驗證每個 always_on/*.md 重現其舊常數
python -m tools.skillgen --bless # 用目前渲染重寫 expected/
platforms.toml 宣告每個 host 怎麼組裝 skill:bucket(split 或 monolith)、core(split 用的 core 模板)、skill_dst / refs_dst(輸出位置)、name / description(frontmatter,description 各平台 verbatim 保留)、dispatch(Part-B dispatch fragment)、extraction(verbose/compact,選 extraction-spec 的內容)、shell(posix/powershell)、claude_md、hooks_variant、extra_sections、monolith。
load_platforms() 用 tomllib 解析(Python 3.10 退回 tomli),每個 [platform.<key>] table 變成一個 Platform 物件。
渲染邏輯:
_render_frontmatter(platform) — 產生 ---\nname: …\ndescription: …\n---。_render_core(platform) — 載入 fragments/core/core.md(split 的 lean core 模板),把 platform 的 slot 填進去:dispatch fragment、shell fragment、hooks pointer、extra_sections、reference index。render(platform) — 回傳 list[RenderedArtifact](SKILL.md + references/ 側車的渲染結果)。fragments/references/shared/ 帶上。這解釋了為什麼 17 個平台的 skill-<platform>.md 有 40KB 卻彼此只差一點點——它們共用一個 core 模板。
防 drift 的雙向機制:
check() — 把渲染結果與已提交的檔案做 byte-diff,再與 expected/ 做 byte-diff。任何一個不一致就回報(CI 會 exit 1)。這確保「手改了 generated artifact」或「fragment 改了但沒重新產生」都會被抓到。bless() — 把目前的渲染結果寫進 expected/(_expected_path 命名 graphify__skill.md 等)——「這是我承認的真相」。改模板時先 bless 再 commit。段落式說明(skillgen 的「驗證套組」,都是為了讓「拆分 platform」這件事安全):
audit_coverage(platform) — 用 _V8_BASELINE_SHA(git show 拉 v8 分支的原始 skill body)逐 host 驗證:該 host 自己的 v8 body 每個 heading 都「單一居住」在它自己的渲染裡——一個只影響單一 host 的漏接才看得見。schema_singleton(platforms) — 驗證 file_type enum 在所有 artifact 裡 byte-identical。monolith_roundtrip / always_on_roundtrip — 驗證 monolith skill 與 always_on 檔能重現過去的常數。段落式說明:這組 _is_chunk_cleanup_fix_line、_is_zero_node_guard_fix_line、_is_no_api_key_fix_line 等函數,是為「語意修復」判定的——渲染引擎產生內容時,需要識別哪些行屬於特定的歷史修復(例如 zero-node guard、no-API-key 提示),以便穩定地定位/合併。每個都是一個小 predicate,名稱本身就描述了它認得哪一行。
下一頁:scripts/gen_demo_path.py——README 首圖 SVG 動畫的生成器。
前面是「看懂 render/check/bless」;這次是把它當版本管理工具:你的公司 fork 了 graphify,改了一堆 fragments(加了內部工具指引),現在上游釋出新版,你要安全升級。
python -m tools.skillgen --check 確認 fork 的產物與 expected/ 一致(drift 歸零才談升級)。--audit-coverage 驗證「每個 host 的 v8 body 每個 heading 仍單一居住在自己的渲染裡」,抓到「上游改了某段、卻漏接進你 fork」的情形。python -m tools.skillgen 重產所有平台 → --check 過 → --schema-singleton 確認 file_type enum 全站 byte-identical → --monolith-roundtrip 確認 monolith 重現。skillgen 的防護常被混為一談,其實是三個不同層次:
_V8_BASELINE_SHA 拉 v8 分支的原始 skill body,逐 host 驗證「每個 heading 都單一居住在自己渲染裡」——抓「只影響單一 host 的漏接」,這是 byte-diff 抓不到的一層(byte-diff 只驗證「一致」,不驗證「該在的內容有在」)。--bless 通過 = 產物正確」。bless 只代表「目前渲染與我承認的 baseline 一致」——它完全不知道「渲染邏輯本身有沒有 bug」。常見誤用:某個 fragment 放錯位置(例如把 claude 專屬的 section 寫進 shared/),render 照樣輸出、check 照樣過、bless 照樣接受——直到 --audit-coverage 或人工發現「claude 的 SKILL.md 混進了 codex 的內容」。bless 前先跑 audit/roundtrip,不要只靠 check。| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
--check 一直 exit 1 | 產物被手改、或 fragment 改了沒重產 | python -m tools.skillgen 重產;確認後 --check 過;不要手改產物 |
bless 後 --check 仍失敗 | expected/ 的命名路徑與實際產物路徑不一致 | 對照 _expected_path 命名(graphify__skill.md 等);確認 bless 寫對位置 |
| 某平台內容缺一段(check 卻過) | 該段被放在不屬於它的 fragment 或 shared/ 漏接 | 跑 --audit-coverage;逐 heading 對照 v8 baseline 找「漏接」 |
| description 各平台不同步 | 某平台覆寫了 description(platforms.toml 設了不同值) | 確認 platforms.toml 的 description 為「PRESERVED VERBATIM」;統一單一來源 |
| 新增平台後部分驗證炸掉 | 新平台的 hook_variant/shell 組合沒被 roundtrip 覆蓋 | 為新平台補 monolith/always_on roundtrip;確認 expected/ 有該平台 baseline |