Cztery prompty, które wpisujesz od nowa, stają się czterema plikami
Rozszerzenia Claude Code to pliki, które repozytorium może zawierać, żebyś nie wpisywał w kółko tego samego prompta: skill, hook, subagent i serwer MCP. Wykonaliśmy cztery powtarzalne zadania na prawdziwym repozytorium, małym API do faktur z plikiem JSON zamiast bazy danych. Każde zadanie dostało najpierw złe rozszerzenie, a zły wybór za każdym razem zawodził na ekranie. Potem każde zadanie stało się plikiem w repozytorium. Ta strona omawia cztery gesty oraz kartę wyboru, która rozstrzyga, jakiego rodzaju użyć.
Dwa wyniki ustawiają całą resztę. Hook często opisuje się jako gwarancję, a regułę w pliku pamięci jako prośbę, a jednak nasz pierwszy hook się uruchomił, wypisał ostrzeżenie i zapis i tak przeszedł. To, co zamienia hook, który się uruchamia, w hook, który blokuje, opisujemy w sekcji o hookach. Pierwszy serwer dokumentacji, który podłączyliśmy, nic nie zmienił, a ten, który odpowiedział, był serwerem dokumentacji samego narzędzia. Przepis na hook, który kopiuje większość ludzi, wymaga też osobnego pliku ze skryptem, a nasz mieści się w całości w pliku ustawień.
Zanim napiszesz którykolwiek z czterech plików, musisz wiedzieć, co twoja sesja już ładuje.
Czym jest ta czwórka i co twoja sesja już ładuje
Rozszerzenie Claude Code to plik, który dodajesz do repozytorium, żeby nie wpisywać w pole promptu tych samych instrukcji. Istnieją cztery rodzaje, a kto je już zna, może przejść do następnej sekcji.
- Skill to runbook zapisany w repozytorium. Claude ładuje jego kroki dopiero wtedy, gdy pojawia się dane zadanie, a ty możesz go uruchomić, wpisując jego nazwę.
- Hook jest jak hook commita w gitcie, ale dla działań samego Claude'a. To skrypt uruchamiany przy zdarzeniu, na przykład przy edycji, niezależnie od tego, co Claude zamierzał.
- Subagent to wykonawca z pustym biurkiem. Dostaje twój brief, sam czyta i odsyła raport zamiast całej sterty plików.
- Serwer MCP to klient API, który Claude może wywołać, po dane spoza twojego dysku, takie jak baza danych albo dokumentacja jakiegoś narzędzia.
Żeby cokolwiek zmierzyć, najpierw potrzebujesz punktu odniesienia. Otwórz świeżą sesję i uruchom polecenie kontekstu. Wypisze tabelę wszystkiego, co jest załadowane przed twoim pierwszym słowem. Na naszym stanowisku suma wyniosła 26.2k tokenów przy oknie kontekstu 1m, bez linii Memory files i bez żadnej linii MCP. Każde zadanie, które od teraz dajesz Claude'owi, albo dodaje linię do tej tabeli, albo zostawia ją w spokoju.
Pierwsze zadanie: checklista release'u staje się skillem
Skill to checklista zapisana w osobnym folderze, którą Claude czyta tylko wtedy, gdy ją wywołasz. Pierwsze zadanie to checklista release'u, którą wciąż wklejasz w pole promptu: typecheck, testy, podbicie wersji, linia w changelogu, commit i tag.
Oczywistym miejscem jest plik pamięci i to zły wybór, bo wszystko z tego pliku ładuje się w każdej turze każdej sesji. Wpisaliśmy tam kroki i ponownie odczytaliśmy tabelę kontekstu. Pojawiła się linia Memory files z 210 tokenami, ładowana niezależnie od tego, czy robisz release. Dokumentacja mówi to wprost: gdy sekcja tego pliku urosła do procedury, jej miejsce jest w skillu.
Dobry wybór to folder w katalogu skilli z krokami w .claude/skills/release/SKILL.md, krótkim opisem na górze i jedną dodatkową linią frontmatteru, disable-model-invocation: true. Ta linia trzyma skill poza listą samego Claude'a, więc tylko ty możesz go uruchomić, a o to chodzi przy czymś, co robi commit i tag. Potem wpisujesz ukośnik i jego nazwę, a checklista rusza.
| Odczyt kontekstu | Suma | Linia Memory files |
|---|---|---|
| Punkt odniesienia, świeża sesja | 26.2k / 1m | brak |
| Checklista w pliku pamięci | 26.4k / 1m | 210 tokenów |
| Checklista jako skill | 26.2k / 1m | brak, skill nie na liście |
Gdy go uruchomiliśmy, Claude wykonał wszystkie sześć kroków w niecałą minutę: typecheck, 34 testy, podbicie wersji z 0.4.0 do 0.4.1, linia w changelogu, commit i tag. Wychwycił też usterkę w naszej własnej checkliście. Polecenie commita w tej postaci pomija pliki nieśledzone, więc Claude najpierw dodał nowy changelog do poczekalni i powiedział o tym. Release jest teraz poleceniem, które wpisujesz, żyje w repozytorium dla całego zespołu i nic nie kosztuje w kontekście, dopóki go nie wpiszesz.
Drugie zadanie: reguła, która musi trzymać, staje się hookiem
Hook to skrypt, który Claude Code uruchamia przed odpaleniem narzędzia, tutaj przed każdą edycją lub zapisem, a to, co skrypt odpowie, decyduje o dalszym biegu. Drugie zadanie to reguła, która nigdy nie może się złamać: w tym repozytorium plik z zapisanymi danymi nigdy nie jest edytowany ręcznie, a każda zmiana idzie przez migrację.
Zapisanie tej reguły wielkimi literami w pliku pamięci niczego nie egzekwuje. Dokumentacja mówi to otwarcie: ten plik to kontekst, nie wymuszona konfiguracja, a instrukcja w rodzaju "never edit this file" to prośba, nie gwarancja, podczas gdy hook blokujący edycję to egzekwowanie.
Oczywisty pierwszy hook to ten, który napisaliśmy. W pliku ustawień obserwuje zapisy do pliku z danymi, wypisuje ostrzeżenie i kończy działanie kodem zero, jak skrypt logujący. Potem poprosiliśmy Claude'a o zmianę kwoty na fakturze bezpośrednio w pliku, bez migracji. Hook się uruchomił i ostrzeżenie się wypisało, ale zapis przeszedł: cena jednostkowa na trzeciej fakturze zmieniła się z 8000 na 12000. Odpowiedź Claude'a ani razu nie wspomniała o hooku, bo hook, który kończy się czysto i nic nie wypisuje, nie podjął żadnej decyzji, a jego ostrzeżenia nigdy nie docierają do Claude'a.
Gwarancją nie jest to, że hook działa, tylko odpowiedź, którą zwraca. Poprawka zachowuje ten sam hook i każe mu zwracać decyzję deny: krótką linię JSON z powodem, który chcesz, żeby Claude przeczytał. Wyjście z kodem dwa blokuje tak samo. Cały hook to jedno polecenie inline w .claude/settings.json, bez pliku ze skryptem:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -c 'if (.tool_input.file_path // \"\") | test(\"data/.*\\\\.json$\") then {hookSpecificOutput: {hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: \"Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions).\"}} else empty end'"
}
]
}
]
}
}
Przy tej samej prośbie edycja została odrzucona, a plik z danymi pozostał nietknięty. Claude zobaczył ten błąd: PreToolUse:Edit hook error: Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions). Powiedział, że niczego nie zmienił, i zapytał, czy obejść zabezpieczenie, czy napisać migrację.
Ograniczenie, które Claude nazwał sam: ten hook pilnuje tylko narzędzi edycji i zapisu, a w późniejszym uruchomieniu Claude zaproponował wprowadzenie zmiany przez powłokę. Hook musi pokrywać także powłokę albo powłoka musi zostać poza twoją allow listą, bo zabezpieczenie obejmujące tylko jedne drzwi nadal jest prośbą. Hook nic nie kosztuje w kontekście, ponieważ działa poza twoją rozmową.
Trzecie zadanie: audyt, który zalewa, staje się subagentem
Subagent to osobna sesja Claude'a z własnym pustym kontekstem. Dostaje twój brief, czyta to, czego potrzebuje, i wraca tylko podsumowanie. Trzecie zadanie to audyt, który zalewa twój wątek: sprawdź każdy plik w folderze ze źródłami pod kątem konwencji z README i wypisz każde naruszenie z plikiem i linią.
Poproszony o to inline, Claude wczytuje każdy plik do twojej rozmowy, a cała ta zawartość zostaje tam do końca sesji. W naszym uruchomieniu Claude przeczytał wszystkie 19 plików źródłowych, linia Messages w tabeli kontekstu spuchła, a wyniki utonęły pod stertą.
Dobry wybór to plik w .claude/agents/, tutaj .claude/agents/conventions-auditor.md, z nazwą, opisem tego, kiedy go użyć, narzędziami, których może używać, i przypiętym modelem. Przypięcie modelu ma znaczenie, bo inaczej subagent dziedziczy to, na czym działa twoja sesja, a większość zadań czytania nie potrzebuje największego modelu.
Zadaliśmy dokładnie to samo pytanie jeszcze raz, a Claude przeczytał pliki sam. W obu próbach nigdy nie wywołał agenta, choć był na liście: 0 z 2 delegowań. Zadziałało nazwanie go na początku prompta: "use the conventions auditor subagent". Wyszło jedno wywołanie i wróciło krótkie podsumowanie.
| Uruchomienie | Kontekst łącznie | Linia Messages |
|---|---|---|
| Audyt inline | 46.1k | 19.3k |
| Nazwany subagent | 34.1k | 7.2k |
Wyniki były równie dobre: subagent wychwycił błąd ścieżki, który uruchomienie inline przeoczyło, i przeoczył jeden, który znalazło uruchomienie inline. Więc plik sprawia, że subagent istnieje, a twój prompt sprawia, że Claude go używa; dopóki Claude nie wybierze go sam, nazywasz go ty. Jedno ograniczenie: rachunek się nie zmienił. Oba uruchomienia kosztowały tyle samo, bo czytanie i tak się odbywa, tylko nie w twoim oknie.
Czwarte zadanie: dane poza zasięgiem Claude'a stają się serwerem MCP
Serwer MCP to klient API, który Claude może wywołać, a zadanie, do którego istnieje, to dane nowsze niż model albo dane leżące poza twoim dyskiem. Odruchowy wybór jest tu zły, więc zrobiliśmy go jako pierwszy.
Podłączyliśmy publiczny serwer dokumentacji dla naszej biblioteki testowej i poprosiliśmy Claude'a o test z fałszywymi timerami. Nic się nie zmieniło: Claude znał tę bibliotekę już wcześniej i wywoływał serwer tylko wtedy, gdy go nazwaliśmy. Dokumentacja, którą model już zna, to nie są dane, do których nie ma dostępu.
Prawdziwy test to pytanie o samego Claude Code, o ustawienie hooków nowsze niż model: które ustawienie sprawia, że nieudany hook blokuje, w której wersji je dodano i jaki jest dokładny JSON. Bez serwera Claude przeczytał schemat ustawień własnej instalacji, niczego nie znalazł i odpowiedział, że nie może znaleźć tego ustawienia i nie zgadnie go. To uczciwa odpowiedź i bezużyteczna, bo ustawienie istnieje; pojawiło się po tym, jak model skończył się uczyć.
Rozwiązaniem jest dodanie do repozytorium serwera dokumentacji samego Claude Code, z zakresem projektu, tak żeby konfiguracja trafiła do pliku w głównym katalogu repozytorium, który dostaje cały twój zespół:
claude mcp add --transport http claude-docs --scope project https://code.claude.com/docs/mcp
To zapisuje .mcp.json. Claude Code wyświetla serwer jako oczekujący, dopóki nie zatwierdzisz go w następnej sesji, a od tej chwili jego narzędzia są dostępne dla Claude'a. Zadaliśmy to samo pytanie zwykłymi słowami, bez nazywania serwera. Claude przeszukał dokumentację, znalazł stronę i odpowiedział ustawieniem, wersją i JSON-em do dodania, z linkiem.
Ustawienie to "onFailure": "block", które sprawia, że nieudany hook blokuje działanie zamiast je przepuszczać. Pojawiło się w v2.1.295, podczas gdy nasza instalacja miała jeszcze 2.1.294. To jest ograniczenie: Claude nigdy nie powiedział, że nasza instalacja nie może tego użyć, bo nie sprawdził wersji, więc wersję sprawdzasz sam. W tabeli kontekstu serwer pojawia się tylko jako nazwy swoich narzędzi, ładowane na żądanie, dopóki Claude nie sięgnie po któreś z nich.
Cztery pliki, jedna karta wyboru
Po czterech zadaniach git status pokazuje dokładnie cztery pliki, bo hook nie potrzebuje własnego skryptu:
.claude/skills/release/SKILL.md.claude/settings.json.claude/agents/conventions-auditor.md.mcp.json
Zrób commit, a następna sesja w tym repozytorium będzie miała je wszystkie, tak samo jak każdy, kto je sklonuje.
Powód, dla którego każdy wybór był trafny, to tabela, którą dostarcza dokumentacja i którą możesz zachować jako kartę decyzyjną:
| Sytuacja | Rozszerzenie |
|---|---|
| Procedura, którą wklejasz kolejny raz | Skill |
| Coś, co ma się dziać za każdym razem, bez pytania | Hook |
| Zadanie poboczne, które zalewa rozmowę wyjściem, którego nie przeczytasz ponownie | Subagent |
| Dane, które wciąż kopiujesz z miejsca, którego Claude nie widzi | Serwer MCP |
Rodzaje można też łączyć. Dokumentacja łączy skill z serwerem MCP: serwer daje połączenie, a skill uczy Claude'a, jak je dobrze wykorzystać. Skill może też działać wewnątrz rozwidlonego subagenta, dzięki czemu jego czytanie zostaje poza twoim oknem. A gdy inne repozytorium potrzebuje tej samej konfiguracji, plugin jest pudełkiem, które wysyła je wszystkie razem.
Karta rozstrzyga o rodzaju, nie o jakości. Hook, który tylko loguje, subagent, którego nigdy nie nazywasz, i serwer dla dokumentacji, którą model już zna, każdy stał w dobrym rodzaju i mimo to zawiódł. Wybierz rodzaj z karty, a potem spraw, żeby plik odpowiadał, a nie tylko istniał.
Werdykt: kiedy który, kiedy żaden, jedna zasada
Wszystkie cztery zadania, które wpisujesz od nowa, najpierw wypróbowano w zły sposób, zawiodły na ekranie i są teraz plikami w repozytorium, które działają. Release to polecenie, które wpisujesz. Reguła dotycząca danych to hook, który odrzuca edycję i każe Claude'owi zapytać. Audyt wraca jako podsumowanie. Pytanie o dokumentację dostaje ustawienie i wersję, których Claude nie umiał znaleźć sam.
Hook, od którego zaczęliśmy, ten, który się uruchomił i przepuścił zapis, nauczył zasady leżącej pod tym wszystkim: hook decyduje tylko przez to, co zwraca jego polecenie, więc hook, który tylko loguje, to prośba w przebraniu.
Niektóre przypadki nie wymagają żadnego z nich:
- Konwencja, którą Claude wciąż myli, nadal należy do pliku pamięci, jako jedna linia, a nie procedura.
- Zadanie, którym musisz sterować tam i z powrotem, mając przed oczami swoją historię, zostaje w głównym wątku, a nie w subagencie.
- Serwer dokumentacji dla biblioteki, którą model już zna, kosztuje kontekst i niczego nie zmienia. Podłączaj to, czego model mieć nie może, a nie to, co już ma.
Zasada do zapamiętania: daj każdemu zadaniu, które wpisujesz od nowa, własny plik, wybierz rodzaj z karty i pamiętaj, że spośród nich tylko hook nigdy nie pyta.
AIDive