Pure docs work — 0 thay đổi code/test. 77 test vẫn pass (Domain 54 + Infra 23). 3 skill refresh stale (audit định kỳ §6.4 + §9.4 phát hiện): - form-engine: "Phase 2 MVP missing PDF + form builder" → "Tier 3 feature-complete" + bỏ section duplicate "Gen mã HĐ chưa implement" (đã DONE Phase 3+6) - permission-matrix: 12 menu cũ → ~60 menu key (Bg_*/Pe_*/PeWf_*/Catalogs) + inheritance roots 4 group + Budgets KHÔNG inherit (gotcha #35) - ef-core-migration: "24 DbSet" → "52 bảng (15 migration)" 2 rule mới chốt: - rules.md §6.4 — Audit + compact MD định kỳ (cadence + checklist + anti-pattern) Triết lý: KHÔNG rewrite toàn bộ. Compact + patch drift. Cron solution-erp-skill-audit-monthly mở rộng scope (skill + doc drift combined) - rules.md §9.4 mở rộng cross-ref §6.4 Update STATUS Session 7+ priority + HANDOFF cảnh báo session 7 + migration-todos Phase 9 Session 6 done sub. Cron 2026-05-01 fire mai → combined audit theo checklist §6.4 + §9.4. Session log đầy đủ: docs/changelog/sessions/2026-04-30-chot-session-6-md-audit-compact.md Commit MD-only → CI skip (path filter gotcha #41). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
19 KiB
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:
ValidationBehaviorauto-chạy trước Handler — throwValidationException→ 400 ProblemDetails
2.4 Error handling
- KHÔNG try-catch ở Controller
- Throw domain exception:
NotFoundException,ForbiddenException,UnauthorizedException,ConflictException,ValidationException GlobalExceptionMiddlewaremap 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
CancellationTokenmọ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ặcAuditableEntity(+ soft delete) - PK:
IdtypeGuid, defaultGuid.NewGuid()trong constructor - FK:
{Entity}IdtypeGuid - Enum:
HasConversion<int>()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ôngDateTime.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ặclatest— 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.mdcho full list
3. Frontend — React 19 + Vite 8 + TS 6
3.1 Named export, không default export
// ✅
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:
// ❌
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ôngbaseUrl)
3.4 Data fetching
- TanStack Query cho mọi API call (KHÔNG useState + useEffect thủ công)
useQuerycho GET,useMutationcho POST/PUT/DELETEinvalidateQueriessau mutation thành công
3.5 API calls
- Qua
apiinstance (src/lib/api.ts) — đã wire axios interceptor JWT + 401 redirect - Base URL:
/api(Vite proxy →:5443)
3.6 Permission guard
<PermissionGuard menuKey="Contracts" action="Update">
<Button>Sửa</Button>
</PermissionGuard>
// 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<Toaster>ở App) toast.success('...'),toast.error(getErrorMessage(err))- Error helper:
src/lib/apiError.tsextract 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.
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] <scope>: <imperative message>
<body optional — chi tiết what + why>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.jsonnode_modules/,bin/,obj/wwwroot/uploads/(user files)SolutionErp_Designdatabase (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
# Session YYYY-MM-DD HH:MM — <topic>
**Dev:** Claude / Copilot / <name>
**Duration:** ~Nh
**Base commit:** <sha>
## Làm được
### Chunk X — <name>
- bullet list deliverable
## E2E verified
<curl output hoặc screenshot>
## 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.mdschema section - Thêm endpoint → update
flows/relevant flow - Thêm bug fix lặp lại → update
gotchas.md - Thêm pattern → update skill tương ứng ở
.claude/skills/ - Phase đổi → update
STATUS.md+HANDOFF.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.
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
TestApplicationDbContextoverridenvarchar(max) → TEXTcho SQLite compat (xemtests/.../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
# 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:
# 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
ContractApprovalhoặcAuditLog(future) - Rate limit:
/api/auth/login5 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
descriptiontrongSKILL.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-usetriggers 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
---
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 <name>`
- Skill cần thêm → tạo theo §9.2 §9.3 + commit `[CLAUDE] Skill: add <name>`
- 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— entry point cho AI agentarchitecture.md— kiến trúc chi tiết (layered, CQRS, state machine)gotchas.md— pitfalls đã gặpdatabase/database-guide.md— DB conventionsflows/— sequence diagram per feature../.claude/skills/README.md— skill index 6 skill