# วาง Structure ให้ Claude รีวิว Merge Request ใน GitLab CI
Table of Contents
ทำไม structure ถึงสำคัญกว่า prompt
ตอนเริ่มผมก็คิดเหมือนหลายคน — เขียน prompt เก่งๆ แล้วโยนเข้า CI ก็จบ แต่พอจะใช้จริง ทั้งทีมหลาย repo ปัญหาโผล่มาทันที:
- prompt/script กระจาย — แต่ละ repo copy ไปแก้เอง สุดท้ายไม่มีใครรู้ว่าเวอร์ชันไหนคือของจริง
- environment ไม่เหมือนกัน — repo นึงมี
jqอีก repo ไม่มี, Claude Code คนละเวอร์ชัน ผลรีวิวเลยไม่คงเส้นคงวา - อัปเดตยาก — แก้ logic รีวิวที ต้องไปไล่ทุก repo
ทางออกของผมคือมองมันเป็น product ชิ้นหนึ่ง ที่มี source of truth เดียว แล้วแยกเป็น 3 ส่วน:
1. Workspace (git) → "สมอง" — skill + script + template ที่ทุกคน contribute ได้2. Build pipeline → "โรงงาน" — auto-build image ทุกครั้งที่ workspace มี commit3. CI/CD Component → "ทางเข้า" — repo อื่นดึง image มารันรีวิวได้ภาพรวมของทั้งระบบเป็นแบบนี้ — แบ่งชัดเป็น 2 ฝั่ง: ฝั่ง contribute → build กับฝั่ง execute:
แต่ละชิ้นเปลี่ยนแยกกันได้ และ image มีเวอร์ชันของตัวเอง — นี่คือหัวใจที่ทำให้มัน maintain ได้จริง
ชิ้นที่ 1 — Workspace ใน git
ผมเก็บทุกอย่างที่เป็น “สมอง” ของการรีวิวไว้ใน repo กลาง repo เดียว (ผมเรียกมันว่า platform/core) เป็น workspace bundle — รวม skill, script และ template ไว้ในที่เดียว ใช้ layout แบบนี้:
platform/core/├── ai/│ └── workspaces/│ └── code-review/│ ├── SKILL.md # คำสั่ง + วิธีรีวิว (สมองจริงๆ)│ ├── review-template.md # โครงรายงานรีวิวที่ตายตัว│ ├── fetch-diff.sh # ดึง diff ของ MR ผ่าน API│ ├── post-note.sh # โพสต์ผลกลับเข้า MR│ ├── Dockerfile # นิยาม base image (ชิ้นที่ 2)│ └── README.md # วิธี build / run / ใช้ใน CI├── templates/│ └── ai-review.yml # CI/CD Component (ชิ้นที่ 3)└── docs/adr/ # ADR บันทึกการตัดสินใจSKILL.md คือหัวใจ — มันบอก Claude ว่ารีวิวยังไง โฟกัสอะไร (correctness, security, audit, naming) แล้ว format ผลตาม template ก่อนโพสต์ ส่วน script เป็นแค่ “มือ” ที่ดึง diff กับโพสต์ note
ให้ทุกคนในทีม contribute ได้
จุดที่ผมตั้งใจที่สุดคือ — workspace นี้ ไม่ใช่ของผมคนเดียว แต่เป็นของทีม ใครเจอว่า AI รีวิวพลาดแบบไหน หรืออยากให้มันโฟกัสเรื่องอะไรเพิ่ม ก็ เปิด MR เข้ามาที่ platform/core ได้เลย เหมือน contribute โค้ดทั่วไป
เพื่อให้ contribute ได้อย่างปลอดภัย ผมวางกติกาไว้ใน repo:
- CODEOWNERS — การแก้
SKILL.mdหรือ Dockerfile ต้องผ่าน reviewer ที่กำหนด ไม่ใช่ใครก็ merge ได้ - README — บอกชัดว่าแต่ละไฟล์ทำอะไร build/test ยังไง คนใหม่อ่านแล้วเริ่ม contribute ได้เลย
- ADR — บันทึกเหตุผลของ design ไว้ คนที่จะเสนอเปลี่ยนจะได้เข้าใจ context ก่อน
ชิ้นที่ 2 — Base Image ที่มีของพร้อม
ปัญหา environment ไม่เหมือนกันแก้ด้วยการ bake ทุกอย่างลง image เดียว — Claude Code CLI, tool ที่ skill ต้องใช้ (ripgrep, jq, python3, git) และ workspace bundle ทั้งชุด แล้ว pin เวอร์ชันไว้ให้ผลคงเส้นคงวา
FROM node:20-bookworm-slim
# tool ที่ skill + script ต้องใช้ + tini เป็น init กัน zombie processRUN apt-get update && apt-get install -y --no-install-recommends \ ripgrep jq python3 git ca-certificates tini \ && rm -rf /var/lib/apt/lists/*
# Claude Code CLI — pin เวอร์ชันไว้เสมอRUN npm install -g @anthropic-ai/[email protected]
# คัดลอก workspace bundle เข้า imageCOPY ai/workspaces/code-review/ /workspace/code-review/
# รันด้วย user ธรรมดา ไม่ใช่ rootRUN useradd -m ciUSER ci
ENTRYPOINT ["tini", "--"]CI: auto-build image ทุกครั้งที่ workspace มี commit
ผมไม่ build image ด้วยมือ — platform/core มี pipeline ของตัวเอง ที่ build แล้ว push image เข้า registry อัตโนมัติทุกครั้งที่มี commit ใหม่บน main (เช่น พอมี MR ของเพื่อนในทีม merge เข้ามา) แปลว่าพอใครแก้ skill เสร็จ image ใหม่ก็พร้อมใช้เองโดยไม่ต้องมีใครจำไปสั่ง build
build-ai-review-image: stage: build image: docker:27 services: [docker:27-dind] rules: # build เฉพาะตอน commit เข้า main และเฉพาะเมื่อ workspace เปลี่ยน - if: $CI_COMMIT_BRANCH == "main" changes: [ai/workspaces/code-review/**/*] script: - IMAGE=$CI_REGISTRY_IMAGE/ai-review - docker build -f ai/workspaces/code-review/Dockerfile -t $IMAGE:$CI_COMMIT_SHORT_SHA . - docker tag $IMAGE:$CI_COMMIT_SHORT_SHA $IMAGE:latest - docker push $IMAGE:$CI_COMMIT_SHORT_SHA - docker push $IMAGE:latestผลคือ flow ฝั่ง contribute → build เป็นแบบนี้: เพื่อนเปิด MR แก้ skill → review/merge เข้า main → pipeline build image ใหม่ → push เข้า registry — จบโดยไม่มี manual step เลย repo ปลายทางไม่ต้องรู้ว่าข้างใน image มีอะไร แค่ดึง image ตาม version มาใช้ก็ได้ environment เดียวกันทั้ง fleet
ชิ้นที่ 3 — รันผ่าน GitLab CI/CD Component
ชิ้นสุดท้ายคือ “ทางเข้า” — แทนที่จะให้แต่ละ repo copy บล็อก .gitlab-ci.yml ไปแปะ (แล้ว maintain ไม่ไหว) ผม package เป็น GitLab CI/CD Component ที่มี spec.inputs แบบ typed
spec: inputs: stage: default: test image: default: registry.example.com/platform/ai-review:0.1.0 max_diff_lines: type: number default: 8000---ai-mr-review: stage: $[[ inputs.stage ]] image: $[[ inputs.image ]] rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" variables: MAX_DIFF_LINES: $[[ inputs.max_diff_lines ]] script: - /workspace/code-review/fetch-diff.sh # ดึง diff ของ MR - claude -p --append-system-prompt "$(cat /workspace/code-review/SKILL.md)" \ "review the diff in $CI_DIFF_FILE following the skill" > review.md - /workspace/code-review/post-note.sh review.md # โพสต์ผลกลับเข้า MRฝั่ง repo ที่อยากเปิดใช้ ก็ include มาบรรทัดเดียว:
include: inputs: max_diff_lines: 5000 # override ได้ตาม repoการ execute ใน CI — เชื่อมทุกอย่างเข้าด้วยกัน
พอ MR เปิด/อัปเดต GitLab จะ trigger pipeline แล้ว job ก็เดินตามนี้:
MR event ─► pull base image (Claude Code + tool + workspace พร้อม) ─► fetch-diff.sh : ดึง diff ผ่าน GitLab API ─► claude -p + SKILL.md : รีวิวตาม skill ─► post-note.sh : โพสต์ผลกลับเข้า MR เป็น noteเรื่อง auth ที่ต้องเตรียมในขั้นนี้:
CI_JOB_TOKEN— ใช้ clone/อ่าน MR และโพสต์ note กลับ โดยไม่ต้องสร้าง PAT แยก- API token ของ Anthropic — เก็บเป็น masked CI/CD variable ไม่ commit เข้า git เด็ดขาด
พฤติกรรมที่ทำให้ skill อยู่ร่วมกับทีมได้
structure ที่ดีต้องมาคู่กับ behavior ที่ไม่กวนทีม — สี่อย่างที่ผมใส่ไว้ใน skill/script:
- Idempotent — โพสต์ note ด้วย hidden marker แล้ว upsert (รันซ้ำ = แก้ note เดิม ไม่ spam note ใหม่ทุกรอบ)
- Preflight label —
skip-ai-review/force-ai-reviewให้คนคุมว่าจะรีวิว MR ไหน - Diff cap —
MAX_DIFF_LINES(default 8000) ถ้า MR ใหญ่เกินก็ข้ามพร้อมบอกเหตุผล แทนที่จะรีวิวกว้างๆ ไม่มีคุณภาพ - Fallback parser — parse JSON ด้วย
jq → python → awkตามที่มี (จริงๆ image เรา bake มาครบ แต่กันเหนียวไว้)
เริ่มเล็กแล้วค่อยโต
ผมไม่ได้ทำใหญ่ตั้งแต่วันแรก — เวอร์ชันแรก trim เหลือ minimal v0.1.0 ราว 19 ไฟล์ (workspace + skill + Dockerfile + CI Component + ADR ไม่กี่ตัว) แล้วลองกับ MR จริงก่อน รอบแรกมันจับ bug จริงได้ 4 ตัว (cache key ผิด, ลืม audit transaction, สร้าง URI ผิด, typo) — พอพิสูจน์ว่าได้ผลค่อยขยาย
ADR (Architecture Decision Record) ช่วยมากในจุดนี้ — ผมบันทึกว่าทำไมเลือก workspace-bundle, ทำไม CI Component, ทำไม pin เวอร์ชัน ไว้ใน docs/adr/ ครั้งหน้ากลับมาดูก็รู้เหตุผล ไม่ต้องเดาว่าตอนนั้นคิดอะไรอยู่
สรุป
key takeaway ของโพสต์นี้คือ — AI review ที่ใช้ได้ทั้งทีม อยู่ที่ structure ไม่ใช่ prompt แยกให้ชัดเป็น 3 ชิ้น:
- Workspace ใน git — skill + script + template เป็น source of truth เดียว ที่ ทุกคนในทีม contribute ผ่าน MR ได้ (มี CODEOWNERS + ADR คุม)
- Build pipeline — auto-build image ทุกครั้งที่ workspace มี commit ใหม่ แล้ว push เข้า registry แบบมีเวอร์ชัน
- CI/CD Component — repo อื่น
includeมาแล้ว pipeline ก็ดึง image นั้นมารันรีวิวได้เลย
พอวางสามชิ้นนี้แล้ว เพื่อนในทีมแก้ skill ผ่าน MR → image build เองอัตโนมัติ → repo ทั้ง fleet ได้ของใหม่โดยไม่ต้องไล่แก้ทีละที่ และการเพิ่ม repo ใหม่เข้าระบบก็เหลือแค่ include บรรทัดเดียว — นั่นแหละคือความต่างระหว่าง “เล่น AI review” กับ “มี AI review ที่ scale ได้จริง”