Przejdź do głównej zawartości

Zarządzanie snippetami w Cursorze dzieli boilerplate na trzy warstwy: pliki snippetów VS Code wstawiają stały szkielet natychmiast i powtarzalnie, wąska reguła projektu niesie konwencje obowiązujące agenta, a prompt z plikiem wzorcowym uzupełnia części zależne od projektu. Cursor Tab kończy pisaną linię, ale nie jest zarządzaną warstwą. CI pilnuje, by wszystkie trzy się kompilowały i były zgodne.

Czwarty raz w tym tygodniu stawiasz komponent pobierający dane. Snippet skopiowany od kolegi wciąż importuje stary hook do pobierania danych, agent wymyśla trzeci sposób renderowania stanu błędu, a recenzent poświęca cały przegląd pull requesta boilerplate’owi zamiast zachowaniu kodu. Nikt celowo nie zmienił konwencji; snippet, reguła i kod rozjechały się edycja po edycji.

Ta strona jest dla developerów, którzy codziennie piszą w Cursorze te same wzorce kodu, oraz dla tech leadów, którzy odpowiadają za wspólne snippety i reguły zespołu. Strona zakłada, że używasz już reguł projektu; jeśli nie, zacznij od reguł projektu w Cursorze.

  • Tabelę decyzyjną: która warstwa powinna generować dany fragment boilerplate’u.
  • Zacommitowany plik .vscode/*.code-snippets z konwencjami, dzięki którym prefiksy łatwo znaleźć.
  • Regułę o wąskim zakresie i cztery prompty do skopiowania, które zamieniają szkielet ze snippetu w gotowy kod zgodny z konwencjami.
  • Dwie kontrole w CI: pierwsza dowodzi, że każdy snippet rozwija się do kodu przechodzącego sprawdzanie typów, druga wyłapuje reguły wskazujące na usunięte pliki.
  • Typowe awarie biblioteki snippetów i sposób wyjścia z każdej z nich.

Każda warstwa wymienia szybkość i powtarzalność na znajomość twojego kodu. Umieszczaj każdy fragment boilerplate’u w najtańszej warstwie, która wygeneruje go poprawnie.

WarstwaGdzie jestSzybkośćZawsze ten sam wynik?Zna twój kod?Do czego
Snippet VS Code.vscode/*.code-snippetsNatychmiast, bez wywołania modeluTakNie, zna tylko nazwę pliku i twoje tab stopyStały szkielet: interfejs propsów, eksport, szkielet testu, cztery stany renderowania
Reguła projektu.cursor/rules/*.mdcDziała przy każdym pasującym żądaniu do agentaNiePrzez pliki, które wskazujeKonwencje, których agent musi przestrzegać przy każdej zmianie tych plików
Prompt z plikiem wzorcowym lub Agent SkillCzat, zapisana komenda albo skillJedno uruchomienie agentaNieTak, czyta plik wzorcowySzkielety wieloplikowe i treść zależna od prawdziwych typów i hooków
Cursor Tab (nie jest warstwą, którą zarządzasz)WbudowanyW trakcie pisaniaNieOstatnie edycje i kod w pobliżuDokończenie linii, którą już piszesz; zobacz autouzupełnianie Tab

Z tabeli wynikają dwie praktyczne zasady. Jeśli wynik nigdy nie powinien się zmieniać, to snippet, a nie prompt. Jeśli wynik musi pasować do pliku, który zmienia się w czasie, to reguła albo prompt wskazujący ten plik, a nie snippet, który go kopiuje.

Cursor jest zbudowany na VS Code, więc czyta te same pliki snippetów. Poniższa składnia pochodzi z dokumentacji snippetów VS Code (microsoft/vscode-docs, sprawdzone 2026-09-26). Po utworzeniu pliku sprawdź, czy Cursor go widzi, poleceniem Insert Snippet z palety poleceń.

  1. Uruchom Snippets: Configure Snippets z palety poleceń i wybierz New Snippets file for ‘<folder>’. VS Code tworzy plik .code-snippets w katalogu .vscode projektu; commitujesz go, żeby cały zespół miał te same snippety.

  2. Dodawaj snippety z polami scope i description oraz prefiksem w formie kategoria-nazwa. Bez scope snippet projektu pojawia się w każdym języku. Prefiks kategorii pozwala wpisać comp- i zawęzić listę do komponentów. Opcjonalne właściwości include i exclude przyjmują wzorce glob i ograniczają snippet do pasujących plików, więc poniższy comp-query pojawia się tylko w src/components, czyli w tych samych plikach, które obejmują globs reguły. Wzorce ze ścieżką są dopasowywane do pełnej ścieżki pliku, stąd początkowe **/. To świeża funkcja VS Code, więc sprawdź, czy Cursor ją obsługuje: snippet nie powinien się pojawić w pliku .tsx poza src/components.

    {
    "Component with query states": {
    "scope": "typescriptreact",
    "prefix": "comp-query",
    "include": ["**/src/components/**/*.tsx"],
    "description": "Component that renders loading, error, empty and data states",
    "body": [
    "import { EmptyState, ErrorState, Skeleton } from '@/components/ui/states';",
    "import { ${2:useNotifications} } from '@/hooks/queries';",
    "",
    "interface ${TM_FILENAME_BASE}Props {",
    " ${1:userId}: string;",
    "}",
    "",
    "export function ${TM_FILENAME_BASE}({ ${1:userId} }: ${TM_FILENAME_BASE}Props) {",
    " const query = ${2:useNotifications}(${1:userId});",
    "",
    " if (query.isPending) return <Skeleton />;",
    " if (query.isError) return <ErrorState onRetry={query.refetch} />;",
    " if (query.data.length === 0) return <EmptyState>${3:Nothing here yet}</EmptyState>;",
    " return ${4:null};$0",
    "}"
    ]
    },
    "Vitest file": {
    "scope": "typescript,typescriptreact",
    "prefix": "test-file",
    "description": "Vitest file for the module next to it",
    "isFileTemplate": true,
    "body": [
    "import { describe, expect, it } from 'vitest';",
    "",
    "describe('${1:${TM_FILENAME_BASE}}', () => {",
    " it('${2|returns,renders,throws|} ${3:the expected result}', () => {",
    " ${4:expect(true).toBe(false);}$0",
    " });",
    "});"
    ]
    }
    }

    ${TM_FILENAME_BASE} wstawia nazwę pliku bez rozszerzenia, więc nazwanie pliku InvoiceList.tsx nadaje nazwę komponentowi. ${2|returns,renders,throws|} daje wybór z listy. $0 to miejsce, w którym kończy kursor. Snippet z isFileTemplate pojawia się w poleceniu Snippets: Fill File with Snippet, które wypełnia nowy, pusty plik. Domyślna asercja w snippecie testu celowo nie przechodzi, więc nieuzupełniony szkielet sprawia, że zestaw testów świeci na czerwono, zamiast po cichu przechodzić. Każdemu tab stopowi daj wartość domyślną, która istnieje w twoim kodzie, na przykład prawdziwą nazwę hooka: kontrola w CI opisana niżej rozwija każdy snippet z wartościami domyślnymi i sprawdza typy wyniku.

  3. Trzymaj plik mały. Snippety mają sens tylko dla kształtów, które piszesz codziennie; wszystko inne należy do reguły albo promptu. Usuń snippet, gdy nic napisanego w ostatnim kwartale nie pasuje do jego kształtu.

Plik pisany ręcznie z pamięci daje te same generyczne szablony, od których próbujesz uciec. Niech agent przeczyta twój kod, a wynik sprawdź tak jak każdą zmianę konfiguracji. Najpierw dodaj dwa pliki z sekcji o weryfikacji poniżej (scripts/check-snippets.mjs i tsconfig.snippets.json) albo usuń ostatni akapit promptu.

Ostatnia linia jest ważna: wskazanie plików źródłowych pozwala porównać każdy snippet z dwoma prawdziwymi plikami, z których powstał, zamiast czytać cały JSON.

Trzymaj konwencje w regule o wąskim zakresie, nie w snippecie

Dział zatytułowany „Trzymaj konwencje w regule o wąskim zakresie, nie w snippecie”

Snippet nie wie, że zespół przeszedł z jednego hooka do pobierania danych na inny. Reguła o tym samym zakresie plików wie, bo wskazuje wzorcowy przykład, który agent czyta za każdym razem. Utwórz .cursor/rules/components.mdc. Poniższe klucze frontmattera (description, globs, alwaysApply) to format opisany na stronie reguły projektu w Cursorze, a dokumentacja reguł Cursora (sprawdzone 2026-10-12) wymienia te same trzy klucze. Pomoc Cursora podaje, że reguły działają tylko w Agencie (Chat), a nie w autouzupełnianiu Tab, edycji inline ani w przeglądach PR przez Bugbota (sprawdzone 2026-10-12), więc prompt kończący komponent, podany niżej, dołącza plik reguły po nazwie.

---
description: Conventions for React components in src/components
globs: src/components/**/*.tsx
alwaysApply: false
---
- New components start from the comp-query snippet in .vscode/project.code-snippets.
- Data comes from the hooks in src/hooks/queries.ts. Never call fetch in a component.
- Render loading, error and empty states with Skeleton, ErrorState and EmptyState from
src/components/ui/states.tsx.
- The reference implementation is src/components/NotificationList.tsx. Match it.
- Every component gets a test file created from the test-file snippet.

Reguła i snippet wskazują teraz na siebie nawzajem. Gdy konwencja się zmienia, zmieniasz snippet, regułę i plik wzorcowy w jednym pull requeście, a opisana niżej kontrola kotwic nie przejdzie, jeśli plik wskazany w regule został przeniesiony. Typy reguł i format pliku opisuje strona reguły projektu w Cursorze; zespołową bibliotekę reguł znajdziesz w niestandardowych regułach i szablonach.

Snippet daje plik, który ma już właściwy szkielet. Zadaniem agenta jest część zależna od twojego kodu: prawdziwy hook, prawdziwe typy i prawdziwe znaczniki JSX.

  1. Utwórz src/components/InvoiceList.tsx, wpisz comp-query i zaakceptuj snippet. Przejdź klawiszem Tab przez tab stopy: nazwę propa, nazwę hooka (domyślny useNotifications należy do komponentu wzorcowego, więc zamień go na useInvoices), tekst pustego stanu i końcową wartość zwracaną.

  2. Z kursorem w miejscu $0 otwórz edycję inline (skrót znajdziesz w skrótach klawiszowych Cursora, a cały sposób pracy opisuje strona edycja inline) albo zaznacz plik i wyślij go do agenta. Wklej poniższy prompt.

  3. Oceń wynik po bramkach, a nie linia po linii: sprawdzanie typów, lint i test komponentu.

Do szkieletu obejmującego kilka plików, na przykład trasy, jej schematu, serwisu i testów, snippet na każdy plik to złe narzędzie. Daj agentowi jeden prompt z plikami wzorcowymi i zapisz go tam, gdzie zespół może go użyć ponownie, jak opisuje strona szablony promptów dla agenta Cursora.

Jeśli ten sam prompt uruchamiasz co tydzień, zapakuj go jako Agent Skill, który Cursor opisuje jako „a portable, version-controlled package that teaches agents how to perform domain-specific tasks” (cursor.com/docs/skills, sprawdzone 2026-08-28). Skill podróżuje wtedy razem z repozytorium, tak jak plik snippetów.

Jak zweryfikować boilerplate ze snippetów bez czytania każdej linii?

Dział zatytułowany „Jak zweryfikować boilerplate ze snippetów bez czytania każdej linii?”

Boilerplate to miejsce, w które nikt nie patrzy, i dlatego zepsuty boilerplate się rozprzestrzenia. Dodaj do CI dwie kontrole, żeby zły snippet albo nieaktualna reguła wywracały build, zanim trafią do kolejnych dziesięciu plików.

Kontrola 1: każdy snippet się parsuje, ma unikalny prefiks i rozwija się do kodu, który się kompiluje. Zapisz ten skrypt jako scripts/check-snippets.mjs. Zastępuje każdy tab stop jego wartością domyślną, zapisuje wynik do .snippet-check/ i zgłasza błąd przy niepoprawnym JSON-ie, braku scope lub description, prefiksie spoza formy kategoria-nazwa albo prefiksie użytym dwa razy.

// scripts/check-snippets.mjs: lint and expand project snippets. Run: node scripts/check-snippets.mjs
import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
const DIR = '.vscode';
const OUT = '.snippet-check';
const EXT = { typescript: 'ts', typescriptreact: 'tsx' };
const errors = [];
const seen = new Map();
// Replace every tab stop with its default so the result is plain source code.
function expand(text) {
let prev;
do {
prev = text;
text = text
.replace(/\$\{\d+:([^${}]*)\}/g, '$1') // ${1:Default} -> Default
.replace(/\$\{\d+\|([^,|]*)[^}]*\|\}/g, '$1') // ${1|a,b|} -> a
.replace(/\$\{[A-Z_]+\}/g, 'Example'); // ${TM_FILENAME_BASE} -> Example
} while (text !== prev);
return text.replace(/\$\d+/g, '').replace(/\$[A-Z_]+/g, 'Example').replace(/\\\$/g, '$');
}
rmSync(OUT, { recursive: true, force: true });
mkdirSync(OUT);
for (const file of readdirSync(DIR).filter((f) => f.endsWith('.code-snippets'))) {
let snippets;
try {
snippets = JSON.parse(readFileSync(join(DIR, file), 'utf8'));
} catch (e) {
errors.push(`${file}: not strict JSON (${e.message})`);
continue;
}
for (const [name, s] of Object.entries(snippets)) {
const where = `${file} > ${name}`;
if (!s.prefix || !s.body) {
errors.push(`${where}: needs prefix and body`);
continue;
}
if (!s.scope) errors.push(`${where}: no scope, so it is offered in every language`);
if (!s.description) errors.push(`${where}: no description`);
for (const p of [s.prefix].flat()) {
if (!/^[a-z]+-[a-z0-9-]+$/.test(p)) errors.push(`${where}: prefix "${p}" is not category-name`);
if (seen.has(p)) errors.push(`${where}: prefix "${p}" is also used by ${seen.get(p)}`);
seen.set(p, where);
}
const ext = EXT[String(s.scope ?? '').split(',')[0].trim()];
if (ext) writeFileSync(join(OUT, `${name.replace(/\W+/g, '_')}.${ext}`), expand([s.body].flat().join('\n')));
}
}
if (errors.length) {
console.error(errors.join('\n'));
process.exit(1);
}
console.log(`${seen.size} snippet prefixes OK, expanded bodies in ${OUT}/`);

VS Code akceptuje komentarze w plikach snippetów; ta kontrola celowo ich nie akceptuje, żeby plik pozostał czytelny dla maszyn. Obsługuje zwykłe zmienne, takie jak ${TM_FILENAME_BASE}, ale nie transformacje zmiennych; trzymaj transformacje poza wspólnymi snippetami albo rozbuduj funkcję expand. Sprawdzaj typy rozwiniętych treści za pomocą pliku tsconfig.snippets.json obok tsconfig.json i dodaj .snippet-check/ do .gitignore:

{ "extends": "./tsconfig.json", "include": [".snippet-check/*.ts", ".snippet-check/*.tsx"] }

Zostaw jawne wzorce *.ts i *.tsx: przy samym wpisie ".snippet-check" TypeScript pomija katalog, bo jego nazwa zaczyna się od kropki, i zgłasza „No inputs were found” (sprawdzone na TypeScript 5.9.3).

Gdy sprawdzanie typów nie przechodzi na rozwiniętym snippecie, snippet rozjechał się z kodem: ktoś zmienił nazwę hooka, przeniósł komponent albo wartość domyślna wskazuje coś, co nigdy nie istniało. Popraw snippet, nie kontrolę.

Kontrola 2: każdy plik wskazany w regule nadal istnieje. Zapisz ten skrypt jako scripts/check-rule-anchors.sh. Nie przechodzi, gdy reguła wskazuje plik przemianowany lub usunięty, a to najczęstszy sposób, w jaki reguła po cichu przestaje działać.

#!/usr/bin/env sh
# Fails when a Cursor rule names a file that no longer exists.
missing=0
for p in $(grep -ohE '(src|app|lib|tests)/[A-Za-z0-9_./-]*\.[a-z]+' .cursor/rules/*.mdc | sort -u); do
[ -e "$p" ] || { echo "stale anchor in .cursor/rules: $p"; missing=1; }
done
exit $missing

Uruchamiaj obie w tym samym jobie CI, który już sprawdza typy:

- run: node scripts/check-snippets.mjs
- run: npx tsc --noEmit -p tsconfig.snippets.json
- run: sh scripts/check-rule-anchors.sh

Kod, który agent generuje ze szkieletu snippetu, przechodzi przez te same bramki co każda inna zmiana: testy, o które prosi prompt, sprawdzanie typów i lint. Kontrole nie powiedzą ci natomiast, czy sama konwencja jest dobra. To decyzja człowieka, więc nadaj jej właściciela: wpis w CODEOWNERS, na przykład /.vscode/*.code-snippets @your-org/frontend-leads i /.cursor/rules/ @your-org/frontend-leads, sprawia, że tech lead zatwierdza każdą zmianę we wspólnej warstwie. Raz w miesiącu albo po refaktoryzacji uruchom poniższy audyt rozjazdów.

Co psuje się w bibliotece snippetów Cursora i jak to naprawić

Dział zatytułowany „Co psuje się w bibliotece snippetów Cursora i jak to naprawić”

Snippet nie pojawia się na liście podpowiedzi. Plik nie leży w katalogu .vscode w korzeniu projektu, scope podaje zły identyfikator języka (typescriptreact dla .tsx, nie tsx) albo JSON jest niepoprawny. Uruchom node scripts/check-snippets.mjs, a potem Insert Snippet w pliku docelowego języka, żeby zobaczyć, co Cursor faktycznie wczytał.

Cursor Tab i snippet walczą o klawisz Tab. Tab akceptuje podpowiedź inline Cursor Tab, ale może też zaakceptować podświetloną pozycję na liście podpowiedzi. To, która wygrywa, gdy widać obie, zależy od wersji i ustawień; nie udało się tego zweryfikować dla linii Cursor 3.x w dniu 2026-09-26. Używaj prefiksów kategoria-nazwa, które nie wyglądają jak kod, żeby Cursor Tab rzadko coś na nich przewidywał, a w razie wątpliwości wstawiaj snippet z listy podpowiedzi albo przez Insert Snippet. Zostaw ustawienie VS Code editor.tabCompletion na domyślnej wartości (wyłączone), chyba że zespół zgodzi się je włączyć.

Agent ignoruje kształt ze snippetu. Reguła nie ładuje się dla tego pliku albo wskazuje snippet, ale nie plik wzorcowy. Porównaj globs reguły ze ścieżką pliku i dopisz do reguły konkretny plik wzorcowy. Reguła mówiąca „trzymaj się wzorca” bez wskazania pliku nie daje agentowi niczego do skopiowania.

Snippet, reguła i kod mówią co innego. Ktoś zaktualizował tylko jedno z trojga. Uruchom prompt audytu rozjazdów, ustal, które z nich odzwierciedla aktualną konwencję, i zmień pozostałe dwa w jednym pull requeście, żeby recenzent widział całą zmianę.

Reguła wskazuje plik, który został przeniesiony. Kontrola kotwic nie przechodzi w CI. Wskaż w regule nową ścieżkę pliku albo lepszy przykład, jeśli stary został podzielony. Jeśli kontroli jeszcze nie ma w CI, uruchom lokalnie sh scripts/check-rule-anchors.sh przed zmergowaniem refaktoryzacji.

Biblioteka urosła do stu snippetów i nikt niczego nie znajduje. Poszukaj w repozytorium kształtu, który generuje każdy prefiks; jeśli nic napisanego w ostatnim kwartale nie pasuje do jego kształtu, usuń snippet. Zostaw tylko kształty, które zespół pisze co tydzień, a resztę przenieś do reguł albo promptów.

Wygenerowany boilerplate się kompiluje, ale brakuje w nim stanu. Sprawdzanie typów dowodzi kształtu, nie zachowania. Wymagaj w prompcie jednego testu na każdy stan renderowania, tak jak robi to prompt komponentu powyżej, a o tym, jak nie dać agentowi edytować tych testów, żeby przeszły, przeczytasz w ochronie wyroczni testowej.