---
url: https://docs.nemo12.com/conventions.md
description: >-
  Quy ước viết tài liệu Nemo12: một nguồn duy nhất, loại tài liệu và ID scheme,
  frontmatter bắt buộc cho người và AI viết docs.
---

# Documentation Conventions

Quy ước cho toàn bộ tài liệu Nemo12. Mọi tài liệu mới (do người hay AI viết) phải tuân theo file này.

## 1. Một nguồn duy nhất

* Chỉ có **một** canonical documentation source: thư mục `docs/`.
* Không tạo bản copy "cho AI" hay "cho human" riêng. Cùng một file phục vụ cả hai.
* Tài liệu đầu vào (bạn gửi thêm) được ghi nhận vào [intake.md](intake.md) với ID `SRC-xxx`, sau đó nội dung được **canonical hóa** vào PRD/SDD tương ứng. Bản gốc không lưu lại thành file riêng để tránh hai nguồn sự thật.

## 2. Loại tài liệu và ID scheme

| Prefix | Loại | Vị trí | Ví dụ |
| --- | --- | --- | --- |
| `SRC-xxx` | Source — tài liệu đầu vào được tiếp nhận | ghi trong `intake.md` | SRC-001 |
| `REQ-<nhóm>-xx` | Requirement — yêu cầu đánh số | định nghĩa trong PRD | REQ-PLT-01 |
| `PRD-xxx` | Product Requirements Document | `product/` | PRD-001 |
| `SDD-xxx` | System Design Document | `architecture/` | SDD-001 |
| `QG-xxx` | Quality Gate | `quality/quality-gates.md` | QG-003 |
| `ADR-xxx` | Architecture Decision Record | `architecture/decisions/` (khi cần) | ADR-001 |

Nhóm requirement được định nghĩa tại [PRD-001 §5](product/prd-001-nemo12-platform.md) — đó là nguồn sự thật duy nhất về danh sách nhóm. Hiện có (2026-08-14), xếp theo domain:

| Domain | Nhóm REQ |
| --- | --- |
| Người dùng | `ACC` (identity/family), `LRN` (learner), `VIS` (views), `PAR` (parent), `MEN` (mentor), `ONB` (onboarding), `POR` (student portrait), `UX` (UI/UX learner-first) |
| Trường & luyện thi | `SCH` (schools), `EXAM` (exam banks), `PED` (pedagogy), `ADAPT` (adaptive mode) |
| Trí tuệ học tập | `INT` (learner intelligence) |
| Nội dung & tri thức | `KNW` (knowledge & quality), `CNT` (content plane), `LAB` (labs), `MED` (media) |
| Tương tác | `INX` (interaction — active, SDD-005) |
| Nền tảng | `BRD` (brand/public web), `PLT` (platform/tech), `NFR` (reliability), `SEC` (security), `DOC` (docs system) |
| Cộng đồng (đa phần pending) | `GAM`, `COM` (forum COM-03 active), `GRW`, `TRU` |

(Nhóm `CUR` trong bản cũ của file này chưa từng có REQ nào — curriculum thuộc `KNW`; không dùng.)

## 3. Frontmatter schema (bắt buộc)

Mỗi document bắt đầu bằng YAML frontmatter:

```yaml
---
id: sdd-002                      # ID duy nhất, lowercase
type: prd | sdd | quality | convention | index | adr
title: Learner Intelligence System
description: "Câu tiếng Việt <=160 ký tự: trang trả lời câu hỏi gì"  # SRC-1322, bắt buộc
owner: platform                  # team/người chịu trách nhiệm
status: active | active | deprecated
version: 0.2
last_reviewed: 2026-08-13        # ngày review gần nhất (absolute date)
sources: [SRC-003, SRC-604]               # tài liệu đầu vào mà file này canonical hóa
satisfies: [REQ-INT-01, REQ-INT-02]  # requirements mà file này thiết kế/đáp ứng
verified_by: [QG-005]            # quality gates kiểm chứng
ai_readable: true
---
```

Trường `description` (SRC-1322) là dòng agent đọc trong `/llms.txt` để quyết định có mở trang hay không: một câu tiếng Việt, tối đa 160 ký tự, nói trang trả lời câu hỏi gì, đặt trong ngoặc kép. Áp cho MỌI file trong `docs/`, kể cả `curriculum/` (ở đó frontmatter chỉ gồm `title` và `description`). Cổng `check-docs-agents.mjs` thi hành; chuẩn đầy đủ ở [Docs cho AI agent](engineering/docs-for-agents.md).

Trường `sources`, `satisfies`, `verified_by` là xương sống của traceability — tooling và AI đọc từ đây; [traceability.md](traceability.md) là view tổng hợp cho con người.

## 4. Lifecycle

```text
draft → active → deprecated
```

* `draft`: đang viết hoặc chờ tài liệu đầu vào bổ sung; chưa dùng làm căn cứ implement.
* `active`: đã review, là căn cứ cho implement.
* `deprecated`: giữ lại cho lịch sử, ghi rõ file thay thế.

Khi sửa nội dung đáng kể: tăng `version`, cập nhật `last_reviewed`.

## 5. Quy ước viết

* Tiếng Việt cho phần diễn giải, giữ nguyên thuật ngữ kỹ thuật tiếng Anh (learner model, evidence, workflow…).
* Diagram dùng **Mermaid** trong code fence (VitePress render được, AI đọc được, Git diff được). ASCII tree dùng cho cấu trúc thư mục.
* User story theo format: *As a \[Nemo/Marlin/Dolphin/...], I want \[goal], so that \[benefit]* — kèm ID và acceptance criteria.
* Mỗi bảng requirement trong PRD có cột: ID, mô tả, priority (MUST/SHOULD/COULD), nguồn (SRC-xxx).
* Ngày tháng luôn viết tuyệt đối (2026-08-13), không viết "tuần trước".
* **Định danh kỹ thuật: 100% tiếng Anh** (SRC-604, chỉ đạo chủ dự án 2026-08-26). Tên biến/hàm/
  bảng/cột, địa chỉ email hệ thống, khoá cấu hình, tên file, slug/URL **mới** — tất cả tiếng Anh;
  cấm tiếng Việt bỏ dấu kiểu `khong-tra-loi@`, `viec-can-lam`. Slug tiếng Việt **đã có URL công
  khai** thì khi đổi phải giữ bản cũ làm bí danh — link người dùng đã lưu không được gãy. Chữ
  người dùng đọc trên UI vẫn tiếng Việt; trao đổi với chủ dự án vẫn tiếng Việt. Vì sao thành luật:
  địa chỉ `khong-tra-loi@nemo12.com` từng lọt tới tay người nhận thư — định danh kỹ thuật là thứ
  lộ ra ngoài, không chỉ chuyện thẩm mỹ trong repo.

### 5.2 Link trong `docs/` không được trỏ ra ngoài `docs/` (SRC-746)

`docs.nemo12.com` dựng bằng VitePress với `srcDir: docs/`. Link tương đối được giải nghĩa trong
gốc ấy, nên một link `[SKILL]` trỏ tới `../../.claude/skills/x/SKILL.md` là một **link chết** dù file có thật trên
đĩa: VitePress chặn cả bản build, job Deploy docs đỏ, và deploy của **mọi phiên** đứng lại.

Ngày 16.09.2026 lỗi này xảy ra hai lần trong vài giờ, do hai phiên khác nhau, và cả hai lần
`npm run check:docs` ở máy đều xanh.

**Muốn trỏ tới một file ngoài `docs/` thì viết đường dẫn trong dấu nháy ngược**, đừng làm nó thành
link: `` `apps/pearl/content/completion/` ``. Người đọc vẫn tìm được file, bản build không gãy.

Cổng: `scripts/check-docs-vitepress-links.mjs`, nằm trong `npm run check:docs`. Nó chỉ bắt đích là
**trang** (`.md` hoặc không đuôi) — link tới một file mã (`.ts`) được VitePress coi là tài nguyên
chứ không phải trang nên không làm gãy build, và một cổng bắt rộng hơn thứ thật sự hỏng là một
cổng sẽ bị tắt đi.

### 5.1 Hai luật về chữ người dùng đọc (SRC-748)

Chỉ đạo chủ dự án 2026-09-16. Phạm vi là **mọi chữ người ngoài đọc**: trang web, email, và nội dung
nạp qua SQL script. Không phải chỉ app.

**a. Chỉ dùng ký tự có trên bàn phím.** Năm ký tự bị cấm trong chữ người dùng đọc: em dash `—`,
en dash `–`, nháy kép cong `“ ”`, nháy đơn cong `' '`, ba chấm một ký tự `…`. Thay lần lượt bằng
dấu phẩy/hai chấm, dấu trừ, `"`, `'`, `...`. (SRC-751 và SRC-753, chỉ đạo 2026-09-16:
*"viết như người đang gõ phím"*). Không phím nào gõ ra năm ký tự đó, nên mọi chỗ chúng
xuất hiện đều là do trình soạn thảo tự thay hoặc người dán vào. Em dash ngăn hai mệnh đề nên thay
bằng dấu phẩy hoặc hai chấm; en dash nối hai vế ngang hàng hoặc một khoảng nên thay bằng dấu trừ:
`09:00 - 11:00`, `Thành ngữ - tục ngữ`.

**Một cái bẫy đã gặp thật khi dọn:** trong mã nguồn, thay `“` bằng `"` trần sẽ ĐÓNG SỚM chuỗi nháy
kép của TypeScript và làm hỏng file, nên phải biết đang đứng trong loại chuỗi nào (`\"` trong chuỗi
nháy kép, `"` trong JSX và backtick). Trong D1, cột `options_json` là JSON lưu dạng text: thay `"`
trần vào đó làm hỏng cả 55 bản ghi, phải ghi `\"`. Đã parse thử toàn bộ trên bản sao trước khi chạy.

Chú thích
trong mã và biểu thức chính quy phải khớp dữ liệu cũ thì được miễn, vì chúng không tới tay ai.

**b. Ngày hiển thị luôn `DD.MM.YYYY`.** Dấu chấm chứ không phải gạch chéo: `03/09` và `09/03` là thứ
người Việt và người Mỹ đọc ngược nhau, còn dấu chấm thì không ai đọc theo kiểu Mỹ. Dùng `dmy()` của
`workers/api/src/shared/time.ts` ở phía worker; phía app dùng hàm `dmy` cục bộ nối bằng `"."`.
Ngoại lệ có chủ đích: `toLocaleDateString("en-CA")` cho ra `YYYY-MM-DD` và trong repo này luôn dùng
làm **khoá so sánh ngày**, không phải chữ cho người đọc, nên không đụng tới. Ngày trong *tài liệu*
vẫn viết `2026-09-16` theo mục 5 ở trên: đó là chữ cho người làm, không phải cho người dùng.

**Kiểm bằng cây SẠCH, không kiểm bằng cây làm việc.** Hai cổng này đọc file trên đĩa, mà cây làm
việc chung luôn có sửa dở của phiên khác: một dòng vi phạm còn trên `main` nhưng đã bị ai đó sửa
trong cây thì cổng chạy ở máy KHÔNG thấy, và CI thì thấy. Đã dính thật một lần (SRC-753,
`templatesIelts.ts:59`). Trước khi tin kết quả:

```bash
git worktree add --detach /tmp/wt-clean origin/main
cd /tmp/wt-clean && node scripts/check-no-emdash.mjs && node scripts/check-date-format.mjs
```

Đây là biến thể của bẫy "check:docs local nói dối" trong CLAUDE.md, chỉ khác là nó cắn theo chiều
ngược lại: ở đó cây làm việc khiến gate xanh giả vì THỪA file, ở đây vì THIẾU nguyên trạng.

Hai cổng thi hành, đều nằm trong `npm run check:code`:

| Cổng | Quét gì |
| --- | --- |
| `scripts/check-no-emdash.mjs` | 8 app + `workers/api/src/modules/email` (không dung thứ) và `scripts/*.sql` + `migrations/*.sql` (có trần, chỉ được đi xuống) |
| `scripts/check-date-format.mjs` | Mọi `.ts`/`.tsx` trong `apps/`, `workers/`, `packages/`: chặn `.reverse().join("/")` và `toLocaleDateString("vi-VN")` |

**Dữ liệu đã nạp thì phải backfill.** SRC-749 và SRC-751 (2026-09-16) là hai lần chạy đầu tiên theo
luật này: 197 câu hỏi + 7 kỹ năng (em dash) rồi 60 câu hỏi + 5 kỹ năng (en dash) đang nằm trong D1
được thay bằng dấu trừ qua `scripts/seed-strip-emdash-d1.sql` và `scripts/seed-strip-endash-d1.sql`, có bản sao trước khi sửa ở `scripts/backup/`. Chọn gạch nối chứ
không phải dấu phẩy vì đã đọc mẫu thật trước: em dash ở đó luôn ngăn hai mệnh đề và có dấu cách hai
bên, đổi sang phẩy thì câu vốn đã nhiều phẩy thành một dãy phẩy không đọc được.

**Vì sao `.sql` có trần mà app và email thì không:** file `.sql` trong repo là **sổ ghi những lần nạp
đã chạy**. Sửa một chuỗi trong đó không đổi được một chữ nào đang nằm trong D1, nên bắt sửa hết ngay
chỉ tạo một đợt commit vô nghĩa và cảm giác sai là "đã sạch". Cái sửa thật nằm ở backfill dữ liệu.
Trần chỉ được đi xuống: nạp mới mà mang thêm em dash là gate đỏ.

## 6. Quy trình tiếp nhận tài liệu mới

Khi có tài liệu đầu vào mới:

1. Ghi một dòng vào [sổ intake](intake.md) bằng `node scripts/src-new.mjs`: cấp `SRC-xxx`, ngày nhận, tóm tắt. Dòng nằm ở mảnh 100 số `docs/intake/src-NNNN-MMMM.md` (SRC-1322).
2. Xác định nội dung thuộc PRD nào / SDD nào — cập nhật file hiện có hoặc tạo file mới theo ID scheme.
3. Thêm/ cập nhật `REQ-xxx` trong PRD nếu tài liệu chứa yêu cầu mới.
4. Cập nhật frontmatter (`sources`, `satisfies`) của các file bị ảnh hưởng.
5. Cập nhật [traceability.md](traceability.md) — đánh dấu trạng thái SDD và Quality Gate.

## 7. Render

Docs được render bằng **VitePress** tại `apps/docs/` (deploy: docs.nemo12.com, theo [SDD-001](architecture/sdd-001-platform.md)). Thanh bên **tự sinh theo mảng sản phẩm** từ `apps/docs/.vitepress/structure.mjs` (SRC-1024); [index.md](index.md) là mục lục duy nhất. Đường dẫn file giữ theo loại tài liệu (`product/`, `architecture/`…): mảng là lớp xếp, không phải việc dời file (SRC-030, Q-084). File nào buộc phải dời thì thêm dòng chuyển hướng vào `docs/public/_redirects`; cổng `check-docs-structure` kiểm cả hai. Tài liệu vẫn đọc tốt dưới dạng plain Markdown trên Git — đây là yêu cầu thiết kế, không phải tình trạng tạm.
