設計一個可重用的 Skill:以程式碼審查為例

從需求分析到實作與測試,用程式碼審查為例,完整走一遍設計可重用、可組合、可測試 Skill 的流程。

設計一個可重用的 Skill:以程式碼審查為例

上一篇我們拆解了 Skill 的結構:指令、工具、資源與範例。

但理解結構不等於會設計。

這一篇,我們要用一個具體的例子——程式碼審查(Code Review)——從需求分析開始,逐步設計一個完整的 Skill。
你會看到如何定義範圍、設計流程、選擇工具、撰寫指令、加入範例,以及如何讓這個 Skill 可重用、可組合、可測試。

Requirements → Design → Implement → Test

一、第一步:需求分析

設計任何 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. 邏輯分析找出潛在 bugLLM 推理邏輯問題清單
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?

  1. 一致性:每次輸出都遵循相同格式。
  2. 可組合:其他 Skill 可以解析這個輸出。
  3. 可測試: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 協同完成複雜任務。