CLAUDE.md ที่ดีไม่ใช่ README — มันเก็บ invariant ที่ Claude อ่านโค้ดไม่ออก. 6 ข้อต้องเขียน, 4 ข้อที่ควรเลี่ยง, 5 คำถาม.
โปรเจกต์ Pickful ของผมมีระบบคณะลูกขุนแบบชุมชนกระจายศูนย์ ชำระเงินคริปโตแบบ x402 Sign-In with Ethereum มัลติดาต้าเบส พุชเรียลไทม์ — ล้วนเป็นสแต็กที่เพิ่งโผล่มาในช่วง 1-2 ปีที่ผ่านมา Claude ส่งมอบฟีเจอร์เหล่านี้เร็วและสะอาด
แต่เปิด CLAUDE.md ของโปรเจกต์ดู คุณจะเจอว่า: ระบบคณะลูกขุน และ x402 — ไม่ถูกเอ่ยถึงแม้แต่ครั้งเดียว
นี่ไม่ใช่ลืม จุดมุ่งหมายของ CLAUDE.md ไม่เคยเป็น "อธิบายฟีเจอร์" หน้าที่มันคือบันทึกสิ่งที่ Claude ไม่มีทางอนุมานได้จากการอ่านโค้ด
คนที่เขียน CLAUDE.md ครั้งแรกมักปฏิบัติต่อมันเหมือน README — อธิบายทุกฟีเจอร์หลัก:
เนื้อหาแบบนี้ Claude เปิด topic_review_service.rb / x402.rb / like_points_service.rb แล้วอ่านได้แม่นยำกว่าที่คุณเขียน คำอธิบายลอจิกธุรกิจหนึ่งพันคำ Claude อ่านจากโค้ดเสียไปไม่กี่ร้อย token และไม่มีการเบี่ยงเบนทางการตีความ — โค้ดคือข้อเท็จจริง คำอธิบายคือข้อมูลมือสอง
ที่ Claude สะดุดจริง ๆ คือ 6 หมวดต่อไปนี้
CLAUDE.md ของ Pickful มีบรรทัดแบบนี้:
Propshaft (not Sprockets)
ImportMap (no JavaScript bundler)
Hotwire: Turbo Frames, Turbo Streams, Stimulus
Lexxy gem overrides ActionText:
config.lexxy.override_action_text_defaults = false
ทุกบรรทัด ต่อต้านการเดาแบบดีฟอลต์ เห็นโปรเจกต์ Rails สมมุติฐานดีฟอลต์ของ Claude คือ:
หากไม่เขียนบรรทัดพวกนี้ใน CLAUDE.md ขอให้ Claude เพิ่มฟีเจอร์ JS ใหม่ มีโอกาสสูงเขาจะติดตั้ง Webpacker แก้ package.json เขียน config bundler — ผิดหมด และผิดเงียบ (แอปยังวิ่งได้ แต่ pipeline ของ asset ถูกปนเปื้อน)
บรรทัดพวกนี้ใน CLAUDE.md กำลังบอก Claude ว่า: อย่าเดา ตัดสินใจแล้ว
PostgreSQL with 4 separate databases:
- primary - Main application data
- cache - Solid Cache storage
- queue - Solid Queue jobs
- cable - Action Cable subscriptions
เขียนเรียบ ๆ แต่ช่วยประหยัดทั้งคืน Rails 8 มี multi-DB เป็นดีฟอลต์ซึ่งเป็นพฤติกรรมใหม่ Claude ไม่ไปเช็คเองว่าคุณใช้กี่ DB migration ที่ดูไม่เกี่ยวข้องไปลงผิด DB ที่ dev ไม่ error (ทั้งสี่เป็น PostgreSQL schema ผ่านได้ทุกที่) แต่ที่ production ตาราง job ของ Solid Queue แอบเข้า backup ของ primary หรือ model ของ primary ไป query DB cache — บักแบบนี้ต้องใช้เวลาหลายวันจึงโผล่
สองบรรทัดใน CLAUDE.md vs. หนึ่งวันไล่บักที่ production
/p-{slug} - Short post URLs (4-5 char alphanumeric)
/t-{slug} - Topic URLs (3-4 char alphanumeric)
/s-{code} - Short URL redirects (3-4 char alphanumeric)
/r-{referral} - Referral links
เส้นทางเอง Claude เห็นใน routes.rb แต่ ข้อตกลงเรื่องความยาว (4-5 ตัว, 3-4 ตัว) ฝังอยู่ใน logic สร้าง slug ของ model หรือ service ขอ Claude ให้เพิ่ม short link ชนิดใหม่ เขามีโอกาสสร้าง slug 6 ตัว สไตล์ UUID หรือเลขล้วน — หลุดจากภาษาภาพของทั้งระบบ
คุณสมบัติของ "ข้อตกลง" แบบนี้: ฝ่าฝืนไม่ error แต่คนอ่านโค้ดคนถัดไปจะรู้สึกว่าอะไรบางอย่างเพี้ยน ต้องเขียน
VIP status at 400+ points
Posts with 15+ likes are "hot" posts
สองเลขนี้อยู่ที่ใดที่หนึ่งในโค้ด (User#vip?, scope Post#hot?) ปัญหาคือเมื่อ Claude แก้อะไรที่เกี่ยว — ปรับรางวัลคะแนน เพิ่มแจ้งเตือน "ใกล้เป็น VIP" เขียน cron ปักหมุด hot post — เขาจะไม่ปรับเกณฑ์ที่จุดอื่น ๆ ให้ตรงกันโดยอัตโนมัติ
ผล: คุณให้รางวัล 500 คะแนนสำหรับ task หนึ่ง แต่ copy บอก "กำลังจะเป็น VIP" (จริง ๆ 400 ก็พอ); หรือทำ seed data ให้ฟีเจอร์ใหม่ด้วยจำนวนไลก์น้อยเกินไป จนไม่เคยแตะเกณฑ์ 15
ความสามารถโค้ดของ Claude แข็ง แต่เขาไม่มี ความรู้สึกเรื่องตัวเลขในระดับระบบทั้งหมด การใส่เกณฑ์สำคัญใน CLAUDE.md ทำให้ทุกครั้งที่เริ่มคุยเขารู้ตั้งแต่ต้นว่า "400 กับ 15 เป็นตัวเลขพิเศษ"
- Devise (authentication) + Pundit (authorization)
- Pundit policies in app/policies/
- Check UserPolicy, PostPolicy, etc. for permission rules
หน้าที่ของบรรทัดนี้คือ นำทาง ไม่ใช่บรรยาย
หากไม่มี เมื่อ Claude ต้องเพิ่มการตรวจสิทธิ์ใหม่ มีสามความเป็นไปได้:
unless current_user.admin? ตรง ๆ ใน controllerauthorize? ของตัวเองใน modelมี "Pundit policies in app/policies/" เขียนไว้ Claude ไปที่ app/policies/ เพิ่มไฟล์ policy ทุกครั้ง — สไตล์สม่ำเสมอ
หนึ่งบรรทัดตัด "งานนักสืบ" ของ Claude ออกทุกครั้ง
Supported locales: en, zh-CN, zh-TW
Testing stack: RSpec + FactoryBot + Capybara + Shoulda Matchers
เวลาเพิ่มฟีเจอร์ใหม่ ดีฟอลต์ของ Claude คือ:
แต่โปรเจกต์ของคุณจริง ๆ ต้องการ:
การฝ่าฝืน "ข้อจำกัดภายนอกระดับโปรเจกต์" แบบนี้จะสร้างงานตกค้างจำนวนมาก — แปลที่ต้องเพิ่ม เทสต์ที่ต้องเขียนใหม่ เขียนใน CLAUDE.md คือการตอกตะปู "สิ่งที่ต้องทำทุกครั้ง" ให้เรียบร้อยครั้งเดียว
สำคัญพอกับ "ต้องเขียน" คือ "อย่าเขียน" หมวดต่อไปนี้ เห็น = ลบ:
1. บรรยายฟีเจอร์
"ระบบคณะลูกขุน: ผู้ใช้สามารถรายงานเนื้อหาที่ละเมิดกฎ เนื้อหาที่รายงานเข้าสู่ขั้นตอนโหวตสาธารณะ คณะลูกขุนถูกเลือกจาก..."
→ Claude เปิด topic_review_service.rb แล้วอ่านแม่นกว่าที่คุณเขียน ยัดสิ่งนี้เข้าทุกการสนทนาใหม่คือละลาย token ล้วน ๆ
2. สิ่งที่อ่านจากโครงสร้างไดเรกทอรี / Gemfile ได้ทันที
"app/models/ เก็บ ActiveRecord model", "ใช้ Rails 8", "DB คือ PostgreSQL"
→ Claude กวาดตา root ของโปรเจกต์และ Gemfile ครั้งเดียวก็รู้
3. ความรู้เขียนโปรแกรมทั่วไป
"Controllers should be thin, delegate to services", "เลี่ยง N+1", "เขียนเทสต์ให้ฟีเจอร์หลัก"
→ อยู่ใน training data ของ Claude อยู่แล้ว เขียนก็ต่อเมื่อโปรเจกต์คุณ ผิดปกติ เท่านั้น — เช่น "เราจงใจไม่ใช้ service layer; ลอจิกอยู่ใน controller"
4. บริบทของงานปัจจุบัน
"ตอนนี้เรากำลัง refactor ระบบชำระเงิน; โฟกัสที่..."
→ นี่คือบริบทการสนทนา ไม่ใช่ข้อเท็จจริงของโปรเจกต์ ยัดลงใน CLAUDE.md จะปนเปื้อนทุกการสนทนาอื่น
วางหลักฐาน "ผมทำได้" ไว้หน้าคำเทศนา หลังเขียนส่วนข้างบน ผมเอา CLAUDE.md ของ Pickful — 238 บรรทัด — ผ่าน 5 คำถามของตัวเอง ผลลัพธ์: ประมาณครึ่งหนึ่งคือเสียเปล่า
ที่ต้องตัด (~120 บรรทัด):
ส่วนใหญ่ของบล็อก dev commands (70 บรรทัด → 10): bin/setup / bin/rails db:migrate / bundle exec rspec / bin/rubocop / bin/brakeman ล้วนเป็นคำสั่ง Rails มาตรฐาน อยู่ใน training ของ Claude แล้ว เก็บแค่สามที่เฉพาะของโปรเจกต์: bin/jobs (Solid Queue worker), bin/importmap pin (เฉพาะ ImportMap), bin/kamal deploy
รายการ Core Domain Models (35 บรรทัด → ลบทิ้ง): ไล่รายชื่อ 20 model กับบทบาทคือตัวอย่างคลาสสิกของ "CLAUDE.md สไตล์ README" — Claude รัน ls app/models/ หรืออ่านไฟล์ model หนึ่งไฟล์ก็รู้ ยัดทุกการสนทนาคือเสียเปล่าล้วน
รายการมาตรฐานใน Tech Stack (28 บรรทัด → 8): Rails 8 / Devise / Pundit / Tailwind / pg_search ล้วนอ่านจาก Gemfile ได้ทันที เก็บเฉพาะที่สวนสัญชาตญาณ: Propshaft / ImportMap / Lexxy / x402-rails / Grover
ความรู้เขียนโปรแกรมทั่วไปที่กระจัดกระจาย: "Controllers should be thin", "Use app/jobs/ for async processing", request specs vs. model specs ทดสอบอะไร — Claude ทำโดยดีฟอลต์อยู่แล้ว เปลือง token
ที่เหลือ ~100 บรรทัด: ข้อตกลง URL routing, การแบ่ง 4 DB, เกณฑ์ VIP 400 / Hot 15, override Lexxy, ทางเลือกสถาปัตยกรรมแบบต้านดีฟอลต์, ป้าย Pundit, รายการ locale, FactoryBot (ไม่ใช่ fixtures)
แต่สำคัญกว่า: invariants อะไรที่ยังไม่ได้เขียน?
ระหว่างตรวจสอบ ผมตระหนักถึงหลายข้อที่ควรเพิ่มแต่ไม่เคยเพิ่ม:
238 บรรทัดบีบเหลือ 100–120 บรรทัด บวกเพิ่ม 5–10 บรรทัดของ invariants ที่พลาดไป — นั่นจึงใกล้กับ "ความหนาแน่นที่ถูกต้อง" ของ CLAUDE.md
นี่ไม่ใช่ tutorial นี่คือการตรวจสอบโปรเจกต์ของผมเอง — ผมก็เขียนไม่ถูกเหมือนกัน รูปแบบที่ถูกของ CLAUDE.md คือ ลบไปเรื่อย ๆ และเพิ่มไปเรื่อย ๆ — โปรเจกต์ที่แก่ขึ้นหน่อย CLAUDE.md ควรสั้นลงและแน่นขึ้น
ทุกครั้งที่อยากจะเพิ่มอะไรใน CLAUDE.md ผมจะผ่าน checklist ดังต่อไปนี้:
กฎที่ผ่านทั้ง 5 เก็บไว้; ข้อใดที่ตอบไม่ได้ — ลบหรือเขียนใหม่
การใช้ CLAUDE.md ที่ถูกไม่ใช่ "แนะนำโปรเจกต์" แต่คือ บีบอัดความรู้ซ่อนเร้นระหว่างคุณกับ codebase ที่ Claude ไม่มีทางเติมให้ได้จากการอ่านโค้ด — ทางเลือกผิดปกติ เกณฑ์มองไม่เห็น ข้อตกลงที่ขัดกับดีฟอลต์ ข้อจำกัดภายนอกระดับโปรเจกต์
ทุกบรรทัดที่คุณเพิ่มต้องตอบคำถามนี้: "สิ่งนี้ Claude อ่านจากโค้ดออกไม่ได้หรือ?" ไม่ได้ — เก็บ ได้ — ลบ
CLAUDE.md ที่เขียนแบบนี้มักสั้นกว่าฉบับร่างแรกเกินครึ่ง แต่มีค่าต่อการสนทนาแต่ละครั้งมากกว่าคำอธิบายฟีเจอร์หลายพันคำ และมัน "ยังไม่เสร็จ" ตลอด — ทุกฟีเจอร์ใหม่ที่ส่งไป จะเผยให้เห็น invariant อีกอันที่ควรอยู่ในนั้น และย่อหน้าเก่าอีกย่อหน้าที่ตอนนี้ตัดได้ ลบและเพิ่ม ลบและเพิ่ม — นั่นคือการบำรุงรักษาประจำวันของ CLAUDE.md