Cấu trúc file SKILL.md: tái sử dụng instruction cho Claude Code và AI agent — hoatq.dev

cat blog/.md

Cấu trúc file SKILL.md: tái sử dụng instruction cho Claude Code và AI agent

date: tags: ai, claude-code, developer-tools, productivity, workflow

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. Cái hay là format này chung giữa Claude Code SDK và Craft Agent, nghĩa là một skill viết đúng chuẩn chạy được ở cả hai. 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

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.

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.ts thì skill test gợi ý chạy).

Với Claude Code, skill nằm ở ba cấp: global (~/.agents/skills/), workspace, và project ({root}/.agents/skills/). Khi invoke, agent check theo thứ tự đó — workspace override global, project override workspace. 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``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.

requiredSources (Craft Agent). Array source slug sẽ được auto-enable khi skill được invoke. Ví dụ skill /review-pr cần GitHub source, không cần user click enable thủ công:

requiredSources:
  - github
  - linear

Nếu source chưa authenticate, nó bị skip — không lỗi, agent vẫn chạy nhưng tool tương ứng không có sẵn. Đây là field đặc thù Craft Agent, Claude Code bỏ qua cũng không sao.

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.

Skill precedence và override

Đây là chi tiết ít ai để ý nhưng rất hữu dụng. Khi invoke /commit:

  1. Craft Agent check workspace skill trước (~/.craft-agent/workspaces/{id}/skills/commit/).
  2. Nếu không có, dùng SDK skill mặc định đi kèm Claude Code.

Tức là bạn có thể override skill built-in bằng cách tạo file cùng slug trong workspace. 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 mặc định của Claude Code, và convention team thắng.

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õ.

Craft Agent có skill_validate tool, check syntax + required fields:

skill_validate({ skillSlug: "commit" })

Không có tool đó thì tự check: mở file bằng bất kỳ YAML parser nào, verify frontmatter parse được, glob patterns hợp lệ, alwaysAllow chỉ chứa tên tool thực sự tồn tại.

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-helper là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 /commit chạy 2 lần không tạo 2 commit — check git status trướ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.

// reactions


cat comments.log


hoatq@dev : ~/blog $