設計一個可重用的 Skill:以程式碼審查為例
從需求分析到實作與測試,用程式碼審查為例,完整走一遍設計可重用、可組合、可測試 Skill 的流程。
設計一個可重用的 Skill:以程式碼審查為例
上一篇我們拆解了 Skill 的結構:指令、工具、資源與範例。
但理解結構不等於會設計。
這一篇,我們要用一個具體的例子——程式碼審查(Code Review)——從需求分析開始,逐步設計一個完整的 Skill。
你會看到如何定義範圍、設計流程、選擇工具、撰寫指令、加入範例,以及如何讓這個 Skill 可重用、可組合、可測試。
一、第一步:需求分析
設計任何 Skill 之前,先問三個問題:
1. 這個 Skill 要解決什麼問題?
問題:開發者提交程式碼後,需要有人審查品質、找出問題、提供改進建議。
但人工審查耗時、容易遺漏、標準不一致。
目標:讓 Agent 能自動審查程式碼,找出常見問題,並提供具體的改進建議。
2. 誰會用這個 Skill?
- 開發者:提交 PR 前先自我審查
- 團隊領導:自動化初審,減少人工負擔
- CI/CD 系統:作為自動化流程的一部分
3. 這個 Skill 的邊界在哪裡?
它能做:
- 檢查程式碼風格與命名慣例
- 找出潛在的 bug 與邏輯錯誤
- 檢查安全性問題(如 SQL 注入、XSS)
- 檢查效能問題(如 N+1 查詢、不必要的迴圈)
- 提供具體的改進建議
- 標註問題的嚴重程度
它不能做:
- 執行程式碼(靜態分析,不執行程式)
- 保證找出所有 bug
- 取代人工審查(它是輔助工具)
- 理解業務邏輯的深層意圖
邊界定義很重要,因為它決定了:
- 需要哪些工具
- 流程怎麼設計
- 輸出規範怎麼定
- 什麼情況下該拒絕
二、第二步:設計流程
有了需求,接下來設計執行流程。
程式碼審查的標準流程
1. 接收程式碼與上下文
↓
2. 識別程式語言與框架
↓
3. 檢查程式碼風格與命名
↓
4. 分析邏輯與潛在 bug
↓
5. 檢查安全性問題
↓
6. 檢查效能問題
↓
7. 彙整問題並評分
↓
8. 產出結構化審查報告
每個步驟的細節
| 步驟 | 做什麼 | 使用工具 | 輸出 |
|---|---|---|---|
| 1. 接收輸入 | 取得程式碼、語言、上下文 | 無 | 結構化輸入 |
| 2. 識別語言 | 判斷程式語言與框架 | detect_language | 語言資訊 |
| 3. 風格檢查 | 檢查命名、縮排、註解 | lint_code | 風格問題清單 |
| 4. 邏輯分析 | 找出潛在 bug | LLM 推理 | 邏輯問題清單 |
| 5. 安全檢查 | 找出安全漏洞 | security_scan | 安全問題清單 |
| 6. 效能檢查 | 找出效能問題 | LLM 推理 | 效能問題清單 |
| 7. 彙整評分 | 整合所有問題,計算分數 | 無 | 問題摘要 |
| 8. 產出報告 | 生成結構化報告 | format_report | 最終報告 |
三、第三步:定義輸入輸出
輸入 Schema
{
"code": "要審查的程式碼",
"language": "程式語言(可選,會自動偵測)",
"context": "額外上下文(可選)",
"focus": "審查重點(可選):security / performance / style / all",
"severity_threshold": "最低回報嚴重程度(可選):low / medium / high"
}
輸出 Schema
{
"summary": {
"language": "python",
"total_issues": 5,
"score": 7.5,
"severity_breakdown": {
"high": 1,
"medium": 2,
"low": 2
}
},
"issues": [
{
"id": "issue_1",
"severity": "high",
"category": "security",
"line": 12,
"title": "SQL 注入風險",
"description": "使用字串拼接建立 SQL 查詢,可能導致 SQL 注入",
"suggestion": "改用參數化查詢",
"example_fix": "cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,))"
}
],
"positive_notes": [
"變數命名清晰",
"有適當的錯誤處理"
],
"overall_suggestion": "整體程式碼品質不錯,但需要修正安全性問題。"
}
為什麼要定義 Schema?
- 一致性:每次輸出都遵循相同格式。
- 可組合:其他 Skill 可以解析這個輸出。
- 可測試:Harness 可以驗證輸出是否符合 Schema。
四、第四步:設計 Skill 檔案結構
skills/
└── code_review/
├── SKILL.md
├── config.yaml
├── steps/
│ ├── 01_detect_language.md
│ ├── 02_style_check.md
│ ├── 03_logic_analysis.md
│ ├── 04_security_check.md
│ ├── 05_performance_check.md
│ └── 06_generate_report.md
├── resources/
│ ├── style_guide.md
│ ├── security_checklist.md
│ ├── performance_checklist.md
│ └── severity_criteria.md
└── examples/
├── example_1_input.json
├── example_1_output.json
├── example_2_input.json
├── example_2_output.json
└── edge_cases.json
五、第五步:撰寫主指令(SKILL.md)
# Skill: code_review
## 描述
審查程式碼,找出風格、邏輯、安全性與效能問題,
並提供具體的改進建議與修正範例。
## 何時使用
- 使用者要求審查程式碼
- 使用者提交 PR 並要求回饋
- 使用者問「這段程式碼有什麼問題」
- CI/CD 流程中的自動化審查
## 何時不該使用
- 使用者要求執行程式碼(這個 Skill 只做靜態分析)
- 使用者要求撰寫新功能(這是 code generation,不是 review)
- 程式碼超過 1000 行(應該分批審查)
## 輸入
- code (string, 必填): 要審查的程式碼
- language (string, 選填): 程式語言,會自動偵測
- context (string, 選填): 額外上下文,例如這個函式的用途
- focus (string, 選填): 審查重點,可選 "security" / "performance" / "style" / "all",預設 "all"
- severity_threshold (string, 選填): 最低回報嚴重程度,可選 "low" / "medium" / "high",預設 "low"
## 輸出
{
"summary": {
"language": "...",
"total_issues": 5,
"score": 7.5,
"severity_breakdown": {"high": 1, "medium": 2, "low": 2}
},
"issues": [...],
"positive_notes": [...],
"overall_suggestion": "..."
}
## 執行流程
1. 識別程式語言與框架
2. 檢查程式碼風格與命名慣例
3. 分析邏輯與潛在 bug
4. 檢查安全性問題
5. 檢查效能問題
6. 彙整問題,計算分數
7. 產出結構化審查報告
## 輸出規範
- 每個問題都必須包含:嚴重程度、類別、行號、標題、描述、建議、修正範例
- 嚴重程度分為 high / medium / low
- 分數從 0 到 10,10 代表完美
- 必須包含至少一則正面評價
- 如果沒有發現問題,也要明確說明
## 評分標準
- 10 分:沒有問題
- 8-9 分:只有低嚴重度問題
- 6-7 分:有中等嚴重度問題
- 4-5 分:有高嚴重度問題
- 0-3 分:有嚴重安全漏洞或大量問題
## 邊界與限制
- 只做靜態分析,不執行程式碼
- 不保證找出所有 bug
- 如果程式碼超過 1000 行,要求分批審查
- 如果語言無法識別,要求使用者提供語言資訊
- 如果程式碼包含敏感資訊(如密碼、API key),提醒使用者移除
## 使用的工具
- detect_language: 偵測程式語言
- lint_code: 執行程式碼風格檢查
- security_scan: 執行安全性掃描
- format_report: 格式化審查報告
六、第六步:撰寫步驟說明
每個步驟都應該有詳細的說明。以下是幾個關鍵步驟的範例。
steps/01_detect_language.md
# 步驟 1:識別程式語言
## 目標
判斷程式碼的語言與框架,以便選擇正確的檢查規則。
## 方法
1. 如果使用者提供了 language 參數,直接使用。
2. 否則,根據以下特徵判斷:
- 檔案副檔名(如果有)
- 關鍵字(def、function、class、import 等)
- 語法特徵(縮排、括號、分號)
3. 如果無法確定,呼叫 detect_language 工具。
## 常見語言的識別特徵
- Python: def, import, self, 縮排
- JavaScript: function, const, let, =>, {}
- Java: public class, void, ;
- Go: func, package, :=
## 輸出
{
"language": "python",
"framework": "fastapi",
"confidence": 0.95
}
## 錯誤處理
如果無法識別語言:
- 如果 confidence < 0.5,要求使用者提供語言資訊
- 不要猜測,錯誤的語言會導致錯誤的檢查
steps/04_security_check.md
# 步驟 4:安全性檢查
## 目標
找出程式碼中的安全性漏洞。
## 檢查清單
請參考 resources/security_checklist.md 的完整清單。
## 重點檢查項目
1. **注入攻擊**
- SQL 注入:字串拼接 SQL
- 命令注入:未過濾的系統命令
- XSS:未轉義的使用者輸入
2. **認證與授權**
- 硬編碼的密碼或 API key
- 缺少身份驗證
- 權限檢查不完整
3. **資料暴露**
- 敏感資訊寫入日誌
- 錯誤訊息洩漏內部資訊
- 不安全的資料傳輸
4. **加密**
- 使用弱加密演算法
- 硬編碼的加密金鑰
- 不安全的隨機數生成
## 嚴重程度判斷
參考 resources/severity_criteria.md。
## 輸出格式
每個安全問題都應該包含:
- 行號
- 漏洞類型
- 風險描述
- 攻擊場景(如果適用)
- 修正建議
- 修正範例
steps/06_generate_report.md
# 步驟 6:產出審查報告
## 目標
把所有發現的問題整合成結構化的報告。
## 流程
1. 彙整所有問題(風格、邏輯、安全、效能)
2. 依嚴重程度排序(high → medium → low)
3. 計算總分
4. 提取正面評價
5. 生成整體建議
6. 格式化輸出
## 評分公式
基礎分 10 分,扣分規則:
- 每個 high 問題扣 2 分
- 每個 medium 問題扣 1 分
- 每個 low 問題扣 0.5 分
- 最低 0 分
## 正面評價
從以下面向找出做得好的地方:
- 命名清晰度
- 錯誤處理
- 註解品質
- 程式碼結構
- 測試覆蓋
## 整體建議
用 2-3 句話總結:
1. 整體品質如何
2. 最需要優先處理的問題
3. 下一步建議
七、第七步:撰寫資源
resources/security_checklist.md
# 安全性檢查清單
## 注入攻擊
- [ ] SQL 查詢是否使用參數化?
- [ ] 系統命令是否過濾使用者輸入?
- [ ] HTML 輸出是否轉義?
- [ ] LDAP 查詢是否轉義?
- [ ] XML 解析是否禁用外部實體?
## 認證與授權
- [ ] 密碼是否使用強雜湊(bcrypt、argon2)?
- [ ] 是否有硬編碼的憑證?
- [ ] 敏感操作是否有權限檢查?
- [ ] Session 管理是否安全?
## 資料保護
- [ ] 敏感資料是否加密儲存?
- [ ] 傳輸是否使用 HTTPS?
- [ ] 日誌是否包含敏感資訊?
- [ ] 錯誤訊息是否洩漏內部資訊?
## 加密
- [ ] 是否使用已知安全的演算法?
- [ ] 金鑰是否安全儲存?
- [ ] 隨機數是否使用密碼學安全的來源?
- [ ] IV/Nonce 是否正確使用?
## 常見漏洞
- [ ] 是否有路徑遍歷風險?
- [ ] 是否有 SSRF 風險?
- [ ] 是否有反序列化風險?
- [ ] 是否有競態條件?
resources/severity_criteria.md
# 嚴重程度判斷標準
## High(高)
- 可被遠端利用的安全漏洞
- 導致資料洩漏或損壞
- 導致服務中斷
- 硬編碼的生產環境憑證
## Medium(中)
- 需要特定條件才能利用的漏洞
- 效能問題會影響使用者體驗
- 邏輯錯誤可能導致錯誤結果
- 缺少重要的錯誤處理
## Low(低)
- 程式碼風格問題
- 命名不夠清晰
- 缺少註解
- 可以改進但不影響功能
八、第八步:撰寫範例
examples/example_1_input.json
{
"code": "def get_user(user_id):\n query = f\"SELECT * FROM users WHERE id = {user_id}\"\n return db.execute(query)",
"language": "python",
"context": "這是使用者查詢功能",
"focus": "all"
}
examples/example_1_output.json
{
"summary": {
"language": "python",
"total_issues": 2,
"score": 6.0,
"severity_breakdown": {"high": 1, "medium": 1, "low": 0}
},
"issues": [
{
"id": "issue_1",
"severity": "high",
"category": "security",
"line": 2,
"title": "SQL 注入風險",
"description": "使用 f-string 拼接 SQL 查詢,使用者輸入未經處理直接進入查詢,可能導致 SQL 注入攻擊。攻擊者可以透過精心設計的 user_id 參數讀取、修改或刪除資料庫中的任意資料。",
"suggestion": "改用參數化查詢,讓資料庫驅動程式處理轉義。",
"example_fix": "def get_user(user_id):\n query = \"SELECT * FROM users WHERE id = ?\"\n return db.execute(query, (user_id,))"
},
{
"id": "issue_2",
"severity": "medium",
"category": "logic",
"line": 2,
"title": "使用 SELECT * 查詢",
"description": "使用 SELECT * 會取出所有欄位,包括可能不需要的敏感欄位(如密碼雜湊)。這會增加網路傳輸量,也可能無意間暴露敏感資料。",
"suggestion": "明確列出需要的欄位。",
"example_fix": "query = \"SELECT id, name, email FROM users WHERE id = ?\""
}
],
"positive_notes": [
"函式命名清晰,意圖明確",
"使用參數化查詢的修正方向正確"
],
"overall_suggestion": "這段程式碼有嚴重的 SQL 注入風險,必須優先修正。建議改用參數化查詢,並明確列出需要的欄位。修正後重新審查。"
}
examples/edge_cases.json
{
"edge_cases": [
{
"name": "無法識別語言",
"input": {"code": "some weird syntax"},
"expected_behavior": "要求使用者提供語言資訊"
},
{
"name": "程式碼過長",
"input": {"code": "...(超過 1000 行)..."},
"expected_behavior": "要求分批審查"
},
{
"name": "包含敏感資訊",
"input": {"code": "password = 'hardcoded_password'"},
"expected_behavior": "標記為 high severity,提醒移除"
},
{
"name": "沒有問題",
"input": {"code": "def add(a, b):\n return a + b"},
"expected_behavior": "回報 10 分,沒有問題"
}
]
}
九、第九步:實作 Skill
現在把上述設計轉成可執行的程式碼。
import json
import re
import os
class CodeReviewSkill:
"""程式碼審查 Skill"""
name = "code_review"
description = "審查程式碼,找出風格、邏輯、安全性與效能問題"
def __init__(self, llm_client, tools: dict, resources_dir: str = "skills/code_review/resources"):
self.llm = llm_client
self.tools = tools
self.resources_dir = resources_dir
self.resources = self._load_resources()
def _load_resources(self) -> dict:
"""載入資源"""
resources = {}
if not os.path.exists(self.resources_dir):
return resources
for filename in os.listdir(self.resources_dir):
path = os.path.join(self.resources_dir, filename)
with open(path, "r", encoding="utf-8") as f:
key = filename.replace(".md", "")
resources[key] = f.read()
return resources
def execute(
self,
code: str,
language: str = None,
context: str = None,
focus: str = "all",
severity_threshold: str = "low",
) -> dict:
"""執行審查"""
# 前置檢查
if len(code.split("\n")) > 1000:
return self._error_response("程式碼超過 1000 行,請分批審查。")
if self._contains_sensitive_info(code):
sensitive_warning = True
else:
sensitive_warning = False
# 步驟 1:識別語言
if not language:
language = self._detect_language(code)
if not language:
return self._error_response("無法識別程式語言,請提供 language 參數。")
# 步驟 2-5:執行各項檢查
style_issues = []
logic_issues = []
security_issues = []
performance_issues = []
if focus in ("all", "style"):
style_issues = self._check_style(code, language)
if focus in ("all", "logic"):
logic_issues = self._check_logic(code, language, context)
if focus in ("all", "security"):
security_issues = self._check_security(code, language)
if focus in ("all", "performance"):
performance_issues = self._check_performance(code, language)
# 合併所有問題
all_issues = (
security_issues
+ logic_issues
+ performance_issues
+ style_issues
)
# 根據嚴重程度門檻過濾
threshold_map = {"low": 0, "medium": 1, "high": 2}
threshold = threshold_map.get(severity_threshold, 0)
severity_map = {"low": 0, "medium": 1, "high": 2}
all_issues = [
issue for issue in all_issues
if severity_map.get(issue["severity"], 0) >= threshold
]
# 步驟 6:產出報告
report = self._generate_report(
language=language,
issues=all_issues,
code=code,
sensitive_warning=sensitive_warning,
)
return report
def _detect_language(self, code: str) -> str:
"""偵測程式語言"""
# 簡單的啟發式判斷
patterns = {
"python": [r"\bdef\b", r"\bimport\b", r"\bself\b", r":\s*$"],
"javascript": [r"\bfunction\b", r"\bconst\b", r"\blet\b", r"=>"],
"java": [r"\bpublic\s+class\b", r"\bvoid\b", r";\s*$"],
"go": [r"\bfunc\b", r"\bpackage\b", r":="],
"rust": [r"\bfn\b", r"\blet\s+mut\b", r"->"],
}
scores = {}
for lang, pats in patterns.items():
score = sum(1 for p in pats if re.search(p, code, re.MULTILINE))
if score > 0:
scores[lang] = score
if not scores:
return None
return max(scores, key=scores.get)
def _contains_sensitive_info(self, code: str) -> bool:
"""檢查是否包含敏感資訊"""
patterns = [
r"password\s*=\s*['\"][^'\"]+['\"]",
r"api_key\s*=\s*['\"][^'\"]+['\"]",
r"secret\s*=\s*['\"][^'\"]+['\"]",
r"sk-[a-zA-Z0-9]{20,}",
]
return any(re.search(p, code) for p in patterns)
def _check_style(self, code: str, language: str) -> list:
"""檢查程式碼風格"""
issues = []
lines = code.split("\n")
for i, line in enumerate(lines, 1):
# 檢查行長度
if len(line) > 100:
issues.append({
"id": f"style_{i}_long_line",
"severity": "low",
"category": "style",
"line": i,
"title": "行長度超過 100 字元",
"description": f"第 {i} 行長度為 {len(line)} 字元,超過建議的 100 字元。",
"suggestion": "將長行拆分為多行,提升可讀性。",
"example_fix": "",
})
# 檢查命名(Python 的 snake_case)
if language == "python":
camel_case = re.findall(r"\b[a-z]+[A-Z][a-zA-Z]*\b", line)
if camel_case and "class" not in line:
issues.append({
"id": f"style_{i}_naming",
"severity": "low",
"category": "style",
"line": i,
"title": "命名慣例不一致",
"description": f"Python 建議使用 snake_case,但發現 camelCase:{camel_case}",
"suggestion": "改用 snake_case 命名。",
"example_fix": "",
})
return issues
def _check_logic(self, code: str, language: str, context: str) -> list:
"""檢查邏輯問題(用 LLM)"""
prompt = f"""請分析以下 {language} 程式碼的邏輯問題。
程式碼:
{code}
{f"上下文:{context}" if context else ""}
請找出:
1. 潛在的 bug
2. 邊界條件未處理
3. 錯誤處理缺失
4. 邏輯錯誤
請以 JSON 陣列輸出,每個問題包含:
{{"severity": "high/medium/low", "line": 行號, "title": "標題", "description": "描述", "suggestion": "建議", "example_fix": "修正範例"}}
如果沒有問題,輸出 []。
只輸出 JSON,不要其他文字。"""
result = self.llm([{"role": "user", "content": prompt}])
return self._parse_issues(result, category="logic")
def _check_security(self, code: str, language: str) -> list:
"""檢查安全性問題(用 LLM + 檢查清單)"""
checklist = self.resources.get("security_checklist", "")
prompt = f"""請根據以下檢查清單,審查 {language} 程式碼的安全性問題。
檢查清單:
{checklist}
程式碼:
{code}
請找出所有安全性問題。以 JSON 陣列輸出,每個問題包含:
{{"severity": "high/medium/low", "line": 行號, "title": "標題", "description": "描述", "suggestion": "建議", "example_fix": "修正範例"}}
如果沒有問題,輸出 []。
只輸出 JSON,不要其他文字。"""
result = self.llm([{"role": "user", "content": prompt}])
return self._parse_issues(result, category="security")
def _check_performance(self, code: str, language: str) -> list:
"""檢查效能問題(用 LLM)"""
prompt = f"""請分析以下 {language} 程式碼的效能問題。
程式碼:
{code}
請找出:
1. 不必要的迴圈或計算
2. N+1 查詢問題
3. 記憶體浪費
4. 可以最佳化的地方
請以 JSON 陣列輸出,每個問題包含:
{{"severity": "high/medium/low", "line": 行號, "title": "標題", "description": "描述", "suggestion": "建議", "example_fix": "修正範例"}}
如果沒有問題,輸出 []。
只輸出 JSON,不要其他文字。"""
result = self.llm([{"role": "user", "content": prompt}])
return self._parse_issues(result, category="performance")
def _parse_issues(self, llm_result, category: str) -> list:
"""解析 LLM 回傳的問題"""
try:
if hasattr(llm_result, "choices"):
content = llm_result.choices[0].message.content
else:
content = str(llm_result)
# 嘗試提取 JSON
json_match = re.search(r"\[.*\]", content, re.DOTALL)
if not json_match:
return []
issues = json.loads(json_match.group(0))
# 標準化每個問題
normalized = []
for i, issue in enumerate(issues):
normalized.append({
"id": f"{category}_{i}",
"severity": issue.get("severity", "medium"),
"category": category,
"line": issue.get("line", 0),
"title": issue.get("title", ""),
"description": issue.get("description", ""),
"suggestion": issue.get("suggestion", ""),
"example_fix": issue.get("example_fix", ""),
})
return normalized
except (json.JSONDecodeError, AttributeError):
return []
def _generate_report(
self,
language: str,
issues: list,
code: str,
sensitive_warning: bool,
) -> dict:
"""產出最終報告"""
# 依嚴重程度排序
severity_order = {"high": 0, "medium": 1, "low": 2}
issues.sort(key=lambda x: severity_order.get(x["severity"], 3))
# 計算分數
score = 10.0
for issue in issues:
if issue["severity"] == "high":
score -= 2.0
elif issue["severity"] == "medium":
score -= 1.0
else:
score -= 0.5
score = max(0.0, round(score, 1))
# 統計
breakdown = {"high": 0, "medium": 0, "low": 0}
for issue in issues:
breakdown[issue["severity"]] += 1
# 正面評價
positive_notes = self._find_positive_notes(code, language)
# 整體建議
overall = self._generate_overall_suggestion(
issues, score, sensitive_warning
)
return {
"summary": {
"language": language,
"total_issues": len(issues),
"score": score,
"severity_breakdown": breakdown,
"sensitive_warning": sensitive_warning,
},
"issues": issues,
"positive_notes": positive_notes,
"overall_suggestion": overall,
}
def _find_positive_notes(self, code: str, language: str) -> list:
"""找出程式碼的優點"""
notes = []
# 檢查命名
if language == "python":
if re.search(r"\b[a-z_][a-z0-9_]*\b", code):
notes.append("變數與函式命名清晰")
# 檢查錯誤處理
if "try" in code and "except" in code:
notes.append("有適當的錯誤處理")
# 檢查註解
if '"""' in code or "'''" in code or "#" in code:
notes.append("有適當的註解")
# 檢查函式長度
if len(code.split("\n")) < 30:
notes.append("函式長度適中,易於閱讀")
return notes if notes else ["程式碼結構基本正確"]
def _generate_overall_suggestion(
self, issues: list, score: float, sensitive_warning: bool
) -> str:
"""生成整體建議"""
parts = []
if sensitive_warning:
parts.append("⚠️ 程式碼中發現疑似硬編碼的敏感資訊,請立即移除。")
if score >= 9:
parts.append("整體程式碼品質優秀,幾乎沒有問題。")
elif score >= 7:
parts.append("整體程式碼品質不錯,有少量需要改進的地方。")
elif score >= 5:
parts.append("程式碼有明顯問題,建議修正後再提交。")
else:
parts.append("程式碼有嚴重問題,必須優先處理。")
high_count = sum(1 for i in issues if i["severity"] == "high")
if high_count > 0:
parts.append(f"有 {high_count} 個高嚴重度問題需要優先修正。")
return " ".join(parts)
def _error_response(self, message: str) -> dict:
"""錯誤回應"""
return {
"summary": {
"language": None,
"total_issues": 0,
"score": 0,
"severity_breakdown": {"high": 0, "medium": 0, "low": 0},
},
"issues": [],
"positive_notes": [],
"overall_suggestion": message,
"error": message,
}
十、第十步:測試 Skill
設計完 Skill 之後,我們需要驗證它是否有效。
單元測試
def test_code_review_skill():
"""測試 CodeReviewSkill"""
skill = CodeReviewSkill(llm_client=call_llm, tools=TOOL_FUNCTIONS)
# 測試 1:正常案例
result = skill.execute(
code="def get_user(user_id):\n query = f\"SELECT * FROM users WHERE id = {user_id}\"\n return db.execute(query)",
language="python",
)
assert result["summary"]["language"] == "python"
assert result["summary"]["total_issues"] > 0
assert any(i["category"] == "security" for i in result["issues"])
assert result["summary"]["score"] < 8
# 測試 2:沒有問題的程式碼
result = skill.execute(
code="def add(a, b):\n return a + b",
language="python",
)
assert result["summary"]["total_issues"] == 0
assert result["summary"]["score"] == 10.0
# 測試 3:無法識別語言
result = skill.execute(code="some weird syntax")
assert "error" in result or result["summary"]["language"] is None
# 測試 4:程式碼過長
long_code = "\n".join([f"line_{i}" for i in range(1001)])
result = skill.execute(code=long_code, language="python")
assert "error" in result
def test_security_detection():
"""測試安全性問題偵測"""
skill = CodeReviewSkill(llm_client=call_llm, tools=TOOL_FUNCTIONS)
# SQL 注入
result = skill.execute(
code="query = f'SELECT * FROM users WHERE id = {user_id}'",
language="python",
)
assert any("SQL" in i["title"] or "注入" in i["title"] for i in result["issues"])
# 硬編碼密碼
result = skill.execute(
code="password = 'my_secret_password'",
language="python",
)
assert result["summary"]["sensitive_warning"] is True
def test_scoring():
"""測試評分邏輯"""
skill = CodeReviewSkill(llm_client=call_llm, tools=TOOL_FUNCTIONS)
# 完美程式碼應該得 10 分
result = skill.execute(
code="def add(a: int, b: int) -> int:\n return a + b",
language="python",
)
assert result["summary"]["score"] >= 9.0
# 有安全問題應該扣分
result = skill.execute(
code="password = 'hardcoded'\nquery = f'SELECT * FROM users WHERE id = {user_id}'",
language="python",
)
assert result["summary"]["score"] < 6.0
整合測試:與 Agent 結合
def test_code_review_skill_in_agent():
"""測試 Skill 在 Agent 中的使用"""
from agent import CompleteAgent
agent = CompleteAgent(user_id="test_user")
# 把 Skill 註冊到 Agent
skill = CodeReviewSkill(llm_client=call_llm, tools=TOOL_FUNCTIONS)
agent.register_skill(skill)
# 使用者請求審查程式碼
result = agent.run("""
請幫我審查這段程式碼:
def get_user(user_id):
query = f"SELECT * FROM users WHERE id = {user_id}"
return db.execute(query)
""")
assert "SQL" in result or "注入" in result
assert "安全" in result or "security" in result.lower()
十一、可重用性設計
一個好的 Skill 應該能跨專案、跨團隊重用。以下是提升可重用性的設計要點。
1. 不依賴特定上下文
Skill 不應該假設特定的專案結構、特定的資料庫、特定的框架。
# 錯誤例子:假設使用 Django
def check_style(code):
if "models.Model" in code:
...
# 正確例子:通用檢查
def check_style(code, language):
...
2. 參數化可變部分
class CodeReviewSkill:
def __init__(
self,
llm_client,
tools,
style_rules: dict = None, # 可自訂風格規則
security_rules: list = None, # 可自訂安全規則
scoring_weights: dict = None, # 可自訂評分權重
):
self.style_rules = style_rules or DEFAULT_STYLE_RULES
self.security_rules = security_rules or DEFAULT_SECURITY_RULES
self.scoring_weights = scoring_weights or {
"high": 2.0,
"medium": 1.0,
"low": 0.5,
}
3. 輸出格式標準化
輸出應該遵循固定的 Schema,方便其他 Skill 或系統解析。
# 所有 Skill 的輸出都應該包含
{
"success": bool, # 是否成功
"data": dict, # 主要輸出
"error": str | None, # 錯誤訊息
"metadata": { # 執行資訊
"skill_name": str,
"version": str,
"duration": float,
"tokens_used": int,
}
}
4. 版本管理
# config.yaml
name: code_review
version: 1.2.0
changelog:
- version: 1.0.0
date: 2026-01-01
changes: 初始版本
- version: 1.1.0
date: 2026-02-01
changes: 加入效能檢查
- version: 1.2.0
date: 2026-03-01
changes: 改進評分公式
十二、可組合性設計
Skill 可以呼叫其他 Skill,形成更複雜的能力。
組合範例:PR 審查流程
class PRReviewSkill:
"""PR 審查 Skill,組合多個子 Skill"""
name = "pr_review"
def __init__(self, llm_client, tools):
self.code_review = CodeReviewSkill(llm_client, tools)
self.test_coverage = TestCoverageSkill(llm_client, tools)
self.docs_check = DocumentationSkill(llm_client, tools)
def execute(self, pr_data: dict) -> dict:
"""執行完整的 PR 審查"""
results = {
"code_review": None,
"test_coverage": None,
"documentation": None,
}
# 1. 程式碼審查
for file in pr_data.get("files", []):
if file["type"] == "code":
results["code_review"] = self.code_review.execute(
code=file["content"],
language=file.get("language"),
)
# 2. 測試覆蓋率檢查
results["test_coverage"] = self.test_coverage.execute(
files=pr_data.get("files", [])
)
# 3. 文件檢查
results["documentation"] = self.docs_check.execute(
files=pr_data.get("files", [])
)
# 4. 整合結果
return self._merge_results(results)
def _merge_results(self, results: dict) -> dict:
"""整合所有子 Skill 的結果"""
all_issues = []
for key, result in results.items():
if result and "issues" in result:
for issue in result["issues"]:
issue["source"] = key
all_issues.append(issue)
# 計算總分
total_score = sum(
r["summary"]["score"]
for r in results.values()
if r and "summary" in r
) / max(len([r for r in results.values() if r]), 1)
return {
"summary": {
"total_issues": len(all_issues),
"overall_score": round(total_score, 1),
},
"issues": all_issues,
"details": results,
}
十三、常見的設計陷阱
1. 範圍太大
# 錯誤例子:一個 Skill 做太多事
class EverythingSkill:
def execute(self, code):
# 審查 + 測試 + 部署 + 監控...
...
# 正確例子:拆成多個 Skill
class CodeReviewSkill: ...
class TestGenerationSkill: ...
class DeploymentSkill: ...
2. 輸出格式不固定
# 錯誤例子:有時回傳 list,有時回傳 dict
def execute(self, code):
if len(code) > 100:
return ["錯誤:程式碼太長"]
return {"issues": [...]}
# 正確例子:永遠回傳相同結構
def execute(self, code):
return {
"success": True/False,
"data": {...},
"error": None,
}
3. 忽略錯誤處理
# 錯誤例子:沒有錯誤處理
def execute(self, code):
result = self.tools["lint_code"](code) # 可能失敗
return result
# 正確例子:完整的錯誤處理
def execute(self, code):
try:
result = self.tools["lint_code"](code)
except Exception as e:
return self._error_response(f"Lint 失敗:{e}")
...
4. 沒有範例
沒有範例的 Skill,LLM 很難知道「好的輸出」長什麼樣子。
至少要提供:
- 1 個正常案例
- 1 個邊界案例
- 1 個失敗案例
5. 沒有測試
Skill 的品質需要被驗證。
沒有測試的 Skill,上線後可能帶來災難。
十四、總結:從需求到可重用的 Skill
讓我們回顧這一篇的核心:
- 需求分析:先問「解決什麼問題、誰會用、邊界在哪」。
- 流程設計:把任務拆解成可執行的步驟。
- 輸入輸出定義:用 Schema 確保一致性與可組合性。
- 檔案結構:
SKILL.md+steps/+resources/+examples/+config.yaml。 - 主指令:描述、何時使用、輸入輸出、流程、規範、邊界。
- 步驟說明:每個步驟的詳細做法與輸出格式。
- 資源:檢查清單、評分標準、範本。
- 範例:正常案例、邊界案例、失敗案例。
- 實作:把設計轉成可執行的程式碼。
- 測試:單元測試與整合測試。
- 可重用性:不依賴特定上下文、參數化、標準化輸出、版本管理。
- 可組合性:Skill 可以呼叫其他 Skill。
- 常見陷阱:範圍太大、輸出不一致、忽略錯誤、沒有範例、沒有測試。
設計一個好的 Skill,需要的不只是寫 Prompt,而是系統性的思考:
從需求到流程,從結構到實作,從測試到迭代。
當你掌握了這個方法,你就能為任何領域設計出高品質的 Skill。
不過,設計完 Skill 之後,下一個問題是:你怎麼知道它真的好用?
這就是下一篇要談的主題:Skill 的組合與編排。
我們會談如何讓多個 Skill 協同工作,如何處理依賴關係,以及如何設計一個 Skill 編排系統。
下一篇預告
《Skill 的組合與編排:讓 Skill 呼叫 Skill》
我們會談 Skill 的組合模式、依賴管理、錯誤傳播、以及如何設計一個 Skill 編排系統,讓多個 Skill 協同完成複雜任務。