From a4873e64bfbe36f5b28d16975e1006ea89bbb03a Mon Sep 17 00:00:00 2001 From: "andrew.ng" Date: Tue, 2 Jun 2026 13:57:47 +0700 Subject: [PATCH] Update AI Review job claude --- .claude/commands/ai-review.md | 157 +++++++++++++++++++++++ .claude/commands/review-task-jira.md | 97 ++++++++++++++ .claude/commands/smoke-test-checklist.md | 143 +++++++++++++++++++++ 3 files changed, 397 insertions(+) create mode 100644 .claude/commands/ai-review.md create mode 100644 .claude/commands/review-task-jira.md create mode 100644 .claude/commands/smoke-test-checklist.md diff --git a/.claude/commands/ai-review.md b/.claude/commands/ai-review.md new file mode 100644 index 0000000..6475ff4 --- /dev/null +++ b/.claude/commands/ai-review.md @@ -0,0 +1,157 @@ +--- +description: AI review một PR/commit Gitea theo thứ tự Security → Logic → Performance → Consistency → Simplicity +argument-hint: +allowed-tools: Bash, Read, Grep, Glob +--- + +# /ai-review + +Đầu vào (`$ARGUMENTS`) là **một link Gitea**, hai dạng: + +- Pull request: `https://///pulls/` (chấp nhận cả `/pull/`). +- Commit: `https://///commit/` (chấp nhận cả `/commits/`). + +Nếu thiếu link, **dừng** và yêu cầu cung cấp. + +## Credentials Gitea (Basic Auth) + +- Username: `andrew.ng@apactech.io` +- Password: `andrew.ng@123` + +Gọi API bằng `curl -s -u "andrew.ng@apactech.io:andrew.ng@123" -H "Accept: application/json" --fail-with-body ""`. **Không** in lệnh kèm password ra response. Nếu 401/403, báo lỗi auth, không in credentials. + +## Các bước phải làm + +### 1. Parse link + +Từ URL suy ra: + +- `GITEA_HOST` = scheme + host. +- `OWNER`, `REPO`. +- `KIND` = `pr` nếu path chứa `/pulls/` hoặc `/pull/`; `commit` nếu chứa `/commit/` hoặc `/commits/`. +- `REF` = số PR (cho `pr`) hoặc SHA (cho `commit`). + +Nếu không khớp pattern trên, hỏi lại thay vì đoán. + +### 2. Lấy dữ liệu để review + +Base API: `${GITEA_HOST}/api/v1/repos/${OWNER}/${REPO}` + +#### KIND = pr + +- `GET /pulls/${REF}` → metadata (title, body, base.ref, head.ref, head.sha, state, merged). +- `GET /pulls/${REF}/commits` → list commit + message. +- `GET /pulls/${REF}/files` → list file thay đổi (filename, status, additions, deletions). +- Lấy **diff đầy đủ** để đọc nội dung: `GET ${GITEA_HOST}/${OWNER}/${REPO}/pulls/${REF}.diff` (raw web endpoint, vẫn cùng Basic Auth). Lưu vào `/tmp/ai-review-${REF}.diff`. + +#### KIND = commit + +- `GET /git/commits/${REF}` → message, author, files (nếu có). +- Lấy diff đầy đủ: `GET ${GITEA_HOST}/${OWNER}/${REPO}/commit/${REF}.diff` lưu vào `/tmp/ai-review-${REF}.diff`. + +**Fallback ưu tiên dùng git local** nếu `GITEA_HOST` trỏ về repo hiện tại (so sánh `git remote -v`): khi đó dùng `git show ` / `git diff ..` để có diff đầy đủ + nhanh hơn, không phải hit Gitea. Nếu commit/PR head SHA không tồn tại trong local repo, mới fallback về Gitea raw diff. + +### 3. Đọc context xung quanh + +Trước khi nhận xét, với mỗi file đụng đến trong diff: + +- Mở file bằng `Read` (ưu tiên đọc nguyên file nếu < 800 dòng; nếu lớn hơn, đọc các vùng quanh dòng thay đổi). +- Đọc `CLAUDE.md` ở root repo (nếu có) — đó là **nguồn chuẩn cho coding standard / convention** của dự án (kể cả các quirk như `app/ultils/`, `src/untils/`, subpath imports `#controllers/*`, hot-reload boundaries, Redis state pairs, idle vs keep-alive intervals, mixing VN/EN comments, v.v.). +- Nếu repo có `.editorconfig`, `eslint.config.*`, `tsconfig.json`, `.prettierrc*` → đọc để biết coding standard cụ thể. + +### 4. Thực hiện review theo **đúng thứ tự** dưới đây + +Với MỖI mục, output: + +- Status: ✅ Pass / ⚠️ Cần cải thiện / ❌ Có vấn đề. +- Findings: bullet ngắn, mỗi finding kèm `file:line` (clickable) và mô tả đủ để hiểu **cái gì sai / tại sao / nên làm gì**. +- Nếu không có gì để nói: ghi rõ "Không phát hiện vấn đề" — không bịa. +- Ghi ngắn gọn nội dung + +Thứ tự **cố định**: + +#### 4.1. Security + +Soi các nguy cơ: + +- Injection (SQL, command, prompt, log), template/string interpolation từ input người dùng. +- Hardcoded secret / token / credential trong code mới. +- AuthZ/AuthN: endpoint mới có thuộc middleware `auth` không? (theo `start/kernel.ts` + `start/routes.ts`). Có lộ thông tin user khác không? +- Unsafe deserialization / `eval` / `Function`. +- File path không sanitize (path traversal) khi đọc/ghi file. +- Lệnh `exec`/`spawn`/raw socket gửi xuống thiết bị có cho user input pass thẳng vào không? +- CORS / cookie / token handling thay đổi (đặc biệt trong `socket_io_provider.ts`). +- Logging có in PII / password / token không. + +#### 4.2. Logic & Edge Cases + +- Happy path đúng chưa? Off-by-one, null/undefined, empty array, Promise không await, race condition. +- Error path: try/catch có nuốt lỗi không, có rollback / restore state không. +- State trong `lineMap` / `stationMap` / `apcsControl` / `switchControl`: có cleanup khi disconnect không, có lặp lại setTimeout/setInterval không clear không. +- Redis state (`socket_state`, `station:{id}:line:{id}:history`): khi thêm field mới, `saveState` / `restoreState` có cover không. +- Idle-timeout (`setTimeoutConnect`, 8h) **và** keep-alive (`keepConnectAPC` 40s / `keepConnectStation` 120s) có được wire đầy đủ cho connection mới không (theo CLAUDE.md). +- Socket event mới: cả hai phía FE/BE có khớp tên + payload shape không. + +#### 4.3. Performance + +- Vòng lặp lồng nhau / N+1 query Lucid (eager loading?). +- `await` trong `for` thay vì `Promise.all` khi có thể song song. +- Re-render React thừa (`App.tsx` đang dùng `lineBuffersRef` + flush 50ms — diff mới có phá vỡ pattern này không). +- Bộ nhớ giữ output dài (truncate ở `saveState` 5000 chars — diff mới có giữ thêm field nặng không). +- Setinterval/setTimeout có dồn (leak) không. + +#### 4.4. Consistency + +Hai gạch đầu dòng phụ: + +- **Đúng `CLAUDE.md`?** Đối chiếu từng quy ước liên quan: subpath imports `#controllers/*` (không phải relative trừ file ngoài `app/`), folder names `ultils`/`untils` (không rename), VN/EN comment language giữ nguyên, hot-reload boundaries, idle/keep-alive paired, `saveState`/`restoreState` extend khi thêm state. +- **Đúng coding standard?** ESLint flat config / Prettier / TS strict / naming convention (camelCase biến, PascalCase component, `T*` prefix cho type ở FE). Đặt tên file/component có nhất quán không. Comment có ý nghĩa, không lặp lại code. + +#### 4.5. Simplicity + +- Có code trùng lặp với chỗ đã có (helper trong `BACKEND/app/ultils/helper.ts`, hook/component sẵn ở FE) mà nên reuse không. +- Có over-engineering, abstraction sớm. +- Có thể gộp branch / sớm return để giảm nesting. +- Đề xuất cụ thể cách rút gọn (mỗi đề xuất kèm trước/sau ngắn nếu hữu ích). + +### 5. Trả về kết quả + +Format output (Vietnamese): + +``` +# AI Review — # + +## 1. Security — +- +... + +## 2. Logic & Edge Cases — +- +... + +## 3. Performance — +- +... + +## 4. Consistency — +### Đúng CLAUDE.md? +- <điểm phù hợp / vi phạm> +### Đúng coding standard? +- <điểm phù hợp / vi phạm> + +## 5. Simplicity — +- — <đề xuất rút gọn> +... + +## Tổng kết +- finding bắt buộc fix (❌) +- finding nên cải thiện (⚠️) +- Đánh giá chung: <1–2 câu> +``` + +### 6. Không tự ý + +- **Không** sửa code (đây là review only). Nếu muốn fix, người dùng sẽ chạy `/simplify` hoặc `/code-review --fix` riêng. +- **Không** comment lên Gitea / Jira. +- **Không** approve / merge / close PR. +- **Không** in password. diff --git a/.claude/commands/review-task-jira.md b/.claude/commands/review-task-jira.md new file mode 100644 index 0000000..e4ce854 --- /dev/null +++ b/.claude/commands/review-task-jira.md @@ -0,0 +1,97 @@ +--- +description: Đọc task Jira + PR/commit Gitea, sinh comment ghi nhận công việc đã làm theo format ngày +argument-hint: +allowed-tools: mcp__claude_ai_Atlassian_Rovo__getJiraIssue, mcp__claude_ai_Atlassian_Rovo__getAccessibleAtlassianResources, Bash, Read, Grep, Glob +--- + +# /review-task-jira + +Đầu vào (`$ARGUMENTS`) gồm **hai link**, có thể đứng theo thứ tự bất kỳ, cách nhau bằng dấu cách hoặc xuống dòng: + +1. **Link Jira task** — `https://.atlassian.net/browse/ABC-123` (hoặc thẳng issue key `ABC-123`). +2. **Link Gitea** — một trong hai dạng: + - Pull request: `https://///pulls/` (cũng chấp nhận `/pull/`). + - Commit: `https://///commit/` (cũng chấp nhận `/commits/`). + +Nếu thiếu một trong hai, **dừng** và yêu cầu người dùng cung cấp đầy đủ. + +## Credentials + +### Jira + +Dùng MCP `claude_ai_Atlassian_Rovo` (đã xác thực sẵn). Không đăng nhập thủ công bằng `curl`. + +### Jira (HTTP Basic Auth) + +- Username: `andrew.ng@apactech.io` +- Password: `fdpF8Dqb` + +### Gitea (HTTP Basic Auth) + +- Username: `andrew.ng@apactech.io` +- Password: `andrew.ng@123` + +Khi gọi API Gitea bằng `curl`, dùng `-u "andrew.ng@apactech.io:andrew.ng@123"` và `--silent --show-error --fail-with-body`. **Không** in lệnh kèm password ra ngoài log/response cho người dùng. + +## Các bước phải làm + +### 1. Parse hai link + +- Tách `$ARGUMENTS` thành 2 URL. Phân biệt: + - URL chứa `atlassian.net/browse/` hoặc khớp regex `[A-Z][A-Z0-9_]+-\d+` đơn lẻ → **Jira**. + - URL còn lại → **Gitea**. Từ Gitea URL tự suy ra: + - `GITEA_HOST` = scheme + host (vd. `https://gitea.apactech.io`). + - `OWNER`, `REPO`. + - `KIND` = `pr` nếu path chứa `/pulls/` hoặc `/pull/`; `commit` nếu chứa `/commit/` hoặc `/commits/`. + - `REF` = số PR (cho `pr`) hoặc SHA (cho `commit`). +- Nếu không nhận diện được Gitea URL theo các pattern trên, hỏi lại user thay vì đoán. + +### 2. Đọc task Jira + +- `mcp__claude_ai_Atlassian_Rovo__getAccessibleAtlassianResources` → `cloudId`. +- `mcp__claude_ai_Atlassian_Rovo__getJiraIssue` với `issueIdOrKey` đã trích. +- Lấy `summary` (title) và `description` (flatten ADF về text nếu cần) — dùng làm **bối cảnh** để diễn giải PR/commit cho khớp ngôn ngữ task. + +### 3. Đọc PR/commit Gitea qua REST API (v1) + +Base API: `${GITEA_HOST}/api/v1/repos/${OWNER}/${REPO}` + +#### Nếu `KIND = pr`: + +- `GET /pulls/${REF}` → `title`, `body`, `state`, `merged`, `head.sha`, `base.ref`, `head.ref`, `user.login`, `created_at`, `merged_at`. +- `GET /pulls/${REF}/commits` → danh sách commit (`sha`, `commit.message`). +- `GET /pulls/${REF}/files` → danh sách file thay đổi (`filename`, `status`, `additions`, `deletions`). Nếu danh sách dài, tóm tắt theo nhóm thư mục (vd. `BACKEND/app/...`, `FRONTEND/src/...`). + +#### Nếu `KIND = commit`: + +- `GET /git/commits/${REF}` → `commit.message`, `author`, `files` (nếu có). +- Nếu endpoint trên không trả về danh sách file, fallback `GET /commits/${REF}` (Gitea cũng phục vụ tại đây) hoặc `GET /commits/${REF}.diff` (raw diff — chỉ dùng khi cần đếm file/dòng). + +> Tất cả request đều: `curl -s -u "andrew.ng@apactech.io:andrew.ng@123" -H "Accept: application/json" ""`. Nếu nhận 401/403, **báo lỗi auth** thay vì in credentials. + +### 4. Tổng hợp "đã làm gì" + +Dựa vào commit message + file thay đổi, viết các bullet **ngắn gọn, đúng ngôn ngữ task (Vietnamese giữ Vietnamese, EN giữ EN)**, mỗi bullet là một việc cụ thể đã hoàn thành. Quy tắc: + +- Ưu tiên mô tả **theo hành vi/feature** (vd. "Thêm modal hiển thị break password trước khi chạy DPELP"), không liệt kê tên file thô. +- Nếu một PR/commit gộp nhiều feature, gom theo nhóm. +- Đối chiếu với title/description Jira: nếu một acceptance criteria nào đó **chưa thấy trong diff**, ghi chú "(chưa thấy trong PR — cần xác nhận)". +- Không bịa thêm việc ngoài diff. + +### 5. Trả về đúng format dưới đây + +Ngày = hôm nay theo `TIME_ZONE` trong `BACKEND/.env` (project đang chạy), format `DD/MM/YYYY`. Output thuần văn bản (không bọc `code block`), sẵn sàng paste vào ô comment Jira: + +``` +Ngày DD/MM/YYYY + - + - + - ... +``` + +### 6. Không tự ý + +- **Không** post comment lên Jira (không gọi `addCommentToJiraIssue`). Chỉ in nội dung ra để user copy. +- **Không** transition issue, không edit Jira fields. +- **Không** push/merge gì lên Gitea. +- **Không** in password ra response. diff --git a/.claude/commands/smoke-test-checklist.md b/.claude/commands/smoke-test-checklist.md new file mode 100644 index 0000000..1f92c24 --- /dev/null +++ b/.claude/commands/smoke-test-checklist.md @@ -0,0 +1,143 @@ +--- +description: Sinh smoke test checklist (Happy / Empty / Error / Responsive / Data Edge) từ PR/commit Gitea +argument-hint: +allowed-tools: Bash, Read, Grep, Glob +--- + +# /smoke-test-checklist + +Đầu vào (`$ARGUMENTS`) là **một link Gitea**: + +- Pull request: `https://///pulls/` (chấp nhận cả `/pull/`). +- Commit: `https://///commit/` (chấp nhận cả `/commits/`). + +Nếu thiếu link, **dừng** và yêu cầu cung cấp. + +## Credentials Gitea (Basic Auth) + +- Username: `andrew.ng@apactech.io` +- Password: `andrew.ng@123` + +Gọi API bằng `curl -s -u "andrew.ng@apactech.io:andrew.ng@123" -H "Accept: application/json" --fail-with-body ""`. **Không** in lệnh kèm password ra response. Nếu 401/403, báo lỗi auth, không in credentials. + +## Các bước phải làm + +### 1. Parse link + +- `GITEA_HOST` = scheme + host. `OWNER`, `REPO`. +- `KIND` = `pr` (path chứa `/pulls/` hoặc `/pull/`) | `commit` (path chứa `/commit/` hoặc `/commits/`). +- `REF` = số PR (cho `pr`) hoặc SHA (cho `commit`). + +Nếu không khớp pattern → hỏi lại thay vì đoán. + +### 2. Lấy diff để hiểu phạm vi thay đổi + +Base API: `${GITEA_HOST}/api/v1/repos/${OWNER}/${REPO}` + +#### KIND = pr + +- `GET /pulls/${REF}` → `title`, `body`, `base.ref`, `head.ref`, `head.sha`, `state`, `merged`. +- `GET /pulls/${REF}/files` → list file thay đổi. +- Raw diff: `GET ${GITEA_HOST}/${OWNER}/${REPO}/pulls/${REF}.diff` → lưu `/tmp/smoke-${REF}.diff`. + +#### KIND = commit + +- `GET /git/commits/${REF}` → `message`, `files`. +- Raw diff: `GET ${GITEA_HOST}/${OWNER}/${REPO}/commit/${REF}.diff` → lưu `/tmp/smoke-${REF}.diff`. + +**Ưu tiên git local** nếu `git remote -v` trỏ về cùng `GITEA_HOST/${OWNER}/${REPO}`: dùng `git show ` / `git diff ..` thay vì hit Gitea. + +### 3. Phân loại thay đổi + +Đọc diff + (nếu cần) mở file gốc bằng `Read` để xác định **bề mặt cần test**: + +- **UI / FE**: file dưới `FRONTEND/src/` (components, modals, pages, routing, `App.tsx`, terminal). +- **API / BE**: file dưới `BACKEND/app/controllers/`, routes (`start/routes.ts`), models, migrations. +- **Socket event**: handler mới/sửa trong `BACKEND/providers/socket_io_provider.ts` hoặc handler trong `FRONTEND/src/App.tsx`'s big `useEffect` / `SocketContext`. +- **Device interaction**: thay đổi trong `BACKEND/app/services/{line,station,apc,switch}_connection.ts` (gửi command xuống thiết bị, scenario, DPELP, physical test, IOS/license load). +- **Persisted state**: thay đổi `saveState`/`restoreState`, key Redis `socket_state` hoặc `station:{id}:line:{id}:history`, migration MySQL. + +Ghi nhớ phân loại để generate checklist phù hợp (vd. không thêm "Mobile / Responsive" cho thay đổi thuần BE). + +### 4. Sinh checklist + +**5 nhóm bắt buộc** (đúng thứ tự, đúng tên): + +#### Happy Path + +Kịch bản chính của tính năng vừa thêm/sửa, đi từ A→Z với input hợp lệ. Mỗi mục mô tả **hành động cụ thể + kỳ vọng quan sát được**. + +#### Empty State + +- Danh sách rỗng, chưa có dữ liệu. +- User chưa chọn line/station nào. +- Modal mở khi không có context (chưa connect, chưa run scenario, v.v.). +- Trang load lần đầu chưa có `localStorage.user`. + +#### Error Case + +- API trả 4xx/5xx. +- Socket disconnect giữa chừng (mất line/station session → cần auto-reconnect theo `handleLineOperation`). +- TCP session timeout / `setTimeoutConnect` chạm 8h. +- Device không phản hồi command (vd. APC, switch). +- Input invalid (tên rỗng, ký tự đặc biệt, số âm). +- Lỗi từ Lucid model (unique constraint, FK). + +#### Mobile / Responsive + +**Chỉ thêm nếu** diff đụng FE UI; nếu thuần BE thì ghi `N/A — thay đổi không ảnh hưởng UI`. + +- Viewport ≤ 768px (mobile), 1024px (tablet), ≥ 1440px (desktop). +- Layout không bị tràn, không che button. +- Modal stacking (terminal modal, nested modal) vẫn đúng. +- Drag tabs (`DragTabs.tsx`) còn dùng được trên touch. +- Terminal xterm fit lại khi resize. + +#### Data Edge Case + +- Chuỗi rất dài (output line, log, scenario name → buffer truncate ở `saveState` 5000 chars). +- Ký tự Unicode / emoji / VN dấu trong tên station, scenario, comment. +- Số rất lớn / âm / 0 cho port number, line number, timeout. +- Concurrent: 2 user thao tác cùng 1 line/station (CLI ownership trong `userConnecting`). +- Race với keep-alive (`keepConnectAPC` 40s / `keepConnectStation` 120s) đúng lúc user gửi command. +- Restart backend → state restore đúng từ Redis (`restoreState`). + +### 5. Trả về kết quả + +Format markdown (Vietnamese), checkbox GitHub-style `- [ ]`: + +``` +# Smoke Test Checklist — # + +## Happy Path +- [ ] +- [ ] +... + +## Empty State +- [ ] +... + +## Error Case +- [ ] +... + +## Data Edge Case +- [ ] +... +``` + +### 6. Nguyên tắc viết checklist + +- Mỗi item bắt đầu bằng **động từ hành động** (Mở, Nhấn, Gửi, Disconnect…) + **kỳ vọng quan sát được**, không viết chung chung "test feature X". +- Bám sát diff: chỉ liệt kê case **liên quan trực tiếp** thay đổi này. Không generate checklist generic cho cả app. +- Đặt item theo thứ tự dễ thực hiện (UI flow trước, edge sau). +- Nếu một nhóm không có case nào áp dụng → ghi rõ `N/A — `, không tự bịa. +- Tổng số item khuyến nghị: 3–8 / nhóm. Nếu nhiều hơn, gom case tương đương. + +### 7. Không tự ý + +- **Không** tự chạy test, không khởi động app (nếu user muốn, họ chạy `/run` hoặc `/verify` riêng). +- **Không** comment lên Gitea / Jira. +- **Không** sửa code. +- **Không** in password.