Claude Code 模型進步後,我拿自己的 GitLab MR 審查 Skill 開刀
我看到 Thariq 在 X 發的 The new rules of context engineering for Claude 5 generation models 時,最醒目的數字是:Anthropic 為 Opus 5、Fable 5 這類新模型移除了超過 80% 的 Claude Code 系統提示詞,程式能力評測沒有出現可測量的退步。不過原文沒有說明這個 80% 是按行數、token 還是規則數計算。
如果只看這個數字,很容易把文章理解成「提示詞愈短愈好」。原文談的其實是另一件事:模型能力變強後,我們也要重新檢查餵給它的整包上下文。
這個評測結果也有適用範圍。程式能力評測不會自動涵蓋外部寫入權限、diff 是否完整,以及 review 報告有沒有逐項完成安全檢查。一份漏了檢查卻回報乾淨的 review,如果評測只看最後輸出,可能和真正通過的 review 沒有差別。
80% 是 Anthropic 調整 Claude Code 系統提示詞的結果,不是每個人的 CLAUDE.md 或 Skill 都該照抄的比例。Thariq 後來也補充,不同模型會使用不同的 system prompt。這次我看的不是「能不能刪到八成」,而是每一條規則還有沒有存在的理由。
這篇做的是內容稽核,不是已完成的重構。我會打開自己真的在用的 GitLab MR 審查 Skill,找出刪除、搬移與保留的候選;實際 Skill 目前沒有被修改。
先說清楚:Claude Code Skill 是什麼
Skill 不是一段存起來、等著貼進對話框的提示詞範本。它是一個資料夾,必要入口是 SKILL.md,旁邊還可以放 scripts、templates、examples 與 references。
SKILL.md 的 frontmatter 會描述它適合處理什麼任務。description 會留在 Claude 可看到的 Skill 清單裡,讓它判斷何時使用;當使用者直接呼叫 Skill,或 Claude 判斷目前任務符合觸發條件時,主檔本文才會進入對話上下文。
references 也不會因為放在同一個資料夾就自動全部讀入。主檔必須說明每份資料放了什麼、何時需要;Claude 走到相關步驟時才讀。這種「先載入入口,細節需要時再取用」的做法,本文統一稱為漸進式揭露(progressive disclosure)。
本文提到的 tool,是 Claude 可以呼叫的外部能力,例如 Bash 或包好的 GitLab API;scripts/ 裡則是一般 shell 或 PHP script,Claude 可以透過 Bash 執行。和 references 一樣,SKILL.md 要寫明什麼時候該跑哪一支。script 能從 API response、頁碼或 exit code 產生可重跑的結果,但這還不等於可信證據;若要拿它約束模型,runner 必須從模型不可改寫的位置執行 script,並保留原始輸出供最後驗證。
X 長文到底在說什麼
我平常輸入的「幫我 review 這個 MR」只是這一次的提示詞。Claude 真正開始工作時,還會一起看到系統提示詞、CLAUDE.md、已觸發的 Skills、記憶、工具說明,以及前面讀過的程式碼。如何安排這些長期會重複使用的資訊,就是這篇文章所說的 Context Engineering。
麻煩在於,這些規則不一定會隨模型一起更新。Thariq 提到,他們回頭查看 Claude Code 的內部使用紀錄,發現同一個請求可能同時出現「需要時補文件」和「絕對不要加註解」等互相衝突的要求。模型通常還是能猜到使用者想做什麼,只是得先花力氣處理衝突。
原文把新舊做法整理成六組變化:
| 過去的做法 | 現在的做法 | 白話解讀 |
|---|---|---|
| 給模型明確規則 | 讓模型依情境判斷 | 不再寫死註解長度,而是要求跟著周圍程式碼的風格。 |
| 提供大量工具範例 | 把工具介面設計清楚 | 用參數名稱、型別與列舉值說明怎麼用,避免範例限制模型的探索方式。 |
| 一開始載入全部資訊 | 漸進式揭露 | Code review、驗證或少用工具,等任務真的需要時再載入。 |
| 在不同地方重複提醒 | 保留一份簡單的工具描述 | 使用方式寫在工具本身,不在 system prompt 再抄一次。 |
把記憶塞進 CLAUDE.md | 使用 Auto-memory | 個人與工作習慣交給記憶系統,CLAUDE.md 留給 repository 的固定資訊;這一組屬於 CLAUDE.md 整理,本文不展開。 |
| 只提供簡單 Markdown 規格 | 提供可核對的完整參考 | 可以直接給測試、程式碼、HTML 成品或評分表(rubric),讓模型有東西可比對。 |
Thariq 把這個方向稱為 unhobbling,也就是替模型鬆綁。舊模型容易漏步驟時,強規則是一種保護;新模型已有較好的判斷力,繼續保留所有舊限制,可能讓它先處理規則衝突,反而沒把力氣放在任務上。
影片補上了哪些實作細節
01Coder 的 Context Engineering 的全新規則 前半段把六組變化翻成實際例子,後半段則拿自己的開源專案 boring-video-studio 動手檢查。
影片裡有三個我認為很重要的補充:
- 一份超過 300 行的
SKILL.md,如果同時塞了觸發時機、流程、格式表、目錄結構與重建細節,光是拆檔還不夠;主檔必須留下清楚的載入路由。 - 「鐵律」不能全刪。財經數字要回頭核對官方原件、輸出路徑要再次確認,這些是會直接影響正確性的限制。
- 檢查重點不是文件有幾行,而是模型是否每次都被迫讀到當下用不到的內容。
影片在 08:01–08:09 提到,可在 Claude Code 輸入 /doctor 幫 Skills 與 CLAUDE.md 瘦身。這一點和我本機實測有出入,後面會單獨說明;我不會把未重現的功能當成這次稽核依據。
我拿自己的 762 行 Skill 當案例
我的 review-mr 一開始只負責抓 MR diff、檢查 Laravel 常見問題,再輸出 review 結果。每次遇到漏判,我就補一條規則;每次碰到團隊慣例,又加一段說明。現在的檔案是這樣:
review-mr/
├── SKILL.md 128 行
└── references/
├── engineer-levels.md 53 行
├── security-checklist.md 410 行
└── team-cursor-rules.md 171 行
─────
762 行
四個檔案合計約 2.2 萬字元。若排成一般 A4 文件,會是二十頁上下;依中文與程式碼的比例粗估約 1.3 萬到 1.8 萬個 token,實際數字仍取決於模型的 tokenizer。只要載入路由寫得不夠精確,一次 review 就可能把大半內容一起帶進 context。
主檔確實有指向 references。以下是我目前 SKILL.md 的真實片段,已把公司名稱匿名化:
逐類別的 grep pattern、❌/✅ 程式碼例
與專案特例見 `references/security-checklist.md`。
審完要對 9 大類別逐一記錄「發現/已檢查無發現」。
這三行至少有指路,但仍然沒有說「哪些變更才需要讀哪些章節」。結果很可能是一碰到資安審查,就把 410 行整份載入。拆成多個檔案,不代表上下文真的變乾淨。
載入路由至少要具體到能由 diff 判斷:
## 載入路由
- diff 含 `*.blade.php` 且出現 `{!! !!}` → 讀 `references/project-rules.md#rich-text`
- diff 含 `DB::raw`、`whereRaw` 或字串拼接 SQL → 讀 `references/security-details.md#sqli`
- diff 含 `$request->all()` 或空 `$guarded` → 讀 `references/security-details.md#mass-assignment`
- 以上皆未命中 → manifest 仍帶入 `security-baseline.md` 的九格,不載入 details
我檢查時把公司名稱、成員資料、Token 位置與內部規則細節都留在本機。文章只討論結構和判斷方式,不公開團隊資料。
稽核時先分三類,不是一律刪除
在逐段看文件前,我先用三種去向當骨架:
| 類別 | 處理方式 | 例子 |
|---|---|---|
| 模型已能從程式碼與清楚介面判斷的通用知識 | 刪除或縮成一句驗收條件 | 教模型什麼是 SQL injection 的長篇說明 |
| 執行到特定步驟才需要的操作細節 | 搬到 script、tool 或按需載入的 reference | GitLab diff 分頁、留言 API payload |
| 模型不能自行猜測的權限與專案事實 | 保留,而且放在穩定位置 | 唯讀預設、內部 CMS 富文字例外 |
先講結論:真正因為模型變強而可以刪的,只有第一類。
這三類不能混在一起。重複規則應該去重,是文件品質問題;AI 介入比例無法重現,是指標本身有問題;API 指令則不是刪除,而是搬家。真正與「模型進步」直接相關的,是我是否還需要用大量教學和正反例,從頭教模型辨認 Laravel 的通用風險。
真正該開刀的是那 410 行安全檢查表
security-checklist.md 一個檔案就有 410 行,占整套 Skill 約 54%。裡面依序解釋 SQL injection、XSS、Mass Assignment、CSRF、檔案上傳、IDOR(改一下 id 就看到或改到別人的資料)、敏感資訊、Session/Cookie 與 Path Traversal。
每一章幾乎都有同樣的四件事:風險定義、危險範例、正確範例,以及 grep 指令。像下面這些內容,本來是為了避免舊模型漏掉基本模式:
- 解釋字串拼接 request 參數為何會造成 SQL injection,再提供 Query Builder 與綁定參數的完整範例。
- 示範 Blade 的
{!! !!}、{{ }}和 JavaScriptinnerHTML差異。 - 展開
$request->all()、空$guarded與$fillable的 Mass Assignment 教學。 - 示範 POST form 缺少
@csrf的危險與修正方式。 - 列出
http_only、secure、same_site等 Session/Cookie 建議值。
這五類教學在現有檔案裡合計 215 行。如果照下面的草案落地,九格基線表本體是 11 行;詳細範例沒有消失,只在命中或需要修正建議時載入。
| check_id | 每次都要完成的驗收條件 |
|---|---|
sqli | 不可信輸入不得直接拼進 raw SQL。 |
xss | 使用者輸入必須跳脫;富文字例外要符合專案規則。 |
mass_assignment | 寫入 Model 的欄位必須有白名單。 |
csrf | 會改變狀態的瀏覽器請求必須帶 token,例外需有簽章驗證。 |
file_upload | 伺服器端要驗證類型、大小、檔名與存放位置。 |
idor | 查改單筆資料前必須驗證擁有者、Policy 或權限範圍。 |
secrets | 不得硬編碼憑證,也不能信任前端傳回的敏感值。 |
session_cookie | Cookie flags 與登入後的 session regenerate 必須正確。 |
path_traversal | 外部路徑不得直接拼接,實際路徑必須限制在白名單目錄。 |
九個類別的列舉要留下。審完仍要逐格記錄「發現/已檢查無發現」,這是舊版就有的完成契約,比自由格式的清單更容易看出缺項。該縮的是每格背後「從零教會模型」的篇幅:風險定義和正反範例壓成一句驗收條件,重複的 grep pattern 交給 script 產生候選清單,只有命中或需要修正範例時,才載入詳細章節。
但這份文件裡有一條不能跟著刪:
{!! $row->data_exp !!}
在我的專案裡,data_exp 是經過既定流程處理的後台富文字欄位。通用 reviewer 看到 {!! !!} 很容易直接報 XSS;這條例外能避免大量誤報,而且模型不可能只看 Laravel 文件就猜到。通用教學可以縮,專案例外要留下。
這才是模型進步帶來的實際改變:不是停止檢查 OWASP,而是不必再用數百行教材教它每個基本名詞,將 context 留給模型無法自行得知的專案事實。
那 171 行團隊規則怎麼處理
我也打開了 team-cursor-rules.md。它不是單純的團隊風格文件,而是把 debug 殘骸、重複程式碼、四個 CMS helper、安全底線、工程師分級紅線與提交清單混在一起。
像 dd()、未使用 import 和 dead code,可以交給靜態檢查 script;SQL injection、XSS 與 Mass Assignment 已和安全檢查表重複。getCropperImage()、web_url()、tr()、getShowTypeData()、富文字例外和共用 layout 限制則是專案事實,必須進 project-rules.md。至於「複製超過五行一律抽 method」這種寫死門檻,現在的模型應先看周圍程式碼與重複造成的維護成本,不該只數行數。
三份舊 reference 的去向如下:
| 舊檔案 | 新去向 |
|---|---|
security-checklist.md(410 行) | security-baseline.md+security-details.md |
team-cursor-rules.md(171 行) | 專案事實進 project-rules.md;通用檢查交給 script;重複與寫死風格刪除 |
engineer-levels.md(53 行) | 移到 archive/,不再參與預設 review |
「不能刪」不等於「繼續放在 prompt」
這三條規則刪不得,但它們該待的位置不一定是 SKILL.md。第四條專案例外,前面已經談過。
--comment 是請求關鍵字,不是終端機參數
這裡的 --comment 不是叫讀者在 shell 執行的 CLI flag,而是我在自然語言請求中使用的授權關鍵字:
幫我 review 這個 GitLab MR --comment
沒有它時只能回報,不能寫回 GitLab。這個邊界不能刪,但只寫在 SKILL.md 還不夠穩;--comment 本身仍只是模型看到的文字。
更好的設計是把「讀取 MR」與「送出留言」拆成兩個權限設定。預設 runner 只掛載讀取工具,也不能讓通用 Bash 取得 GitLab token 或對外 POST;辨識到 --comment 後,仍要由外部權限層切換到可寫 profile,才掛載 post-mr-comment.sh。若執行環境不支援條件式掛載,就拆成唯讀與留言兩個 Skill,不能假裝一個關鍵字等於權限隔離。
overflow: true 應由抓 diff 的 script 處理
舊 Skill 會呼叫 /projects/:id/merge_requests/:merge_request_iid/changes。GitLab 從 15.7 起將它標記為 deprecated,預計在 API v5 移除;看到 overflow: true 時才換 endpoint,已經不適合當新流程的起點。
我會把它搬進 scripts/fetch-mr-diff.sh:預設走 /projects/:id/merge_requests/:merge_request_iid/diffs?page=...&per_page=...,讀取每一頁並檢查 collapsed、too_large 等狀態;需要 raw diff 時,明確改呼叫 /projects/:id/merge_requests/:merge_request_iid/raw_diffs。舊版 instance 也能用 deprecated 的 /changes?access_raw_diffs=true 直接向 Gitaly 取資料,但不應再是新 script 的預設路徑。若仍拿不到完整內容,script 要用非零 exit code 停止,不能產出部分審查。
檔案與行號仍是輸出契約
每個問題都要附檔案與行號,讓工程師能直接回到 MR 核對。這類完成條件適合留在 SKILL.md,因為它描述的是最終交付物,不是某個 endpoint 的操作細節。
漸進式揭露也可能製造靜默漏報
把 references 改成需要時才讀,對 code review 有一個特別危險的副作用:寫程式漏載入依賴,通常會 build 失敗;review 漏載入安全檢查表,卻可能照樣產出一份「沒有問題」的報告。
所以我不會把九類安全基線全部藏到 references。fetch-mr-diff.sh 會把固定的九格 baseline 骨架放進 manifest,再依命中項目提供詳細規則的載入路由。報告另外列出三種不同來源的狀態:
# 由 fetch-mr-diff.sh 輸出,模型只負責轉貼
diff_complete: true
diff_pages: 3
# 由工具掛載狀態決定,未授權時寫入工具根本不存在
write_mode: read-only
# 模型自述,僅供人工比對,不是證據
checks_claimed:
- security-baseline
- project-rules:xss-rich-text
這裡不能再靠 SKILL.md 寫一句「必須跑 baseline」。外部 runner 執行 fetch-mr-diff.sh,把前面的九格骨架寫進唯讀 manifest,先固定九個 check_id 與 grep 候選數;模型只能往報告裡對應的格子填「發現/已檢查無發現」和證據。任何空格都由驗證 script 擋下,不能產生完成報告。
前兩項的來源是 host 保存的 manifest 和工具掛載紀錄,模型改不了來源;即使它轉貼時改了文字,驗證 script 也會比對失敗。最後一項是模型自述,不能當證據,只能拿來和 manifest、工具呼叫紀錄比對。這裡的原則是只採信外部可觀測的事件,不採信模型自述,也就是後面第三個問題裡的「能不能交給 script 強制執行」。
AI 介入比例應該直接刪掉
原本的 Skill 會從抽象程度、命名方式和教學式註解,猜測這個 MR 有多少比例由 AI 產生。這個數字看似具體,實際上沒有分類器、基準資料或可驗證的標準答案,同一份 diff 重跑也不一定得到相同結果。
我會刪掉百分比,改記錄能核對的現象:
- 是否新增了沒有呼叫端的抽象層。
- 註解是否只是逐行重述程式碼。
- 命名與專案既有風格是否突然斷裂。
- 測試是否只覆蓋理想路徑,卻漏掉驗證與錯誤處理。
這些訊號不能證明程式碼由 AI 撰寫,但能直接說明維護風險;這些才是可以拿來 review 的問題。
我預計改成的結構
把刪除、搬移與保留分清楚後,新結構才有意義:
review-mr/
├── SKILL.md # 觸發、唯讀預設、載入路由、完成條件
├── references/
│ ├── security-baseline.md # 九格驗收條件
│ ├── security-details.md # 命中後才讀的範例與修法
│ ├── project-rules.md # CMS helper 與例外
│ └── output-format.md # 報告與留言格式
└── archive/
└── engineer-levels.md # 不再進入預設 review context
review-mr-host-tools/ # runner 管理,不放進模型可寫 workspace
├── fetch-mr-diff.sh # 分頁、完整性檢查、產生九格 manifest
├── validate-review-manifest.sh # 比對來源並拒絕空格
└── post-mr-comment.sh # 只掛載到已授權的可寫 profile
舊結構裡的 engineer-levels.md 沒有憑空消失,而是先移出執行路徑留作遷移紀錄。archive/ 不是 Claude Code 的保留字,只是一個普通資料夾;只要 SKILL.md 不再指向它,預設 review 就不會讀。程式碼是否有風險,不該取決於作者名單是否完整;需要談維護性時,也應針對這次 diff 提出可驗證的理由。
官方 Code Review Plugin 取代不了的四件事
Anthropic 在 claude-code repository 裡已經有官方 code-review Plugin。一般 GitHub PR review 可以先用官方方案,我不需要重寫通用方法。
我仍保留自訂 Skill,是因為它有四個官方 Plugin 不可能預先知道的條件:
- 程式碼放在 GitLab,MR 資料與 diff 要走 GitLab API。
- 專案是 Laravel 與內部 CMS,有自己的 helper、資料格式和例外。
- 某些看似不標準的寫法,是既有系統刻意保留的相容性要求。
- Review 預設只能回報;只有明確授權,才可以把留言寫回 MR。
這四點就是自訂 Skill 的邊界。官方流程負責通用的 code review,我的內容只補 GitLab 與專案現場才知道的資訊。
我沒有把 /doctor 當成稽核工具
我在 Claude Code 2.1.220 的互動工作階段實際執行 /doctor。它先要求檢查 Claude 安裝位置、PATH、版本、更新設定與 npm/bun 安裝狀態;這和官方文件把 claude doctor 描述為安裝健康檢查是一致的。本次輸出沒有出現 Skill 或 CLAUDE.md 瘦身分析。
因此我把原本「/doctor 可以檢查 context」的斷言移除。影片可能使用不同版本或額外功能,但在我能重現以前,不把它當成本文方法。
怎麼找出該重新確認的強制語句
先找出最容易衝突的強制語句:
# 找出專案指令與 Skills 裡所有強制語句
rg -n "一定|必須|永遠|禁止|不要|always|never|must" \
CLAUDE.md .claude/skills ~/.claude/skills
rg 是 ripgrep;沒安裝時可以改用 grep -rnE。路徑不存在就讓錯誤顯示出來,不要把錯誤吞掉後,誤以為「沒有規則」。
接著逐條問四個問題:
- 這是 repository 事實,還是偏好的寫法?
- 模型能不能從現有程式碼與清楚介面推斷?
- 能不能交給測試、lint、hook 或 script 強制執行?
- 刪掉後會不會造成不可逆的外部操作或靜默漏報?
例如「正式站資料庫名稱」是專案事實;「任何回覆都要語氣活潑」只是偏好,不是鐵律,可以放在當次提示或輸出範例。自訂指令不是全部刪,而是讓每種資訊待在最適合的位置。
我的結論
模型進步後,我真正能拿掉的,是為了從零教舊模型辨認通用風險而堆出的長篇教材。重複規則本來就該去重,壞指標本來就該刪,API 操作則應搬到工具層;不能把三者都包裝成「模型變強了」。
我的 GitLab MR 審查 Skill 最該留下的是唯讀邊界、完整 diff、證據格式與專案例外;最該補上的,則是 diff 完整性和已載入檢查的可觀測紀錄。
762 行不是原罪。現在只要碰到安全面的 MR,主檔 128 行加上安全清單 410 行,共 538 行就可能進入 context;真正要處理的是,其中有多少沒有替這次 review 增加可驗證的判斷力。
留言