CLAUDE.md - kontrakt z AI, który koduje jak nadgorliwy stażysta
Andrej Karpathy nazwał problem po imieniu. Społeczność odpowiedziała plikiem CLAUDE.md, który zmienia model z chaotycznego generatora w zdyscyplinowanego asystenta.
Diagnoza Karpathy’ego #
Andrej Karpathy - były szef AI w Tesli, współzałożyciel OpenAI - w cyklu publicznych analiz dotyczących pracy z modelami językowymi w roli asystentów do kodowania, podsumował główny problem jednym zdaniem: AI koduje jak bardzo pewny siebie stażysta po tygodniu pracy.
Każdy, kto pracował z młodszym programistą, wie, jak to wygląda. Dostaje zadanie. Zamiast zapytać o niejasności - zgaduje. Zamiast dodać jedną linijkę, której wymaga ticket - refaktoruje pół pliku, „bo akurat zauważył”. Zamiast napisać prostą funkcję - tworzy hierarchię klas i interfejsów „na zapas, żeby było rozszerzalne”. Robi to z dobrymi intencjami i bywa, że trafia. Ale w produkcyjnym kodzie kosztuje to czas - w code review, w testach, w późniejszym debugowaniu, kiedy ktoś trzeci próbuje zrozumieć, dlaczego prosta zmiana ma 400 linijek diffa.
Modele językowe robią dokładnie to samo, tylko z prędkością paru tysięcy linii na minutę. Bez disciplinarnej rozmowy z teamleadem.
Odpowiedź: CLAUDE.md jako kontrakt #
Społeczność programistów pracujących intensywnie z Claude’em (i pochodnych - Cursor ma .cursorrules, Continue ma swój config.json) wypracowała wzorzec: plik z zasadami w głównym katalogu projektu. Najczęściej nazywany jest CLAUDE.md (dla Claude Code) i jest automatycznie zaciągany jako kontekst przed każdą interakcją.
To nie jest lista życzeń ani sugestii. To twardy kontrakt inżynieryjny - opisany prozą, bo model rozumie prozę, ale traktowany jak każde inne ograniczenie projektu, równie wiążące jak .editorconfig czy tsconfig.json.
Zasady, które najlepiej działają, są inspirowane diagnozą Karpathy’ego. Cztery filary, które powtarzają się w większości publicznie udostępnionych szablonów:
Filar 1: Think before coding #
Modele mają tendencję do cichego wybierania jednej z możliwych ścieżek, kiedy prompt jest niejednoznaczny. Często wybierają tę, która wymaga mniej pisania, albo tę, która brzmi bardziej generycznie.
Zasada wymusza odwrotne zachowanie:
- Zakaz zgadywania intencji, kiedy są dwie sensowne interpretacje.
- Obowiązek głośnego nazywania wątpliwości.
- Zatrzymanie pracy i zapytanie, jeśli odpowiedź zmienia kierunek implementacji.
W praktyce: zamiast „dodałem walidację null-a, zgodnie z best practice”, model ma napisać „funkcja może dostać null jako argument - twoja prośba tego nie precyzuje. Mam (a) odrzucać i rzucać błąd, (b) traktować jako brak wartości i zwracać domyślną, czy (c) coś innego?”.
Filar 2: Simplicity first #
Motto, które warto sobie wytatuować: minimum kodu, które rozwiązuje problem.
- Zakaz dodawania funkcji, o które programista nie prosił.
- Zakaz tworzenia interfejsów i abstrakcyjnych klas dla kodu, który ma jedną implementację.
- Zakaz „przygotowywania na przyszłość” - jeśli rozszerzalność nie jest wymagana dziś, jej nie ma.
Test ostateczny: jeśli ten sam efekt da się osiągnąć w 50 linijkach, a model wygenerował 200, to nie jest „dokładniejsze”. To jest błąd, który należy naprawić.
To bezpośrednio łączy się z YAGNI (You Aren’t Gonna Need It) - regułą znaną z Extreme Programming, która przez dwie dekady tłumaczyła to samo programistom-ludziom.
Filar 3: Surgical changes #
Najważniejszy filar dla starszych projektów. Model ma zakaz dotykania kodu, który nie jest bezpośrednio związany z zadaniem, nawet jeśli z perspektywy „czystego kodu” dałoby się go napisać ładniej.
- Bez „przy okazji poprawiłem nazwę zmiennej w sąsiedniej funkcji”.
- Bez „dodałem brakujące typy, bo akurat zauważyłem”.
- Bez reformatowania, jeśli to nie wynika z prompta.
- Każda zmieniona linia musi mieć uzasadnienie wynikające bezpośrednio z zadania.
Dodatkowo: model dopasowuje się do zastanego stylu. Jeśli projekt używa var-ów zamiast jawnych typów, model nie wprowadza jawnych typów. Jeśli klasy są w jednym pliku, model nie rozbija ich na wiele. Spójność stylu w projekcie ma większą wartość niż abstrakcyjna „elegancja”.
Filar 4: Goal-driven execution #
Zamiast pisać modelowi, jak krok po kroku ma zmienić kod, definiujesz kryteria sukcesu i pozwalasz mu zoptymalizować pod nie.
Standardowy flow w CLAUDE.md to TDD-loop:
- Napisz test (lub zdefiniuj kryterium akceptacji w prompcie).
- Uruchom - test ma się wywalić.
- Napisz kod.
- Uruchom - test ma przejść.
- Jeśli nie przeszedł - wróć do kroku 3.
Model działa w pętli aż do spełnienia kryterium, nie poprzestając na „wygląda dobrze”. Tu zresztą Claude Code mocno błyszczy, bo ma narzędzia do uruchamiania bashu, więc cały loop wykonuje sam, bez „a sprawdź to u siebie”.
Co to daje w praktyce #
Bez CLAUDE.md interakcja z modelem często wygląda tak: prosisz o dodanie pola do API, dostajesz refaktor pięciu plików plus nowy interfejs IFieldStrategy - i godzinę spędzasz na cofaniu rzeczy, których nie chciałeś. Po którymś takim epizodzie programiści zaczynają ręcznie powtarzać te same wytyczne w każdej sesji: „nie dotykaj reszty pliku”, „nie twórz abstrakcji”, „zatrzymaj się i zapytaj, jeśli coś nie jest jasne”.
CLAUDE.md to po prostu przeniesienie tych wytycznych z prompta do persystentnego kontekstu. Piszesz raz, działa zawsze, model nie musi tego za każdym razem zgadywać.
Skrótowo #
CLAUDE.md to nie lista życzeń, tylko twarda umowa. Cztery filary - „pytaj, nie zgaduj”, „minimum kodu”, „chirurgiczne zmiany”, „pisz pod kryterium” - rozwiązują 80% bólu pracy z AI w istniejących projektach. Karpathy zdiagnozował problem, społeczność spakowała rozwiązanie do jednego pliku tekstowego. Nie magia, nie nowa wersja modelu - po prostu lepiej napisany kontrakt.
Persystentny kontekst zamiast powtarzania promptów #
Modele językowe nie utrzymują pamięci między sesjami. Cały kontekst musi być załadowany do okna kontekstowego przed każdą inferencją. Standardowy mechanizm „instrukcji systemowych” w API to jedno - ale dla pracy w konkretnym repozytorium potrzebujemy czegoś bardziej wbudowanego: plik w repozytorium, który narzędzie automatycznie czyta.
Stąd konwencja:
- Claude Code czyta
CLAUDE.mdz root projektu (i nadrzędnych katalogów - rekursywnie). - Cursor czyta
.cursorruleslub.cursor/rules/*.mdc. - Continue używa
config.jsonz polemsystemMessagelub plików rules. - Aider czyta
.aiderules. - OpenAI Codex CLI: pliki w
~/.codex/plusAGENTS.mdw repo.
Wszystkie sprowadzają się do tej samej idei: plik w repo definiuje, jak narzędzie ma się zachowywać. Reszta różnic to dialekt.
Struktura skutecznego CLAUDE.md #
Dobry plik CLAUDE.md ma kilka sekcji, w mniej-więcej tej kolejności:
- Project context - co to za projekt, jakie warstwy, co jest w fazie eksperymentalnej.
- Tech stack - języki, frameworki, version constraints.
- Code style - importy, naming, konwencje.
- Build & test commands - jak uruchomić linter, testy, dev server.
- Hard rules - cztery filary Karpathy’ego, sformułowane jednoznacznie.
- Don’t do this - explicit lista anty-wzorców z dotychczasowych incydentów.
4 filary - sformułowane technicznie #
Think before coding #
Model ma stop condition na ambiguity. Definicja niejednoznaczności:
- Prompt nie precyzuje zachowania w jednym z poniższych przypadków:
nullargument, pusta kolekcja, błąd I/O, brak uprawnień, race condition. - Istnieje więcej niż jedna rozsądna implementacja, których behavioralne kontrakty się różnią.
- Wymagana zmiana wpływa na publiczne API (breaking change vs. additive).
Zachowanie: model formułuje pytanie z konkretnymi opcjami i zatrzymuje się przed pisaniem kodu.
Simplicity first #
Operacjonalizacja:
- No premature abstraction. Interfejs/klasa abstrakcyjna wymaga ≥ 2 rzeczywistych implementacji w aktualnym kodzie.
- No anticipation. Parametry, flagi, opcje konfiguracyjne tylko jeśli są wykorzystywane przez aktualne wywołanie.
- Line budget. Jeśli rozwiązanie alternatywne istnieje w < 50% linii i nie traci ważnej własności (testowalność, wydajność, czytelność) - jest preferowane.
To literalnie YAGNI z Extreme Programming, ale wymuszone na poziomie generation-time.
Surgical changes #
Diff hygiene:
- Każda zmieniona linia musi być uzasadniona w prompcie.
- No drive-by edits. Nie poprawiamy nazw, nie reorganizujemy importów, nie dodajemy brakujących typów - chyba że to wprost żądanie.
- No reformat. Jeśli plik ma niespójne wcięcia lub stary
var x = ..., zostawiamy. - Match existing style. Jeśli kod używa
function expressionzamiastarrow, model używa tego samego.
To pierwszy filar, na którym klasyczne LLM-y odpadają najbardziej, bo są wytrenowane na „czystym” kodzie z OSS-a i mają silną tendencję do unifikacji stylu.
Goal-driven execution #
Zamiast „write a function that does X”, piszesz:
Acceptance criteria (test-driven):
1. Test in tests/payment_test.cs::Should_RejectExpiredCard must pass.
2. Test ::Should_AcceptValidCard must pass.
3. dotnet build must succeed with zero warnings (TreatWarningsAsErrors=true).
4. dotnet format --verify-no-changes must succeed.
Run all four checks after each iteration. Continue until all pass.
Model wykonuje pętlę autonomicznie. Claude Code (jak również Cursor’s agent mode i Aider) ma do tego natywne wsparcie - może odpalić bash, sprawdzić exit code, przeczytać stderr.
Gotowy szablon #
<!-- title: CLAUDE.md -->
# Project: AcmePayments
## Stack
- .NET 9, C# 13, ASP.NET Core minimal API
- PostgreSQL 16 via Npgsql
- xUnit + FluentAssertions for tests
- Build tools: dotnet, just (Justfile)
## Commands
- Build: `just build` (= `dotnet build -c Release -warnaserror`)
- Test: `just test` (= `dotnet test --no-restore`)
- Lint: `just lint` (= `dotnet format --verify-no-changes`)
- Run: `just run`
- Migration: `just migrate <name>`
## Hard rules
### 1. Think before coding
Before writing any code, identify any ambiguity in the request. If multiple
reasonable interpretations exist (null handling, error semantics, edge cases),
STOP and ask. Do not silently pick one.
### 2. Simplicity first
- No premature abstractions. Interfaces require ≥ 2 implementations in this repo.
- No "future-proofing". If a feature isn't requested, it doesn't exist.
- If a 50-line solution exists, the 200-line one is wrong.
### 3. Surgical changes
- Change ONLY what the request requires.
- Do not reformat, rename, or "improve" adjacent code.
- Match existing style, even if it's not your preference.
- Each diff line must be justified by the prompt.
### 4. Goal-driven execution
- Run `just test` after each meaningful change.
- A task is not done until ALL of these pass:
- `just build`
- `just test`
- `just lint`
- If anything fails, fix the root cause. Do not change the test to match the code.
## Don't do this
- Don't add `try/catch` that swallows exceptions silently.
- Don't add logging unless the prompt asks for it.
- Don't introduce new NuGet packages without confirmation.
- Don't write commit messages on my behalf - I'll write them.
- Don't push to remote.
## Project structure
- `src/Api/` - HTTP layer (controllers, middleware)
- `src/Domain/` - business logic, no framework dependencies
- `src/Infrastructure/` - DB, external APIs
- `tests/` - mirrors src structure
## Notes for the agent
- "Production" = `main` branch + `release/*` tags. Treat as immutable.
- We don't use feature flags - if it's merged, it's live.
- Database migrations are forward-only. No `down` script.
Hierarchia plików #
Claude Code (i większość agentów) ładuje pliki rekursywnie z góry:
~/.claude/CLAUDE.md- globalne preferencje użytkownika.<repo-root>/CLAUDE.md- reguły projektu.<repo-root>/<subfolder>/CLAUDE.md- reguły specyficzne dla podsystemu.
Konflikt rozstrzyga najgłębszy plik. Pozwala to mieć ogólne reguły w root, a w katalogu z eksperymentalnym kodem złagodzić niektóre (np. „w tym katalogu możesz dodawać abstrakcje swobodnie - to playground”).
Anty-wzorce #
- Plik z 800 linii - model dostaje go w kontekście za każdym razem. Każda linia kosztuje tokeny i rozcieńcza istotne reguły. Trzymaj < 200 linii.
- Wielowariantowe „it depends” - model nie umie miękko zinterpretować „raczej preferuj X, ale czasem Y”. Jeśli reguła nie jest deterministyczna, lepiej jej w ogóle nie pisać.
- Sprzeczne reguły - model wybiera tę, która brzmi pewniej, niekoniecznie tę, którą miałeś na myśli. Czytaj plik po napisaniu pod kątem konfliktów.
- Dezaktualizacja - reguła „nie używamy biblioteki X” po roku jest tym, co model rozumie jako prawdę, mimo że X jest już głównym frameworkiem projektu. Dorzuć
CLAUDE.mddo code review.
Integracja z Claude Code #
Claude Code szuka CLAUDE.md automatycznie:
# title: terminal
cd ~/projects/acme-payments
claude # CLI sam załaduje ./CLAUDE.md i ~/.claude/CLAUDE.md
Można też wymusić konkretny kontekst flagą --context (przy debugowaniu reguł).
Plik nie wymaga restartu - przy każdej nowej sesji jest przeładowywany. Edycja CLAUDE.md w trakcie pracy z agentem ma efekt przy następnym promptcie.
Skrótowo #
CLAUDE.md to twardy kontekst, ładowany automatycznie przy każdej sesji. Cztery filary Karpathy’ego (think-before-coding, simplicity-first, surgical-changes, goal-driven-execution), spakowane do struktury stack → commands → hard rules → don’t-do. Trzymaj < 200 linii, unikaj „it depends”, aktualizuj jak code style guide. To narzędzie disciplinarne, nie kosmetyczne.
Podobne wpisy
Claude Code - agent w terminalu, nie autocomplete w IDE
Inna filozofia niż Copilot i Cursor. Pełnoprawny agent w CLI, który czyta, pisze, uruchamia testy i sam dochodzi do zielonych checków.
LINQ w C# - lambdy, Where, Select i leniwa ewaluacja
Wyrażenia lambda, operator zapytań LINQ, łączenie metod w łańcuchy oraz pułapka leniwej ewaluacji - jak filtrować, sortować i przekształcać kolekcje jednym, czytelnym łańcuchem wywołań. Czwarta część kursu C#.
Generyki i kolekcje w C# - List, Dictionary, ograniczenia typów
Dlaczego powstały generyki, jak działają List i Dictionary od podszewki oraz jak pisać własne bezpieczne kontenery z ograniczeniami where. Trzecia część kursu C#.