# Rules — Coding Conventions SOLUTION_ERP > Nguồn duy nhất cho rules. Bất cứ commit nào vi phạm cần justify trong message. ## 1. Ngôn ngữ | Thành phần | Ngôn ngữ | |---|---| | UI FE | **100% tiếng Việt** | | Code (.cs/.ts) | **English** (tên biến/hàm/class) | | Comment code | Tiếng Anh hoặc Việt — miễn rõ | | Tên DB table + column | **English PascalCase** (Contracts, SupplierId) | | Business label | Tiếng Việt (vd `"Đang soạn thảo"` cho phase 2) | | Commit message | English hoặc Việt — preferred English | | Doc MD (docs/) | **Tiếng Việt** (audience là người Việt) | ## 2. Backend — .NET 10 ### 2.1 Clean Architecture dependency rule ``` Api → Application ← Domain ↑ Infrastructure ──┘ ``` - **Domain:** pure, chỉ dùng `Microsoft.Extensions.Identity.Stores` (cần cho IdentityUser) — không depend ASP.NET Core framework - **Application:** depend Domain; CQRS handlers + interfaces + DTOs + validators - **Infrastructure:** depend Application + Domain; impl interfaces (EF, JWT, rendering, services) - **Api:** depend Application + Infrastructure; Controllers + middleware + composition root ### 2.2 CQRS + MediatR pattern - 1 feature = 1 file `{Verb}{Entity}Command.cs` / `{Verb}{Entity}Query.cs` - File chứa: Command record + Validator + Handler (cùng 1 file cho compact) - Dài thì tách: `{Feature}/Commands/{Create}/...` **Command naming:** ``` Create{Entity}Command // tạo mới Update{Entity}Command // update full Patch{Entity}FieldCommand // update 1 field Delete{Entity}Command // soft delete (default) {Action}{Entity}Command // action domain-specific (vd TransitionContract) ``` **Query naming:** ``` Get{Entity}Query // single by Id List{Entity}sQuery // list với paging {Entity}{Purpose}Query // custom query (vd GetMyInboxQuery) ``` ### 2.3 Validation - FluentValidation (KHÔNG dùng DataAnnotation) - Validator = class trong cùng file command: `{Command}Validator : AbstractValidator<{Command}>` - Pipeline: `ValidationBehavior` auto-chạy trước Handler — throw `ValidationException` → 400 ProblemDetails ### 2.4 Error handling - KHÔNG try-catch ở Controller - Throw domain exception: `NotFoundException`, `ForbiddenException`, `UnauthorizedException`, `ConflictException`, `ValidationException` - `GlobalExceptionMiddleware` map exception → HTTP status + ProblemDetails JSON - Logging: Serilog structured, không `Console.WriteLine` ### 2.5 Async/await - Tất cả I/O (DB, HTTP, file) PHẢI async - Truyền `CancellationToken` mọi async call - Tên method async: suffix `Async` (trừ ASP.NET action signature) ### 2.6 Entity rules - Mọi entity mới extend `BaseEntity` (Id Guid + audit) hoặc `AuditableEntity` (+ soft delete) - PK: `Id` type `Guid`, default `Guid.NewGuid()` trong constructor - FK: `{Entity}Id` type `Guid` - Enum: `HasConversion()` trong EF config - String: luôn `HasMaxLength(n)` trong EF config — không để `nvarchar(max)` trừ khi là JSON/rich text - Money: `decimal` + `HasPrecision(18, 2)` - DateTime: luôn UTC, lấy từ `IDateTime.UtcNow` (không `DateTime.Now`) - Query filter soft delete: `HasQueryFilter(x => !x.IsDeleted)` cho AuditableEntity ### 2.7 DI registration - Singleton: stateless services (DateTimeService, FormRenderer) - Scoped: per-request services (JwtTokenService, ContractWorkflowService, DbContext) - Transient: lightweight utility (hiếm dùng) ### 2.8 Package version pinning - **KHÔNG dùng `*` hoặc `latest`** — luôn pin version cụ thể - Check compatibility với .NET 10 trước khi add. Gotchas đã biết: - MediatR 14 → lỗi IMediator DI, **pin 12.4.1** - Swashbuckle 10 → conflict OpenApi 2, **pin 6.9.0** - Xem [`gotchas.md`](gotchas.md) cho full list ## 3. Frontend — React 19 + Vite 8 + TS 6 ### 3.1 Named export, không default export ```tsx // ✅ export function SuppliersPage() { } export const MyConstant = 1 // ❌ export default function SuppliersPage() { } // Exception: App.tsx dùng default export (Vite convention) ``` ### 3.2 erasableSyntaxOnly (Vite 8) KHÔNG dùng `enum` — dùng const-object pattern: ```ts // ❌ export enum SupplierType { NhaCungCap = 1 } // ✅ export const SupplierType = { NhaCungCap: 1, NhaThauPhu: 2 } as const export type SupplierType = typeof SupplierType[keyof typeof SupplierType] ``` ### 3.3 Path alias - Dùng `@/` cho absolute import: `import { api } from '@/lib/api'` - Config ở `vite.config.ts` + `tsconfig.app.json` (chỉ `paths`, không `baseUrl`) ### 3.4 Data fetching - **TanStack Query** cho mọi API call (KHÔNG useState + useEffect thủ công) - `useQuery` cho GET, `useMutation` cho POST/PUT/DELETE - `invalidateQueries` sau mutation thành công ### 3.5 API calls - Qua `api` instance (`src/lib/api.ts`) — đã wire axios interceptor JWT + 401 redirect - Base URL: `/api` (Vite proxy → `:5443`) ### 3.6 Permission guard ```tsx // Hook const { can } = usePermission() if (!can('Contracts', 'Update')) return null ``` Nhớ: FE guard chỉ là UX. BE policy `[Authorize(Policy = "Contracts.Update")]` là source of truth. ### 3.7 Component naming - PascalCase cho file: `SuppliersPage.tsx`, `DataTable.tsx` - kebab-case cho utility: `api.ts`, `cn.ts` - React component = PascalCase - Hook: prefix `use`, ví dụ `usePermission` ### 3.8 Toast + error - Dùng `sonner` (đã wire `` ở App) - `toast.success('...')`, `toast.error(getErrorMessage(err))` - Error helper: `src/lib/apiError.ts` extract từ ProblemDetails ### 3.9 Duplicate giữa 2 FE **CÓ CHỦ ĐÍCH.** Mỗi app (`fe-admin` + `fe-user`) là standalone — copy shared code giữa 2 app thay vì extract ra package chung. Lý do: 2 app có UX rất khác (admin mission-critical, user workflow-heavy), evolution độc lập. Khi share code: `cp` file từ app này sang app kia. Đồng bộ tay khi có breaking change. ## 4. Database ### 4.1 Convention | Item | Rule | |---|---| | Schema | 1 schema duy nhất: `dbo` | | Table | PascalCase tiếng Anh, plural: `Contracts`, `Suppliers` | | Column | PascalCase: `FullName`, `CreatedAt` | | PK | `Id` — `uniqueidentifier` | | FK | `{Entity}Id` — `uniqueidentifier` | | Index | `IX_{Table}_{Col}` | | Unique | `UX_{Table}_{Col}` | Full: xem [`database/database-guide.md`](database/database-guide.md). ### 4.2 Migration - Một commit = 1 migration (trừ khi nhiều change nhỏ trong cùng feature) - Naming PascalCase: `AddMasterData`, `AddContractsWorkflow` - Commit đủ 3 file: `{Name}.cs`, `{Name}.Designer.cs`, `ApplicationDbContextModelSnapshot.cs` ## 5. Git & commits ### 5.1 Branch - `main` — deploy branch - Feature branch (nếu nhiều người): `feature/phase{N}-{feature}` ### 5.2 Commit format ``` [CLAUDE] : Co-Authored-By: Claude Opus 4.7 (1M context) ``` **Scope:** `Contract` · `Form` · `Workflow` · `Supplier` · `Auth` · `Admin` · `Api` · `App` · `Domain` · `Infra` · `FE-Admin` · `FE-User` · `Docs` · `CICD` · `Scripts` · `Report` · `Dashboard` ### 5.3 Một commit nên - Build pass (BE + FE) - Không leave WIP nửa chừng (trừ khi commit message nói rõ) - Test E2E với endpoint mới (curl trong session log) ### 5.4 Không commit - `.env`, `appsettings.*.Local.json` - `node_modules/`, `bin/`, `obj/` - `wwwroot/uploads/` (user files) - `SolutionErp_Design` database (tạm cho EF CLI) ## 6. Docs ### 6.1 Khi nào viết session log - Session có commit thay đổi code → PHẢI tạo `docs/changelog/sessions/YYYY-MM-DD-HHMM-{topic}.md` - Session chỉ Q&A, không đụng file → không cần ### 6.2 Session log format ```markdown # Session YYYY-MM-DD HH:MM — **Dev:** Claude / Copilot / **Duration:** ~Nh **Base commit:** ## Làm được ### Chunk X — - bullet list deliverable ## E2E verified ## Bug gặp + fix | Bug | Fix | ## Docs updates ## Handoff ## Thông số cumulative ``` ### 6.3 Update khi thêm feature - Thêm entity → update [`database/database-guide.md`](database/database-guide.md) schema section - Thêm endpoint → update [`flows/`](flows/) relevant flow - Thêm bug fix lặp lại → update [`gotchas.md`](gotchas.md) - Thêm pattern → update skill tương ứng ở `.claude/skills/` - Phase đổi → update [`STATUS.md`](STATUS.md) + [`HANDOFF.md`](HANDOFF.md) + [`changelog/migration-todos.md`](changelog/migration-todos.md) ### 6.4 Audit + compact MD định kỳ **Triết lý:** KHÔNG rewrite toàn bộ. Chỉ **compact + patch drift** định kỳ. Test/code là source of truth, MD theo sau. **Cadence + trigger:** | Tần suất | Action | Effort | Trigger | |---|---|---|---| | **Cuối session** (đã làm tự động) | Update STATUS Recently Done + HANDOFF cảnh báo session tiếp | ~10 phút | Mỗi session có code commit | | **Cuối phase** (~1 tháng) | Compact STATUS Recently Done > 30 row → `changelog/recently-done-archive-{YYYY-MM}.md`. Collapse migration-todos Phase done thành 1 paragraph cross-ref session log | ~30 phút | Phase đóng | | **Đầu mỗi tháng** (cron `solution-erp-skill-audit-monthly`) | Audit skill + doc drift + count consistency. Patch stale qua Edit (KHÔNG rewrite). Log kết quả | ~45 phút | 9:00 ngày 1 mỗi tháng | | **Khi trigger riêng** | Selective rewrite 1 file (skill drift > 30%, architecture redesign, phase major) | 1-2 giờ | Quantitative trigger | | **KHÔNG BAO GIỜ** | Rewrite toàn bộ docs | — | — | **Doc audit checklist (mở rộng từ §9.4 skill audit, chạy cùng cron):** ``` 1. dotnet test SolutionErp.slnx → verify count khớp STATUS/CLAUDE/HANDOFF 2. ls migrations/ → verify count khớp ef-core-migration skill + database-guide 3. grep '#[0-9]\+' gotchas.md → verify khớp count claim trong HANDOFF/STATUS 4. ls .claude/skills/ → verify khớp count trong rules §9 + skills/README 5. STATUS Recently Done > 30 row? → đề xuất archive cũ 6. HANDOFF có section duplicate file khác (PROJECT-MAP/workflow/git log)? → đề xuất bỏ 7. Skill SKILL.md mention "Phase N MVP" mà N <= phase hiện tại? → stale, refresh 8. Output report stale items + suggest patches → human review 9. Log vào docs/changelog/skill-audit-{YYYY-MM}.md (1 page max) ``` **Anti-pattern (đã trải nghiệm):** - ❌ Rewrite toàn bộ MD định kỳ — mất context, paraphrase mất nuance, ROI thấp - ❌ Bulk-clone 3rd party docs — đã từ chối ở Skill governance §9.5 - ❌ Skip audit "vì sắp UAT/sắp deploy" — drift tích lũy → onboard agent mới sai **Trigger override:** User nói "audit MD", "kiểm tra docs", "định kỳ kiểm tra", "compact docs" → chạy doc audit ngay không đợi cron. ### 6.5 Consolidate MD đúng cách — KEEP vs CUT > **Bài học session 6 (2026-04-30):** Compact -288 dòng nhanh, paraphrase + cắt narrative để "rõ đẹp" → user phản hồi "viết MD gọn lại tý là mất mẹ luôn tính cách cũ". Docs đọc 6 tháng sau như machine output, không hiểu tại sao chọn approach. Rule §6.5 này chốt để KHÔNG TÁI PHẠM. **Mục tiêu consolidate:** Giảm độ dài để session sau đọc context không mệt. **KHÔNG = cắt thông tin / paraphrase / "đẹp hóa".** #### KEEP — CẤM CẮT (asset không tái tạo được) | Loại | Vì sao bắt buộc giữ NGUYÊN VĂN | Ví dụ | |---|---|---| | **Narrative tích lũy qua sessions** | Không tái tạo. Là tài sản chỉ ra "đã đi qua đâu, đã đổi hướng ra sao" | "Ban đầu chọn MediatR 14, fail DI runtime, downgrade 12.4.1" | | **Rationale (lý do quyết định)** | Quan trọng hơn quyết định. Mất rationale → agent mới revert vì không biết tại sao | "Pin Swashbuckle 6.9.0 vì 10.x conflict OpenApi 2", "Skip E2E Playwright vì brittle cho solo dev" | | **Gotcha context (incident bối cảnh)** | Gotcha = fix + bối cảnh đau. Mất context → instructions khô không apply được | G-084 "Next.js bind 0.0.0.0 → Gitea fallback IPv6 → IIS ARR resolve IPv4 first → leak homepage" | | **Anecdote / bài học** | Nuance không có trong code/git, chỉ có trong docs | "Bài học NamGroup: Node latest fail CI Windows" | | **Decision why over decision what** | Code chỉ có "what", docs phải bù "why" | "PE workflow tách enum riêng vì Phase ≠ ContractPhase" | #### CUT — XÓA hoặc CHUYỂN CHỖ được | Loại | Cách xử lý | Ví dụ | |---|---|---| | Số liệu lặp giữa nhiều file | Giữ 1 chỗ canonical, các file khác cross-ref | "52 bảng" canonical ở PROJECT-MAP, STATUS/HANDOFF cross-ref | | Section duplicate copy-paste | Cross-ref thay vì copy nguyên văn | "Versioned WF quick ref" → cross-ref `workflow-contract.md §7bis` | | List Recently Done > 30 row | Archive sang file riêng `recently-done-archive-{YYYY-MM}.md` (CHUYỂN CHỖ, KHÔNG xóa) | Phase 0-7 cũ | | Phase đã đóng > 1 tháng | Collapse thành 1 paragraph + link session log đầy đủ | Phase 0-5 → "Phase 0-5 done, [session logs](changelog/sessions/)" | | Stats lặp | Cumulative table giữ ở STATUS, file khác chỉ con số mới nhất | LOC / endpoint / migration count | #### CẤM TUYỆT ĐỐI - ❌ **Paraphrase câu chữ original** — dù "rõ ràng hơn", KHÔNG paraphrase. Giữ y nguyên văn cũ. - ❌ **Summary đoạn đã có narrative** — không tóm tắt 5 dòng kể chuyện thành 1 dòng "X done". Mất context. - ❌ **"Đẹp hóa" bằng cách cắt** — đẹp đạt qua format (bảng, anchor link, heading), KHÔNG qua cắt nội dung. - ❌ **Compact với mindset machine-first** — viết cho agent mới đọc 6 tháng sau hiểu, KHÔNG phải tổng hợp ngắn nhất. #### Decision tree khi gặp đoạn dài ``` Đoạn này có narrative / rationale / gotcha context / anecdote / "tại sao"? ├─ Có → GIỮ NGUYÊN VĂN, kể cả dài (lossless) └─ Không → kiểm tra: ├─ Duplicate file khác? → cross-ref ├─ Số liệu cũ stale? → archive sang file riêng ├─ Phase đã đóng > 1 tháng? → collapse + link session log └─ KHÔNG match gì → GIỮ NGUYÊN (default lossless) ``` #### Validation sau consolidate (BẮT BUỘC chạy) Đọc lại file đã compact, tự hỏi 3 câu: 1. **Agent mới đọc 6 tháng sau có biết "tại sao chọn approach này" không?** 2. **Có còn cảm giác "kể chuyện" tích lũy không, hay chỉ là instructions khô?** 3. **Gotcha context vẫn còn ý nghĩa khi đọc rời rạc không?** → Nếu "không" cho **bất kỳ** câu nào → revert đoạn đó về nguyên văn cũ. #### Trigger áp §6.5 - Audit định kỳ §6.4 (cron đầu tháng) - User nói "consolidate", "compact", "gọn lại MD", "rõ ràng MD" - Cuối phase đóng (>1 tháng) khi compact STATUS/HANDOFF/migration-todos ## 7. Testing (Phase 8 active — 77 test pass + CI gate live) ### Stack đã apply - **xUnit 2.9.3** + **FluentAssertions 7.2** (pin trước v8 commercial license) - **Microsoft.EntityFrameworkCore.Sqlite 10** (in-memory testing — supports transactions, không như InMemory provider) - **Custom `TestApplicationDbContext`** override `nvarchar(max) → TEXT` cho SQLite compat (xem `tests/.../Common/SqliteDbFixture.cs`) - **NO mock framework** (tránh mock-heavy, prefer EF in-memory hoặc fake data) ### Test pyramid (bottom-heavy, KHÔNG E2E) ``` ✅ Phase 1: Domain policy 54 test (pure functions, no DB) ✅ Phase 2: Infra code generator 17 test (SQLite in-mem) ✅ Phase 3 mini: Application versioning 6 test (SQLite, no UserManager) ⏸️ Phase 3 full: Application handlers w/ Identity (cần UserManager DI helper) ⏸️ Phase 4: API smoke (WebApplicationFactory) ⏸️ Phase 5: FE Vitest cho lib utility ❌ E2E (Playwright) — KHÔNG làm (brittle cho solo dev) ``` ### Khi nào viết test — timing rule | Tình huống | Quy tắc | |---|---| | **Feature mới** | **Test-after**: UAT 2-3 lần ổn → viết test (≤1 commit theo sau). Pattern Phase 1-8 đã làm. | | **Bug fix** | **Test-before BẮT BUỘC**: reproduce bug bằng test failing → fix → green. "1 bug = 1 regression test before merge". | | **Critical algorithm** (code generator atomic, workflow guard, financial calc, security check) | **Test-before merge** — edge case nhiều, race condition đắt nếu break prod. | | **Spec change** (không phải bug) | Update test cũ + code, commit chung. **KHÔNG xóa, KHÔNG skip** test cũ. Coverage chỉ tăng. | | **Skip test** | DTO mapping, CRUD master đơn giản, FE component snapshot, wrapper passthrough, migration backfill idempotent. | ### Quy tắc bổ sung mỗi feature mới - **Domain entity / enum** → 0 test (compile check đủ) - **Domain policy method** → 2-3 test (positive + negative + edge case) - **CQRS handler có guard logic** → 1 test mỗi guard branch - **Migration mới** → smoke test "DbInitializer chạy được + seed không lỗi" - **API endpoint mới** → 1 happy path smoke (cho endpoint critical) - **Bug found in production / staging** → 1 regression test BEFORE merge fix Pattern: **"1 bug found → 1 regression test added"**. ### Workflow user end-of-task ```bash # Sau mỗi task code (BẮT BUỘC trước commit code): dotnet test SolutionErp.slnx # local verify ~3s git add -A && git commit -m "..." git push # CI tự run test gate trước build/deploy ``` Test fail local → fix trước khi push. Test fail trên CI → no deploy. ### CI gate behavior `.gitea/workflows/deploy.yml`: ```yaml # Path filter: skip CI hoàn toàn cho commit MD-only on: push: paths-ignore: - 'docs/**' - '**/*.md' - '.claude/skills/**' # 2 step test gate trước build/deploy (fail → exit → no deploy) - name: Run unit tests (Domain) - name: Run integration tests (Infrastructure) ``` ### Pending (Phase 3-5 future) - Application handler tests cần Identity UserManager DI setup helper - API smoke tests via `WebApplicationFactory` + SQLite - FE Vitest cho `lib/queryMatches`, `lib/cn`, money formatter Detail: `docs/architecture.md §11 Testing strategy`. ## 8. Security baseline - HTTPS everywhere prod - JWT Secret trong user-secrets / env var (không commit appsettings) - Password hash: PBKDF2 (Identity default) - CORS whitelist chỉ 2 FE origin - SQL injection: EF Core parameterized (không raw SQL trừ khi phải) - Audit log: mọi ghi phải log qua `ContractApproval` hoặc `AuditLog` (future) - Rate limit: `/api/auth/login` 5 req/min/IP (chưa implement — Phase 5) ## 9. Skills (.claude/skills/) > Project-level skills encode kiến thức SOLUTION_ERP-specific. Claude tự auto-invoke qua Skill tool dựa trên semantic match với `description` trong `SKILL.md`. **PHẢI dùng skill khi gặp task tương ứng — không tự suy luận lại.** ### 9.1 Skills hiện có (6) **Domain (3) — logic nghiệp vụ:** | Skill | Trigger khi | |---|---| | `contract-workflow` | Bug chuyển phase, 403, mã HĐ sai, versioned WF, "HĐ cũ giữ cũ" | | `form-engine` | Render template docx/xlsx, FieldSpec, PDF export, upload form mới | | `permission-matrix` | Permission denied, gán role, menu không hiện, inherit Contracts/Workflows | **Ops (3) — devops + security + schema:** | Skill | Trigger khi | |---|---| | `dependency-audit-erp` | `npm audit`, `dotnet vulnerable`, scan CVE, nâng cấp package | | `ef-core-migration` | `migrations add`, schema change, snapshot lỗi, revert migration, DesignTimeDbContextFactory | | `iis-deploy-runbook` | Prod 500/502, cert hết hạn, restart app pool, gitea-runner fail, SignalR 401 | ### 9.2 Nguyên tắc tạo skill mới **Project-specific, KHÔNG clone generic.** User-level skills global đã có sẵn (`code-reviewer`, `sql-database-assistant`, `focused-fix`, `senior-frontend`, `mcp-builder`, `webapp-testing`, `review`, `security-review`, ...). Project-level skills chỉ thêm khi: - Encode kiến thức **SOLUTION_ERP-only** (path, version pin, gotcha number, commit convention) - Generic skill không thể có (vd: 8 migration history cụ thể của project) - Có YAML `when-to-use` triggers rõ ràng (cả tiếng Việt + Anh) - Workflow Vietnamese-first (audience là dev Việt) ### 9.3 Format bắt buộc `SKILL.md` ```markdown --- name: kebab-case-name description: 1-3 câu mô tả + stack specificity (cho semantic match đúng) when-to-use: - "trigger english" - "trigger tiếng Việt" --- # Skill Name ## Context ## Workflow / Commands (copy-pastable, không pseudocode) ## Pitfalls ## Code pointers (path đầy đủ src/Backend/...) ## Related (dẫn chiếu gotcha #N, docs/...) ``` ### 9.4 Audit định kỳ — mỗi đầu tháng (chạy chung với doc audit §6.4) **Cadence:** Đầu mỗi tháng, cron `solution-erp-skill-audit-monthly` fire 9:00 ngày 1 → chạy combined audit (skill + doc drift theo §6.4 checklist). **Skill audit phần riêng (mỗi skill SKILL.md):** ``` 1. Đọc .claude/skills/README.md — list 6 skill hiện có 2. Cross-check stale: - SKILL.md mention "Phase N MVP" mà N < phase hiện tại → stale - Code pointers paths có còn tồn tại không (Glob check) - Count claim (vd "24 DbSet", "13 migration") khớp thực tế không - Trigger phrases có còn hiệu quả không 3. Cross-check feature gap: - docs/STATUS.md Recently Done — feature mới nào chưa có skill cover? - docs/gotchas.md — gotcha mới (>3 dòng / cluster) nào nên gom thành skill? - migration-todos.md — task lớn sắp tới có cần skill mới? 4. Hành động: - Skill stale → Edit SKILL.md (KHÔNG rewrite) + commit `[CLAUDE] Skill: refresh ` - Skill cần thêm → tạo theo §9.2 §9.3 + commit `[CLAUDE] Skill: add ` - Skill không còn cần → archive vào .claude/skills/_archived/ + commit 5. Update bảng §9.1 trong rules.md + .claude/skills/README.md + docs/CLAUDE.md count 6. Log skill + doc drift report vào docs/changelog/skill-audit-{YYYY-MM}.md (1 page max) ``` **Trigger:** User nói "audit skill", "check skill list", "skill staleness", "audit MD", "kiểm tra docs", "định kỳ kiểm tra" → chạy combined audit (§6.4 + §9.4) ngay không đợi cron. **Threshold trigger ngoài cron:** - `gotchas.md` +5 gotcha mới → audit (xem có gom thành skill mới được không) - Phase mới đóng → audit ngay (track stale từ feature mới) - Skill drift > 30% (count check, path broken, mention phase cũ) → selective rewrite ### 9.5 Anti-patterns ❌ **KHÔNG bulk-clone repo skill 3rd party** — phần lớn doc-dump generic, không có `when-to-use`, dễ trigger sai. ❌ **KHÔNG viết skill chỉ để "có thêm"** — skill = trigger semantic match, nếu description quá chung sẽ trigger nhầm trong session khác. ❌ **KHÔNG copy nội dung skill từ docs/ sang SKILL.md** — skill phải ngắn gọn, action-oriented; docs/ là reference dài. Skill dẫn chiếu docs. ❌ **KHÔNG bỏ qua skill khi gặp task khớp** — tự suy luận lại = lãng phí context + dễ sai. ## 10. Liên quan - [`CLAUDE.md`](../CLAUDE.md) — entry point cho AI agent - [`architecture.md`](architecture.md) — kiến trúc chi tiết (layered, CQRS, state machine) - [`gotchas.md`](gotchas.md) — pitfalls đã gặp - [`database/database-guide.md`](database/database-guide.md) — DB conventions - [`flows/`](flows/) — sequence diagram per feature - [`../.claude/skills/README.md`](../.claude/skills/README.md) — skill index 6 skill