cat blog/.md
Cấu trúc file SKILL.md: tái sử dụng instruction cho Claude Code và AI agent
Sau vài tháng dùng Claude Code, tôi nhận ra mình cứ gõ đi gõ lại cùng những đoạn hướng dẫn: “commit theo conventional commits, không co-author”, “review focus security + performance”, “tạo PR với template của team…”. Copy-paste từ Notion rồi lại copy-paste. Sau lần thứ mười tôi mới chịu đọc tài liệu về skills — và đó là một trong những thứ thay đổi cách tôi làm việc với AI agent nhiều nhất.
Skill bản chất chỉ là một file Markdown có frontmatter. Format đơn giản nhưng từng field có vai trò riêng, viết sai một chỗ là skill không bao giờ được Claude Code gọi. Bài này là những gì tôi rút ra khi thiết kế skill dùng hằng ngày.
Skill là gì, và khác gì với prompt / command / subagent
Khi bạn paste một đoạn hướng dẫn vào chat, nó chỉ sống trong đúng cuộc trò chuyện đó. Mở chat mới là bay. Còn skill là một file được lưu trên máy, invoke bằng slash command (/commit, /review-pr), và được inject vào context của agent mỗi khi được gọi.
Sau khi tổ chức lại folder .claude/, mình thấy có bốn khái niệm dễ trộn lẫn — tách rõ thì design tốt hơn:
| Khái niệm | Nơi đặt | Khi nào load | Vai trò |
|---|---|---|---|
| Rule | .claude/rules/<name>.md | Reference từ skill/agent/command | Single source of truth — quy ước, conventions |
| Skill | .claude/skills/<name>/SKILL.md | Auto-load khi description match với task | Atomic capability — HOW làm 1 procedure |
| Command | .claude/commands/<name>.md | Load khi user gõ /<name> | User-triggered slash entry — luôn explicit |
| Subagent | .claude/agents/<name>/AGENT.md | Spawn qua Task tool | Role/persona, isolated context, long workflow |
Ví dụ cụ thể:
/commitmà user gõ thẳng → đặt ởcommands/(luôn explicit invoke).unit-testingmà Claude tự pick khi đang viết test → đặt ởskills/(auto-trigger by description).ba(Business Analyst persona) chạy workflow dài → đặt ởagents/.coding-guide(KISS/DRY/SRP) — quy ước chung — đặt ởrules/, mọi thứ khác link tới.
Phân biệt này quan trọng vì: skill chứa persona (vd “Bạn là Business Analyst…”) thì sai chỗ — đó là agent. Command 700 dòng dạy KISS/DRY thì sai chỗ — đó là skill. Lúc đầu mình trộn lẫn, dẫn đến file 700+ dòng “god skill” — phải tách ra mới sạch.
Lợi ích rõ nhất:
- Không phải nhớ đã dặn gì với AI, tất cả nằm trong file.
- Chia sẻ được với team (commit vào repo).
- Phiên bản hóa được — skill là code, có git history.
- Chạy được tự động theo
globs(ví dụ: mở file.test.tsthì skill test gợi ý chạy).
Với Claude Code, skill nằm ở hai cấp: global (~/.claude/skills/) và project ({root}/.claude/skills/). Khi invoke, project override global. Rất giống cách Git config hoạt động.
Anatomy của một SKILL.md
File có hai phần: frontmatter YAML ở đầu, và body Markdown ở dưới.
---
name: "Conventional Commit"
description: "Tạo commit message theo chuẩn Conventional Commits dựa trên diff hiện tại"
globs: ["**/*"]
alwaysAllow: ["Bash"]
requiredSources:
- github
---
# Conventional Commit Skill
Bạn là assistant tạo commit message. Khi được gọi:
1. Chạy `git status` và `git diff --staged` để hiểu thay đổi
2. Phân loại theo type: feat, fix, refactor, docs, test, chore, perf
3. Viết message ngắn gọn ở hiện tại đơn (imperative mood)
4. Không thêm Co-Authored-By trừ khi user yêu cầu
## Format
<type>(<scope>): <subject>
<body nếu cần giải thích "why">
## Quy tắc
- Subject <= 72 ký tự, không dấu chấm cuối
- Body dùng bullet khi có nhiều điểm
- Không dùng emoji trong message
Đơn giản vậy thôi, nhưng mỗi field frontmatter đều có ý nghĩa. Đi qua từng cái.
Frontmatter: bộ “hợp đồng” giữa skill và agent
name (bắt buộc). Tên hiển thị trên UI. Viết rõ, không phải slug — slug là tên folder chứa file.
description (bắt buộc). Một đến hai câu. Đây là thứ agent dùng để quyết định có gọi skill hay không khi người dùng nói chung chung. Viết description tệ → skill không bao giờ được gọi. Viết tốt: “Dùng khi user yêu cầu review PR, focus vào security và breaking changes”.
globs (tuỳ chọn). Array pattern file. Khi agent đang làm việc với file match pattern, skill được gợi ý hoặc auto-activate.
globs:
- "*.test.ts"
- "**/__tests__/**"
- "spec/**/*.rb"
Pattern dùng để narrow scope. Skill refactor React không nên áp cho file Python — globs: ["**/*.tsx", "**/*.jsx"] là hợp lý.
alwaysAllow (tuỳ chọn). Array tên tool được pre-approve. Bình thường khi skill gọi Bash, UI sẽ hỏi xác nhận. Với skill chạy thường xuyên (commit, run tests), list tool vào đây để khỏi click confirm mỗi lần.
alwaysAllow:
- "Bash"
- "Read"
Đừng abuse field này — chỉ allow tool thực sự cần. Pre-approve Write cho một skill download file có thể dẫn đến skill đó ghi đè file khác mà không hỏi.
tools (tuỳ chọn, agent đặc biệt). Một số setup cho phép thu hẹp danh sách tool ngay trong frontmatter — ví dụ skill chỉ cần Read/Grep thì khai báo cụ thể, agent invoke skill đó sẽ không thấy Bash. Tuỳ field này có hiệu lực hay không phụ thuộc vào nơi skill chạy; đặt sẵn vẫn hữu ích vì là tài liệu cho người đọc.
Body: nơi skill thực sự “dạy” agent
Frontmatter chỉ là metadata. Logic thực nằm ở body Markdown. Đây là context được inject khi skill active, nên viết như đang brief một junior developer thông minh nhưng chưa biết codebase.
Bốn mục mình thường dùng:
Role / mục đích. Một đoạn ngắn nói skill này làm gì. Agent đọc nó để định hướng.
Steps / workflow. Đánh số hoặc bullet các bước cụ thể. Càng rõ càng tốt. Ví dụ trong skill commit trên: “chạy git status → phân loại type → viết subject → viết body nếu cần”.
Rules / ràng buộc. Liệt kê các “do” và “don’t”. Đây là nơi đưa convention team vào: không co-author, subject <= 72 ký tự, body bullet thay vì đoạn văn.
Examples. Ví dụ cụ thể. AI học từ ví dụ cực nhanh. Show một commit tốt và một commit tệ cạnh nhau, kèm chú thích.
Một cái bẫy tôi hay mắc: viết body dài như tiểu thuyết. Skill 500 dòng = mỗi lần invoke agent đốt 500 dòng context. Giữ ngắn gọn, reference tài liệu ngoài thay vì nhét tất cả vào — đây là lý do mình tách rules/ riêng: skill chỉ link rules/coding-guide.md thay vì copy nội dung KISS/DRY/SRP vào skill.
Tách ngôn ngữ: file tiếng Anh, output tiếng Việt
Một quy tắc quan trọng tôi rút ra sau lần đầu chạy /ba cho team Việt: file SKILL.md viết bằng tiếng Anh, nhưng agent output ra tiếng Việt. Lý do:
- File SKILL.md là reference doc mà agent đọc — cần ổn định, dễ search, không lẫn ngữ cảnh dịch.
- Output (chat reply, file artifact như
requirements.md) là user-facing — phải tiếng Việt để PM/team đọc được.
Pattern trong file:
## Output Language Rules
| Element | Language |
|---------|----------|
| Section headers, body text | Vietnamese |
| AC IDs (AC-001), status keywords (PASS/FAIL) | English (immutable) |
| Code blocks, file paths, command output | English / raw |
| UI text in code (toast, validation) | Japanese (per project convention) |
| Inline code comments | English (default) or Japanese (domain) — **NEVER Vietnamese** |
Templates literal mà agent paste nguyên văn vào artifact thì giữ tiếng Việt:
> **US-1:** Là [vai trò], tôi muốn [tính năng], để [lợi ích]
> AC-001: Cho [bối cảnh], khi [hành động], thì [kết quả mong đợi]
Còn comments code và identifier thì luôn tiếng Anh — code không nên có ba ngôn ngữ trộn lẫn.
Skill precedence và override
Đây là chi tiết ít ai để ý nhưng rất hữu dụng. Khi invoke /commit:
- Claude Code check skill ở project trước (
{root}/.claude/skills/commit/). - Nếu không có, fallback xuống global (
~/.claude/skills/commit/). - Nếu cả hai đều không có, dùng skill mặc định (nếu có) hoặc báo unknown command.
Tức là bạn có thể override skill global bằng cách tạo file cùng slug trong project. Mình dùng trick này để thêm quy tắc riêng team vào /commit — convention của team sống cạnh convention cá nhân ở ~/.claude/skills/, và convention team thắng khi làm trong repo đó.
Cấu trúc trên disk:
skills/
└── commit/
├── SKILL.md # file chính
├── icon.svg # icon hiển thị UI
└── examples.md # tuỳ chọn, reference từ SKILL.md
Icon nhỏ nhưng quan trọng với team — nhìn UI biết skill nào là skill nào mà không cần đọc tên.
Validate trước khi dùng
Một trong những sai sót dễ mắc: typo trong frontmatter, YAML invalid, glob pattern sai. Skill có thể im lặng không load mà không báo lỗi rõ.
Cách tự check nhanh:
# Parse frontmatter để verify YAML hợp lệ
python3 -c "
import yaml, sys
with open('.claude/skills/commit/SKILL.md') as f:
content = f.read()
# Extract frontmatter giữa --- ---
parts = content.split('---', 2)
fm = yaml.safe_load(parts[1])
print('OK:', fm.get('name'))
"
Hoặc đơn giản hơn: gõ /commit thử trong Claude Code — nếu skill không xuất hiện trong autocomplete hoặc trigger sai, kiểm tra lại frontmatter (thường lỗi YAML indent hoặc thiếu name/description).
Thiết kế skill tốt trông như thế nào
Sau nhiều lần viết, mình rút ra vài nguyên tắc:
- Một skill làm một việc. Đừng viết
/dev-helperlàm đủ thứ. Tách thành/commit,/review,/test-gen— composable, dễ maintain. - Description viết như đang mô tả tính năng. Agent match description với user intent. “Review code changes focus on security” tốt hơn “code review skill”.
- Không hardcode đường dẫn máy cá nhân. Skill share được với team, path
/Users/kyro/...sẽ chết ngay ở máy đồng nghiệp. - Idempotent khi chạy lại. Skill
/commitchạy 2 lần không tạo 2 commit — checkgit statustrước. - Fail loud. Khi skill thiếu source hoặc tool, in message rõ thay vì âm thầm chạy tiếp.
Khi nào thì làm skill, khi nào thì viết prompt
Không phải mọi hướng dẫn đều nên thành skill. Rule-of-thumb của mình:
- Lặp lại từ 3 lần trở lên → skill.
- Có thể áp dụng trong nhiều phiên khác nhau → skill.
- Chỉ dùng một lần, context-specific → prompt trực tiếp.
- Chung cho cả project, không phải cá nhân → commit vào
.agents/skills/để team dùng chung.
Skill là một loại code — có format, có version, có cost (context window). Đối xử với nó như helper function trong codebase: viết cẩn thận, đặt tên rõ, đừng viết khi chưa cần.
Sau 3 tháng dùng, repo của tôi có khoảng 8 skill: /commit, /review-pr, /write-test, /explain-diff, /update-claude-md, /gen-migration, /rfc-draft, /debug-trace. Mỗi cái vài chục dòng SKILL.md. Không có skill nào lấp lánh công nghệ — chúng chỉ ghi lại những gì tôi từng gõ đi gõ lại với AI, giờ không cần gõ nữa.
cat comments.log