Lê Duy Khương (Daniel)

Chuỗi: docs-as-code

Năng suất & công cụ dev

Tự động hoá Docs-as-Code — để CI bắt lỗi tài liệu trước con người

Khi tài liệu đã nằm trong Git, bước tiếp theo là để máy canh giữ nó: validate link, schema, traceability, docs site, metrics và rollout theo giai đoạn cho đội lớn.

2026-06-2910 phút đọcVI

Bài này khép vòng vận hành: để CI bắt lỗi tài liệu, đo sức khỏe docs và mở rộng docs-as-code cho đội lớn.

Tóm tắt Lesson 5: tự động hóa docs-as-code bằng CI, docs site và rollout theo giai đoạn


Khi CI bắt được thứ mà mắt người bỏ sót

Ba tháng sau sự cố hoàn tiền của Minh (Lesson 1), đội Payments đã thêm CI vào quy trình tài liệu. Câu chuyện sau đó xảy ra đúng như họ kỳ vọng — chỉ theo hướng khác.

Một reviewer kỳ cựu của đội đang duyệt PR cập nhật hướng dẫn tích hợp hoàn tiền cho mini-app Booking. Anh đọc kỹ nội dung, kiểm tra tất cả các trường API, để lại vài nhận xét nhỏ, rồi nhấn Approve. Nhưng ngay lúc đó, CI bật đèn đỏ — validate-skills.sh báo lỗi: một link trong phần "Xem thêm" trỏ tới file refund-v1-legacy.md đã bị xoá từ sprint trước.

Reviewer ngạc nhiên. Anh đọc tài liệu hai lần mà không để ý một link ở cuối trang. Nếu không có CI, link gãy đó sẽ lọt vào nhánh chính — và nhà phát triển nào đó trong đội Booking sẽ bấm vào nó, nhận thông báo 404, rồi mất niềm tin vào toàn bộ hướng dẫn tích hợp.

Lần này không có sự cố. Chỉ có một dòng lỗi đỏ, một lần sửa nhỏ, và một PR được merge đúng giờ.

💡 Bài học: CI không thay thế reviewer — nó giải phóng reviewer. Máy lo mấy thứ máy móc, người lo thứ chỉ con người làm được: tài liệu này có đúng, có rõ, có đủ không?


Mục tiêu học tập

Sau bài này, bạn sẽ:

  • Hiểu vì sao nên để CI (máy tự chạy kiểm tra mỗi khi có thay đổi) canh tài liệu, và khi nào CI tạo ra giá trị thật sự.
  • Biết 3 loại kiểm tra tự động, và biết cách chạy từng loại ngay trên máy trước khi mở PR.
  • Đọc và hiểu workflow GitHub Actions (dịch vụ chạy tự động việc của bạn mỗi khi có thay đổi trên GitHub) trong repo này từng dòng.
  • Thiết lập được môi trường để bài tập CI chạy được — từ fork cho đến bật Actions.
  • Nắm vài nguyên tắc giữ docs-as-code hoạt động tốt khi đội đông lên, kèm công cụ theo dõi sức khỏe tài liệu.

1. Vì sao để máy kiểm tra giúp

Ở Lesson 4, bạn đã biết tại sao tài liệu cần đi qua pull request (PR — yêu cầu hợp nhất để đồng đội xem trước khi gộp vào nhánh chính) và được reviewer kiểm. Nhưng người review dù kỹ đến đâu cũng hay bỏ sót một loại lỗi: những thứ lặp đi lặp lại, cơ học, không cần phán đoán.

Bạn không thể trông mong reviewer nhớ kiểm từng link trong một tài liệu dài 400 dòng. Bạn không nên để anh ta mất 5 phút đếm cột bảng Markdown để xem có lệch không. Và nếu quy ước frontmatter có 5 trường bắt buộc, đừng hy vọng ai đó ngồi đọc từng file để đảm bảo không trường nào bị thiếu.

Đây là việc của máy, không phải của người.

💡 Nguyên tắc cốt lõi: Máy bắt lỗi vặt, người lo phán đoán. CI là người gác cổng không bao giờ mệt, không bao giờ nhận email quan trọng đúng lúc đang review, và không bao giờ "để qua cho nhanh".

Khi CI lo phần cơ học, reviewer được rảnh tay tập trung vào câu hỏi thật: Tài liệu này có đúng với sản phẩm không? Có rõ với người đọc không? Có đủ thông tin để ai đó tự làm theo không?


2. Ba loại kiểm tra và cách chạy từng loại

Bộ kiểm tra tài liệu cơ bản chỉ gồm 3 loại. Bắt đầu với một loại là đã có giá trị — thêm dần sau.

Kiểm traBắt lỗi gìCông cụ phổ biến
LintĐịnh dạng Markdown sai (thiếu dòng trống, bảng lệch cột, tiêu đề nhảy cấp...)markdownlint-cli2
Link checkLink nội bộ hoặc ngoài bị gãy (như incident đầu bài)lychee, markdown-link-check
FrontmatterThiếu trường bắt buộc (title, status, name, description...)Script tự viết — repo này có sẵn

2.1 Lint Markdown với markdownlint

Cài công cụ một lần, dùng mãi:

npm install -g markdownlint-cli2

Tạo file cấu hình .markdownlint.json ở gốc repo để kiểm soát quy tắc nào bật, quy tắc nào tắt:

{
  "default": true,
  "MD013": false,
  "MD033": false
}

Giải thích hai dòng tắt: MD013 là giới hạn độ dài dòng (thường tắt vì tài liệu hay có bảng và link dài), MD033 là cấm HTML inline (tắt nếu bạn dùng callout HTML đôi khi). Các quy tắc còn lại (default: true) đều bật.

Chạy kiểm tra toàn bộ Markdown trong repo:

npx markdownlint-cli2 "**/*.md"

Nếu có lỗi, công cụ in đường dẫn file, số dòng, và mã quy tắc — ví dụ docs/payments/refund-api.md:45 MD041/first-line-heading. Sửa rồi chạy lại cho tới khi không còn lỗi.

lychee là công cụ kiểm link viết bằng Rust — nhanh, chạy offline cho link nội bộ, và hiểu nhiều định dạng:

# Chạy qua npx (không cần cài toàn cục)
npx lychee --no-progress "**/*.md"

Cờ --no-progress bỏ thanh tiến trình để output gọn hơn trong CI log. Nếu muốn bỏ qua kiểm link ngoài Internet (khi làm offline), thêm --offline.

Một thay thế nhẹ hơn nếu dự án chưa có Node: markdown-link-check:

npm install -g markdown-link-check
markdown-link-check docs/**/*.md

Chọn cái nào cũng được — điều quan trọng là kiểm link, không phải dùng công cụ nào.

2.3 Kiểm frontmatter với script của repo

Repo này có hai script kiểm frontmatter sẵn, chạy được ngay không cần cài thêm gì:

scripts/validate-skills.sh — kiểm chất lượng của mỗi skill (tài liệu năng lực đóng gói cho trợ lý AI). Nó làm ba việc theo thứ tự:

  1. Kiểm .claude-plugin/plugin.json có phải JSON hợp lệ không.
  2. Kiểm .claude-plugin/marketplace.json có phải JSON hợp lệ không.
  3. Duyệt qua mọi skills/*/SKILL.md và xác nhận mỗi file có đủ hai trường frontmatter name:description:.

Nếu bất kỳ kiểm tra nào thất bại, script in dòng ERR và thoát với mã lỗi khác 0 — tức là báo "có vấn đề, dừng lại".

bash scripts/validate-skills.sh

Kết quả bình thường trông như thế này:

==> Validating plugin manifest
  OK  .claude-plugin/plugin.json is valid JSON
  OK  .claude-plugin/marketplace.json is valid JSON
==> Validating skills
  OK  docs-as-code-intro
==> All checks passed

scripts/check-contracts.sh — kiểm frontmatter của mọi file *.contract.md (hợp đồng API giữa các đội). Mỗi contract phải khai báo đủ năm trường: id, version, status, provider, consumers. Thiếu một trường là contract không đáng tin cậy — script thoát lỗi và liệt kê trường thiếu.

bash scripts/check-contracts.sh

Thử cố ý xoá trường version trong một contract rồi chạy lại — bạn sẽ thấy script báo đúng trường nào thiếu. Đây là cách nhanh nhất để hiểu một công cụ: phá nó, quan sát phản ứng, sửa lại.

📌 Thứ tự ưu tiên: Bắt đầu với validate-skills.sh — nó chạy được ngay và bao gồm kiểm JSON + frontmatter skills. Thêm check-contracts.sh khi repo bắt đầu có nhiều contract. Thêm markdownlint và lychee khi đội quyết định chuẩn hóa định dạng.


3. Chạy tự động mỗi pull request với GitHub Actions

Chạy script ở máy thì tốt — nhưng người ta hay quên. Giải pháp: để máy chủ tự chạy mỗi khi có PR. Đó là việc của GitHub Actions.

GitHub Actions là gì? Đây là dịch vụ tích hợp sẵn trong GitHub, cho phép bạn định nghĩa "khi X xảy ra, hãy chạy Y". X có thể là mở PR, push code lên nhánh, hay đơn giản là theo lịch. Y là một chuỗi bước: lấy code về, cài công cụ, chạy script. Tất cả được viết trong một file YAML đặt trong thư mục .github/workflows/.

File workflow thật của repo này

Mở .github/workflows/validate.yml và bạn sẽ thấy:

name: validate
 
on:
  pull_request:
  push:
    branches: [main]
 
jobs:
  validate-skills:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.x"
      - name: Validate plugin manifest + skills
        run: bash scripts/validate-skills.sh
      - name: Verify contracts have required frontmatter
        run: bash scripts/check-contracts.sh

Đọc file này như một câu văn: "Mỗi khi có pull request — hoặc có ai đó push vào nhánh main — hãy khởi một máy chủ Ubuntu, lấy toàn bộ code về, cài Python 3, rồi lần lượt chạy validate-skills.shcheck-contracts.sh."

Vài điểm đáng chú ý:

  • name: validate — tên của workflow, hiển thị trên giao diện GitHub khi bạn xem tab Actions.
  • on: pull_request — trigger mỗi PR, dù PR đó nhắm vào nhánh nào. Không cần cấu hình thêm.
  • jobs: validate-skills — tên job. GitHub hiển thị tên này trong danh sách "checks" ở PR.
  • actions/checkout@v4 — action chính thức của GitHub để lấy code về máy CI; @v4 là phiên bản.
  • Bước cài Python là cần thiết vì validate-skills.sh gọi python3 để kiểm tra JSON. Script tự phát hiện nếu Python không có (command -v python3) và cảnh báo thay vì lỗi — nhưng trên CI thì luôn muốn có Python để kiểm tra đầy đủ.

Kết quả hiển thị ngay trong PR

Sau khi bạn mở PR, GitHub tự chạy workflow. Kết quả hiện trong phần Checks ở cuối trang PR:

  • Xanh — tất cả kiểm tra qua. Reviewer thấy CI xanh thì biết không cần lo phần cơ học, tập trung vào nội dung.
  • Đỏ — có lỗi. GitHub chặn nút Merge và hiển thị chi tiết bước nào thất bại, output là gì. Tác giả sửa, push thêm commit, CI tự chạy lại.

⚠️ Quan trọng: Để CI chặn được merge, bạn cần bật branch protection cho nhánh main và đánh dấu job validate-skills là required check. Lesson 4 đã giới thiệu branch protection — đây là nơi nó phát huy tác dụng thật sự. Không có branch protection, CI chỉ là cảnh báo tùy chọn, không phải rào cản.

Giờ tài liệu sai không thể lọt vào nhánh chính — dù người viết quên kiểm, dù reviewer đọc lướt. Đây chính là mảnh ghép số 4 ("CI tự kiểm tra") trong Lesson 1, giờ đã thành hình cụ thể và đang chạy trong repo này.


4. Thiết lập môi trường để bài tập chạy được

Trước khi vào bài tập, bạn cần 3 bước thiết lập. Nếu bạn đã từng làm việc với GitHub, hai bước đầu có thể đã xong.

Bước 4.1 — Tạo tài khoản GitHub

Truy cập github.comSign up. Chọn tên người dùng dễ nhớ (bạn sẽ dùng nó lâu dài). Xác nhận email. Xong.

Bước 4.2 — Fork repo về tài khoản của bạn

Fork (tạo bản sao repo vào tài khoản của mình) là cách đóng góp vào dự án bạn không có quyền ghi trực tiếp.

  1. Mở repo docs-as-code-kit trên GitHub.
  2. Nhấn nút Fork ở góc trên phải → chọn tài khoản của bạn làm đích.
  3. GitHub tạo một bản sao tại github.com/<tên-của-bạn>/docs-as-code-kit.
  4. Clone bản sao đó về máy:
git clone https://github.com/<tên-của-bạn>/docs-as-code-kit.git
cd docs-as-code-kit

Từ đây, mọi thay đổi bạn push lên sẽ vào fork của bạn, không ảnh hưởng repo gốc.

Bước 4.3 — Bật GitHub Actions trong fork

GitHub mặc định tắt Actions trong fork để tránh chạy tốn tài nguyên khi không cần.

  1. Trong fork của bạn, vào tab Actions (thanh menu trên cùng của repo).
  2. GitHub hỏi xác nhận bạn muốn bật workflows — nhấn I understand my workflows, go ahead and enable them.
  3. Xong. Lần sau mở PR, Actions sẽ tự chạy.

📌 Nếu bạn không bật Actions, CI sẽ không chạy khi bạn mở PR — và bài tập phần CI sẽ không có kết quả để quan sát. Bước này nhỏ nhưng quan trọng.


5. Khi đội lớn dần

Docs-as-code chạy tốt với 3 người. Khi thành 30 người, vài nguyên tắc đơn giản giữ nó không loạn.

Tài liệu sống cạnh thứ nó mô tả. Tài liệu của mini-app Payments thì nằm trong mini-apps/payments/docs/ — không phải trên Confluence, không phải trong một thư mục docs/ chung mà mọi người đổ vào. Ai sửa API của Payments thì thấy ngay tài liệu Payments nằm cạnh đó, dễ cập nhật cùng lúc.

Mỗi mảng có người sở hữu. Dùng CODEOWNERS (đã học ở Lesson 4) để mỗi thư mục tài liệu có một đội chịu trách nhiệm. Không có "tài liệu vô chủ" — thứ không ai sở hữu là thứ không ai cập nhật.

Áp dụng từ từ, đừng ép một lần. Bắt đầu với một đội, một loại tài liệu. Khi họ thấy lợi — reviewer ít phải lo lỗi vặt, PR merge nhanh hơn — các đội khác sẽ muốn theo. Ép cả tổ chức đổi trong một tuần thường thất bại vì không ai thấy lý do.

Theo dõi vài con số đơn giản để biết sức khỏe tài liệu:

Chỉ sốCâu hỏi nó trả lờiCách đo đơn giản
Độ phủ (coverage)Bao nhiêu phần đã có tài liệu?Đếm thư mục có docs/ vs tổng thư mục
Độ tươi (freshness)Tài liệu sửa lần cuối cách đây bao lâu?git log --since="90 days ago" -- docs/
Tỉ lệ đóng gópCó bao nhiêu người thật sự sửa tài liệu?git shortlog -sn -- docs/

Không cần công cụ phức tạp — ba lệnh trên chạy được ngay. Mục tiêu chỉ là phát hiện một mảng tài liệu đang bị bỏ quên trước khi nó trở thành documentation rot (tài liệu cũ dần và lệch khỏi sản phẩm đến mức gây hiểu sai).

Khi nào cần một trang web tài liệu (docs site)?

💡 Câu trả lời ngắn: chưa cần — cho đến khi thật sự cần.

Lúc đầu, đọc Markdown thẳng trên GitHub là đủ. Giao diện GitHub render Markdown đẹp, có thanh điều hướng file, tìm kiếm cơ bản, và hoạt động tốt cho đội kỹ sư nội bộ.

Bạn nên cân nhắc dựng docs site khi có ít nhất hai dấu hiệu này cùng lúc: tài liệu nhiều đến mức khó tra cứu; có người ngoài đội kỹ sư (đối tác, khách hàng, người dùng cuối) cần đọc thường xuyên. Lúc đó, một công cụ như VitePress hoặc Docusaurus sẽ giúp ích.

Với VitePress, ba lệnh để bắt đầu:

# Khởi tạo trong thư mục tài liệu
npx vitepress init
 
# Chạy local để xem kết quả
npx vitepress dev
 
# Build tĩnh để triển khai
npx vitepress build

⚠️ Đừng để việc dựng docs site cản việc viết tài liệu. Một trang web đẹp nhưng trống rỗng không giúp được ai. Tài liệu tốt trên GitHub còn hơn tài liệu kém trên website đẹp.


🛠 Bài tập thực hành

Bài tập này chia làm hai phần. Phần A làm một mình được với repo và fork của bạn. Phần B là câu hỏi suy ngẫm — không cần code.

Phần A — Chạy kiểm tra local và thử CI

A1 — Clone và chạy script ngay trên máy.

Sau khi fork và clone repo về (xem §4), chạy:

bash scripts/validate-skills.sh
bash scripts/check-contracts.sh

Xác nhận cả hai script exit 0 (tất cả đều pass). Bạn vừa làm đúng thứ CI sẽ làm — chỉ là trên máy chủ thay vì máy bạn.

A2 — Cố ý phá frontmatter để xem script phản ứng.

Mở file skills/docs-as-code-intro/SKILL.md. Xoá tạm thời dòng description: (chỉ xoá trong editor, chưa commit). Chạy lại:

bash scripts/validate-skills.sh

Script phải in một dòng ERR và thoát với mã lỗi khác 0. Phục hồi dòng description: và chạy lại để xác nhận về trạng thái xanh.

Bạn vừa tận mắt thấy cơ chế chặn lỗi hoạt động — không chỉ đọc về nó.

A3 — Mở PR với lỗi cố ý và quan sát CI

⚠️ Cần đã bật GitHub Actions trong fork theo §4.3 trước khi làm bước này.

Thực hiện sáu bước dưới đây theo thứ tự:

  • Bước 1 — Tạo branch mới:

    git checkout -b test/broken-contract
  • Bước 2 — Mở file examples/nova-superapp/contracts/bff-refund.contract.md và xoá tạm dòng version: trong frontmatter.

  • Bước 3 — Commit và push:

    git add examples/nova-superapp/contracts/bff-refund.contract.md
    git commit -m "test: cố ý xoá version để kiểm CI"
    git push -u origin test/broken-contract
  • Bước 4 — Mở PR từ fork của bạn trên GitHub (GitHub thường gợi ý tự động khi bạn push nhánh mới).

  • Bước 5 — Chờ khoảng 30–60 giây. Vào tab Checks của PR — bạn sẽ thấy job validate-skills báo đỏ, với output chỉ rõ file và trường thiếu.

  • Bước 6 — Sửa lại file (thêm lại dòng version:), commit thêm một lần nữa, push — CI tự chạy lại và chuyển sang xanh.

👉 Tham khảo đáp án: examples/nova-superapp/.github/workflows/validate.yml là workflow mẫu cho một super-app thật. Bạn có thể so sánh với workflow gốc của repo kit tại .github/workflows/validate.yml để thấy cách mở rộng thêm check theo quy mô dự án.

Phần B — Câu hỏi suy ngẫm

Viết 3–4 câu: nếu đội bạn tăng từ 3 lên 30 người, bạn sẽ áp dụng nguyên tắc nào ở §5 trước tiên, và vì sao? Hãy liên hệ với ít nhất một đặc điểm cụ thể của đội bạn (hoặc đội giả định Payments/Booking trong NovaApp).


Tóm tắt

  • Nguyên tắc: Máy bắt lỗi vặt (định dạng, link gãy, thiếu frontmatter), người lo phán đoán (đúng, rõ, đủ). CI là tuyến phòng thủ không biết mệt.
  • Ba loại check: lint (npx markdownlint-cli2 "**/*.md"), link (npx lychee --no-progress "**/*.md"), frontmatter (bash scripts/validate-skills.sh + bash scripts/check-contracts.sh). Bắt đầu với một loại.
  • Workflow GitHub Actions trong .github/workflows/validate.yml: trigger pull_request + push: branches: [main], job validate-skills, chạy hai script theo thứ tự.
  • Thiết lập fork: tạo tài khoản GitHub → fork repo → clone → bật Actions trong fork. Ba bước, một lần.
  • Khi đội lớn: tài liệu sống cạnh code, mỗi mảng có owner (CODEOWNERS), áp dụng từ từ, theo dõi 3 chỉ số (coverage / freshness / contribution). Docs site chỉ dựng khi thật cần.

Đọc tiếp

  • Lesson 6 — Capstone: ghép cả 5 bài thành một chiến lược hoàn chỉnh cho super-app NovaApp — từ kiến trúc thư mục, quản lý hợp đồng API, đến CI/CD tài liệu đầy đủ, kèm một case study đi từ đầu đến cuối.
  • Thử ngay: chạy bash scripts/validate-skills.sh trong repo này và đọc từng dòng output — giờ bạn hiểu từng dòng nó in ra có nghĩa gì.
  • Xem ví dụ thật: mở examples/nova-superapp/ để thấy một repo super-app được tổ chức theo mọi nguyên tắc bạn đã học từ Lesson 1 đến Lesson 5.
LDK

Le Duy Khuong

AI Transformation & Digital Strategy. Writing about agentic systems, engineering leadership, and building in public.