Dobry CLAUDE.md to nie README — zapisuje inwarianty, których Claude nie wywnioskuje z kodu. 6 do napisania, 4 do pominięcia, 5 pytań.
Mój projekt Pickful ma zdecentralizowany system ławy przysięgłych społeczności, płatności kryptowalutowe x402, Sign-In with Ethereum, multi-database, push w czasie rzeczywistym — wszystkie stacki, które pojawiły się w ciągu ostatnich dwóch lat. Claude dostarcza te funkcje szybko i czysto.
Ale otwórz CLAUDE.md projektu, a zauważysz: system ławy przysięgłych i x402 — ani razu nie padają.
To nie przeoczenie. Cel CLAUDE.md nigdy nie polegał na „opisywaniu funkcji". Polega na uchwyceniu tego, czego Claude nigdy nie wywnioskuje, czytając kod.
Kto pisze CLAUDE.md po raz pierwszy, często traktuje go jak README — opisując każdą kluczową funkcję:
Taką treść Claude, otwierając topic_review_service.rb / x402.rb / like_points_service.rb, odczyta dokładniej niż to napiszesz. Tysiącsłowny opis logiki biznesowej kosztuje Claude'a kilkaset tokenów odczytania z kodu — bez przesunięcia interpretacyjnego. Kod to fakt. Opisy to informacja z drugiej ręki.
To, na czym Claude naprawdę się potyka, to następujące 6 kategorii.
CLAUDE.md Pickfula ma takie linie:
Propshaft (not Sprockets)
ImportMap (no JavaScript bundler)
Hotwire: Turbo Frames, Turbo Streams, Stimulus
Lexxy gem overrides ActionText:
config.lexxy.override_action_text_defaults = false
Każda linia stawia opór domyślnemu zgadywaniu. Widząc projekt Rails, Claude domyślnie zakłada:
Bez tych linii w CLAUDE.md, poproś Claude'a o dodanie nowej funkcji JS: z dużym prawdopodobieństwem zainstaluje Webpackera, zmodyfikuje package.json, napisze config bundlera — wszystko źle, i błąd cichy (aplikacja działa, ale pipeline zasobów jest zanieczyszczony).
Te linie w CLAUDE.md mówią Claude'owi: nie zgaduj, już zdecydowano.
PostgreSQL with 4 separate databases:
- primary - Main application data
- cache - Solid Cache storage
- queue - Solid Queue jobs
- cable - Action Cable subscriptions
Prosto napisane, ale może oszczędzić całą noc. Domyślna multi-DB w Rails 8 to nowe zachowanie — Claude sam nie sprawdzi, ilu baz używasz. Niewinnie wyglądająca migracja ląduje w niewłaściwej bazie, w dev nie ma błędu (wszystkie cztery to PostgreSQL, schema migruje wszędzie). Ale na produkcji tabela job z Solid Queue wciska się do backupu primary, albo model z primary robi zapytanie do bazy cache — takie bugi wypływają po dniach.
Dwie linie w CLAUDE.md vs. cały dzień debugowania produkcji.
/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
Trasy Claude widzi w routes.rb, ale konwencje długości (4-5 znaków, 3-4 znaki) są zakopane w logice generowania slugów w modelach lub serwisach. Poproś Claude'a o nowy typ short linka — prawdopodobnie wygeneruje 6-znakowy slug, w stylu UUID albo czysto cyfrowy — niezgodny z językiem wizualnym całego systemu.
Cecha tych „konwencji": łamanie ich nie daje błędu, ale następny czytelnik kodu poczuje, że coś nie gra. Trzeba pisać.
VIP status at 400+ points
Posts with 15+ likes are "hot" posts
Obie liczby żyją gdzieś w kodzie (User#vip?, scope Post#hot?). Problem: gdy Claude zmienia coś powiązanego — dostosowanie nagród punktowych, dodanie powiadomienia „prawie VIP", napisanie crona przypinającego hot posty — nie wyrówna automatycznie progów w innych miejscach.
Efekt: nagradzasz zadanie 500 punktami, ale copy mówi „możesz zostać VIP" (400 wystarczy); albo seedujesz dane dla nowej funkcji z małą liczbą polubień, i próg 15 nigdy nie zostaje przekroczony.
Zdolności Claude'a w kodzie są mocne, ale nie ma ogólnosystemowego wyczucia liczb. Umieszczenie kluczowych progów w CLAUDE.md sprawia, że każda rozmowa zaczyna się z wiedzą „400 i 15 to liczby specjalne".
- Devise (authentication) + Pundit (authorization)
- Pundit policies in app/policies/
- Check UserPolicy, PostPolicy, etc. for permission rules
Rolą tej linii jest nawigacja, nie opis.
Bez niej, gdy Claude musi dodać nową kontrolę uprawnień, trzy możliwości:
unless current_user.admin? wprost w kontrolerzeauthorize? w modeluZ „Pundit policies in app/policies/" zapisanym, Claude za każdym razem idzie wprost do app/policies/ dodać plik policy — styl spójny.
Jedna linia eliminuje „pracę detektywistyczną" Claude'a za każdym razem.
Supported locales: en, zh-CN, zh-TW
Testing stack: RSpec + FactoryBot + Capybara + Shoulda Matchers
Dodając nową funkcję, domyślne zachowania Claude'a to:
Ale twój projekt faktycznie potrzebuje:
Łamanie tych „ograniczeń zewnętrznych na poziomie projektu" generuje stos drobnej pracy na potem — tłumaczenia do uzupełnienia, testy do przepisania. Zapis w CLAUDE.md wbija raz na zawsze „rzeczy, które trzeba robić za każdym razem".
Tak samo ważne jak „trzeba pisać" jest „nie pisz". Poniższe kategorie — widzisz = usuwasz:
1. Opisy funkcji
„System ławy przysięgłych: użytkownicy mogą zgłaszać treści łamiące zasady, zgłoszone wchodzi w etap publicznego głosowania, przysięgli wybierani są z..."
→ Claude otworzy topic_review_service.rb i przeczyta dokładniej niż ty napisałeś. Wpychanie tego do każdej nowej rozmowy to czyste marnotrawstwo.
2. To, co natychmiast odczytasz z drzewa katalogów / Gemfile
„app/models/ zawiera modele ActiveRecord", „Używa Rails 8", „BD to PostgreSQL"
→ Claude rzuci okiem na korzeń projektu i Gemfile — wie.
3. Ogólna wiedza programistyczna
„Controllers should be thin, delegate to services", „Unikaj N+1", „Pisz testy dla głównych funkcji"
→ To już jest w danych treningowych Claude'a. Pisz to tylko jeśli twój projekt jest nietypowy — np. „celowo nie używamy warstwy service; logika żyje w kontrolerach".
4. Kontekst bieżącego zadania
„Obecnie refaktorujemy system płatności; fokus na..."
→ To kontekst rozmowy, nie fakt projektu. Wrzucone w CLAUDE.md, zanieczyści wszystkie inne rozmowy.
Połóż dowód „umiem to zrobić" przed kazaniem. Po napisaniu powyższej sekcji przepuściłem własny CLAUDE.md Pickfula — 238 linii — przez 5 pytań. Wynik: około połowa to marnotrawstwo.
Do wycięcia (~120 linii):
Większość bloku dev commands (70 linii → 10): bin/setup / bin/rails db:migrate / bundle exec rspec / bin/rubocop / bin/brakeman to wszystko standardowe komendy Rails, już w treningu Claude'a. Zostawić tylko trzy specyficzne dla projektu: bin/jobs (worker Solid Queue), bin/importmap pin (specyficzne dla ImportMap), bin/kamal deploy.
Lista Core Domain Models (35 linii → skasować całą): wymienianie 20 modeli z rolami to klasyczny przykład „CLAUDE.md w stylu README" — Claude uruchomi ls app/models/ albo przeczyta jeden plik modelu i wie. Pakowanie do każdej rozmowy to czyste marnotrawstwo.
Standardowe pozycje w Tech Stack (28 linii → 8): Rails 8 / Devise / Pundit / Tailwind / pg_search — wszystko natychmiast czytelne z Gemfile. Zostawić tylko kontrintuicyjne: Propshaft / ImportMap / Lexxy / x402-rails / Grover.
Rozproszone ogólniki programistyczne: „Controllers should be thin", „Use app/jobs/ for async processing", co testują request specs vs. model specs — Claude robi to domyślnie. Zbędne palenie tokenów.
Zostaje ~100 linii: konwencje routingu URL, podział 4 DB, progi VIP 400 / Hot 15, override Lexxy, anty-default wybory architektoniczne, drogowskaz Pundit, lista locale, FactoryBot (nie fixtures).
Ale co ważniejsze: których inwariantów jeszcze nie ma?
Podczas audytu zdałem sobie sprawę z kilku rzeczy, które powinienem dodać, a nigdy nie dodałem:
238 linii zgnieść do 100–120, plus 5–10 linii wcześniej pominiętych inwariantów — to bliżej „właściwej gęstości" CLAUDE.md.
To nie tutorial. To audyt mojego własnego projektu — i też napisałem źle. Właściwa forma CLAUDE.md to ciągłe kasowanie i ciągłe dodawanie — każdy projekt, który trochę dojrzewa, powinien mieć coraz krótszy i gęstszy CLAUDE.md.
Za każdym razem, gdy chcę coś dodać do CLAUDE.md, przepuszczam to przez następujący checklist:
Reguły, które przechodzą wszystkie 5, zostają; te, które nie przechodzą jednej, kasujemy albo przepisujemy.
Poprawne użycie CLAUDE.md to nie „przedstawianie projektu", ale kompresja wiedzy ukrytej między tobą a codebase'em, której Claude nigdy nie uzupełni czytaniem kodu — nietypowe wybory, niewidzialne progi, konwencje sprzeczne z defaults, ograniczenia zewnętrzne na poziomie projektu.
Każda dodana linia musi odpowiadać na pytanie: „Czy to coś, czego Claude nie odczyta z kodu?" Nie odczyta — zostawić. Odczyta — usunąć.
CLAUDE.md napisany w ten sposób jest zwykle ponad o połowę krótszy od pierwszej wersji, ale na każdą rozmowę wart znacznie więcej niż tysiące słów opisu funkcji. I jest zawsze „jeszcze nieskończony" — każda wydana nowa funkcja ujawnia kolejny inwariant, który powinien był być w środku, i kolejny akapit, który teraz można wyciąć. Usuwać i dodawać, usuwać i dodawać — to codzienna konserwacja CLAUDE.md.