05 — Zaczepy kursora: zautomatyzuj przepływ pracy za pomocą poręczy AI
Praca z autonomicznymi agentami AI wiąże się ze strukturalnym problemem: nie można kontrolować każdego z nich akcja w czasie rzeczywistym, ale nie można nawet ślepo ufać temu, co robią. Agent, który pisze kodu, wykonuj polecenia w terminalu i edytuj pliki w automatycznej sekwencji może być niezwykle produktywne, ale także niebezpiecznie nieprzewidywalne, jeśli nie istnieją mechanizmy kontroli.
Odpowiedź kursora na ten problem nazywa się Haczyki. Wprowadzony z kursorem 1,7 cala 2025 system Hooks umożliwia wstrzykiwanie niestandardowej logiki w precyzyjnych momentach cyklu życia agenta: przed wykonaniem polecenia powłoki, po modyfikacji pliku, przed wywołaniem MCP, kiedy jego praca dobiegnie końca. W zasadzie są bogami automatyczne barierki że możesz skonfiguruj raz i które uruchamiają się za każdym razem, gdy agent działa w twojej bazie kodu.
W tym artykule szczegółowo omówiono system Hooks: od podstawowej konfiguracji po wzorce zaawansowane funkcje automatycznego formatowania, sprawdzania bezpieczeństwa, automatyzacji testów i synchronizacji dokumentacji. Na koniec będziesz w stanie zbudować solidny przepływ pracy, w którym agent AI będzie produktywny, ale zawsze pod Twoją kontrolą.
Czego dowiesz się w tym artykule
- Czym jest system Cursor's Hooks i czym różni się od prostych skryptów
- Dostępne typy hooków: beforeShellExecution, beforeMCPExecution, beforeReadFile, afterFileEdit, stop
- Jak skonfigurować hooki globalne i obejmujące cały projekt za pomocą hooks.json
- Jak zbudować hooki blokujące niebezpieczne działania za pomocą kodu wyjścia i odpowiedzi JSON
- Automatyzacja po edycji: automatyczne formatowanie za pomocą Prettier/ESLint poprzez afterFileEdit
- Zatrzymaj hooki w celu ostatecznej weryfikacji i powiadomień po zakończeniu zadania
- Praktyczne wzorce: kontrola bezpieczeństwa, automatyczne uruchamianie testów, synchronizacja dokumentacji
- Jak hooki integrują się z trybem agenta, zapewniając kompletny i bezpieczny przepływ pracy
- Haki debugowania: kanał wyjściowy, logowanie, rozwiązywanie typowych błędów
- Najlepsze praktyki dotyczące idempotentnych, wydajnych i łatwych w utrzymaniu haków
Pozycja w serii: Cursor IDE i AI-Native Development
| # | Przedmiot | Poziom |
|---|---|---|
| 1 | Cursor IDE: Kompletny przewodnik dla programistów | Początkujący |
| 2 | Reguły kursora: konfigurowanie sztucznej inteligencji dla twojego projektu | Mediator |
| 3 | Tryb agenta: zmodyfikuj bazę kodu za pomocą polecenia | Mediator |
| 4 | Tryb planu i agenci działający w tle | Zaawansowany |
| 5 | Zaczepy kursora: zautomatyzuj przepływ pracy (tutaj jesteś) | Mediator |
| 6 | MCP i kursor: Połącz IDE z bazą danych i API | Zaawansowany |
| 7 | Debugowanie za pomocą Cursor AI: 3x szybsze | Mediator |
| 8 | Kursor vs Windsurf vs Copilot w 2026 roku | Początkujący |
| 9 | Profesjonalny przebieg pracy: projekt kątowy z kursorem | Zaawansowany |
Co to jest hak w kursorze
Hak w Cursor i a proces zewnętrzny który uruchamia się automatycznie w odpowiedzi do określonych zdarzeń cyklu życia agenta. Kiedy agent ma zamiar wykonać polecenie powłoki, Kursor wywołuje hak, przekazując mu informacje o akcji w formacie JSON przez standardowe wejście. Twój hak może przeanalizuj te informacje i odpowiedz, wydając dyrektywę: zezwól na akcję, zablokuj ją lub poproś użytkownika o potwierdzenie.
Ważne jest, aby zrozumieć, że haki nie są to proste skrypty powłoki to się włącza tło. Są to procesy synchroniczne zintegrowane z cyklem agenta: agent zatrzymuje się, czeka na hook, a następnie decyduje, jak postępować w oparciu o tę odpowiedź. To czyni z nich narzędzia potężne narzędzie do walidacji, audytu i automatyzacji.
Haki dostępne w Cursor 1.7+ obejmują następujące zdarzenia cyklu życia:
- przed wykonaniem Shell - Wykonywane przed każdym poleceniem w terminalu. Otrzymuje dokładne polecenie, które agent ma zamiar wykonać. Może blokować niebezpieczne polecenia, rejestrować je lub modyfikować.
- przed wykonaniem MCPE - Wykonywane przed każdym wywołaniem serwera MCP. Otrzymuje nazwę narzędzia i parametry wejściowe. Przydatne do audytowania operacji na bazach danych lub API.
- przedReadFile - Wykonywane zanim agent odczyta zawartość pliku. Umożliwia kontrolowanie, do jakich plików agent może uzyskać dostęp lub maskować poufne informacje.
- poFileEdit - Wykonywane po każdej zmianie pliku. Otrzymuje ścieżkę plik i wprowadzone zmiany (stary_string/nowy_string). Idealny do automatycznego formatowania i lintingu.
- zatrzymywać się - Wykonywane, gdy agent zakończy swoje zadanie. Przydatne do powiadomień, automatyczne zatwierdzanie, synchronizacja dokumentacji i akcje porządkowe.
Funkcja Beta w ewolucji
Zaczepy kursora zostały wprowadzone jako funkcja beta w Cursor 1.7 w październiku 2025 r. dokumentacja i interfejsy API mogą ulec zmianie w kolejnych wydaniach. Przed wdrożeniem hooków w środowisku produkcyjnym sprawdź oficjalną dokumentację pod adresem https://www.cursor.com/docs/agent/hooks w przypadku jakichkolwiek aktualizacji.
Konfiguracja: hooks.json globalny i na poziomie projektu
Zaczepy kursora są konfigurowane za pomocą pliku hooks.json. Istnieją dwa poziomy konfiguracji, które działają komplementarnie:
- Globalne haki (
~/.cursor/hooks.json) - Dotyczą wszystkich otwieraj projekty za pomocą kursora na tym komputerze. Idealny do polityki bezpieczeństwa osobistego, powiadomień lub narzędzia audytu, które zawsze chcesz mieć aktywne. - Haczyki projektowe (
.cursor/hooks.jsonw katalogu głównym repo) - Tak dotyczą one tylko bieżącego projektu i są zapisywane w repozytorium. Pozwalają dzielić się te same automatyzacje w całym zespole.
Gdy oba pliki istnieją, zaczepy są łączone: najpierw wykonywane są zaczepy globalne,
następnie te z projektu dla każdego wydarzenia. Format pliku jest prosty: obiekt JSON z rozszerzeniem
wersja i obiekt hooks który mapuje każde zdarzenie na tablicę poleceń.
Oto kompletny przykład hooks.json który konfiguruje hooki dla wszystkich głównych wydarzeń:
// .cursor/hooks.json - Configurazione hooks a livello di progetto
{
"version": 1,
"hooks": {
"beforeShellExecution": [
{
"command": "node .cursor/hooks/security-check.js"
}
],
"beforeMCPExecution": [
{
"command": "node .cursor/hooks/mcp-audit.js"
}
],
"beforeReadFile": [
{
"command": "bash .cursor/hooks/file-access-policy.sh"
}
],
"afterFileEdit": [
{
"command": "node .cursor/hooks/auto-format.js"
},
{
"command": "node .cursor/hooks/run-tests.js"
}
],
"stop": [
{
"command": "node .cursor/hooks/final-check.js"
}
]
}
}
Pole command akceptuje dowolne polecenie wykonywalne: skrypt Node.js, bash, Python,
pliki binarne npm. Kursor uruchamia polecenie jako oddzielny proces, przekazuje dane zdarzenia
stdin w formacie JSON i odczytuje odpowiedź haka standardowe wyjście.
Pojawiają się błędy i dzienniki diagnostyczne stderr.
Podstawowa struktura JSON, którą Cursor wysyła przez standardowe wejście do każdego haka, obejmuje następujące wspólne pola:
// Struttura JSON inviata via stdin a ogni hook
{
"conversation_id": "668320d2-2fd8-4888-b33c-2a466fec86e7",
"generation_id": "490b90b7-a2ce-4c2c-bb76-cb77b125df2f",
"hook_event_name": "beforeShellExecution", // nome dell'evento
"workspace_roots": ["/path/to/project"], // root del workspace
// campi specifici per ogni tipo di hook...
}
Hak przed wykonaniem: blokuj niebezpieczne działania
Haczyki zanim są najpotężniejsi, ponieważ mogą blokować działania, zanim one nastąpią. Najbardziej bezpośrednim przypadkiem użycia jest ochrona krytycznych plików lub katalogów przed przypadkową modyfikacją agenta lub blokuj destrukcyjne polecenia powłoki.
Dla faceta przed wykonaniem Shell, JSON otrzymany przez standardowe wejście zawiera pole
command z dokładnym poleceniem, które agent ma zamiar wykonać, oraz polem cwd
z bieżącym katalogiem roboczym:
// Input stdin per beforeShellExecution
{
"conversation_id": "668320d2-2fd8-4888-b33c-2a466fec86e7",
"generation_id": "490b90b7-a2ce-4c2c-bb76-cb77b125df2f",
"command": "rm -rf ./dist",
"cwd": "/Users/dev/my-project",
"hook_event_name": "beforeShellExecution",
"workspace_roots": ["/Users/dev/my-project"]
}
Hak odpowiada za pośrednictwem standardowego wyjścia za pomocą kodu JSON wskazującego, czy akcja powinna być dozwolona, czy zablokowana.
Pole kluczowe, np permission z wartościami "allow", "deny"
o "ask". Oto hak Node.js, który blokuje potencjalnie destrukcyjne polecenia:
// .cursor/hooks/security-check.js
// Hook: blocca comandi shell pericolosi
const readline = require('readline');
async function readStdin() {
return new Promise((resolve) => {
const rl = readline.createInterface({ input: process.stdin });
let data = '';
rl.on('line', (line) => { data += line; });
rl.on('close', () => resolve(data));
});
}
// Pattern di comandi che devono essere bloccati
const DANGEROUS_PATTERNS = [
/rm\s+-rf\s+\//, // rm -rf / (root filesystem)
/rm\s+-rf\s+~\//, // rm -rf ~/
/sudo\s+rm\s+-rf/, // sudo rm -rf qualsiasi cosa
/DROP\s+DATABASE/i, // SQL DROP DATABASE
/DROP\s+TABLE.*--drop/i, // SQL DROP con flag pericolose
/git\s+push.*--force\s+origin\s+main/, // force push su main
];
// File e directory protetti dall'agente
const PROTECTED_PATHS = [
'.env',
'.env.production',
'secrets/',
'firebase.json',
'.firebaserc',
];
async function main() {
const rawInput = await readStdin();
let input;
try {
input = JSON.parse(rawInput);
} catch (e) {
// Se non riusciamo a parsare, lasciamo passare (fail-open)
process.stdout.write(JSON.stringify({ permission: 'allow' }));
return;
}
const command = input.command || '';
// Controlla comandi pericolosi
const isDangerous = DANGEROUS_PATTERNS.some((pattern) =>
pattern.test(command)
);
if (isDangerous) {
process.stderr.write(`[security-check] Comando bloccato: ${command}\n`);
process.stdout.write(
JSON.stringify({
permission: 'deny',
userMessage: `Comando bloccato dalla security policy: ${command}`,
agentMessage:
'Questo comando e stato bloccato dalla policy di sicurezza del progetto. ' +
'Non eseguire comandi rm -rf su percorsi di sistema o force push su branch protetti.',
})
);
return;
}
// Controlla se il comando tocca file protetti
const touchesProtectedPath = PROTECTED_PATHS.some((path) =>
command.includes(path)
);
if (touchesProtectedPath) {
process.stdout.write(
JSON.stringify({
permission: 'ask',
userMessage: `Stai per eseguire: ${command} - Procedi?`,
})
);
return;
}
// Comando sicuro: consenti
process.stdout.write(JSON.stringify({ permission: 'allow' }));
}
main().catch((err) => {
process.stderr.write(`[security-check] Errore: ${err.message}\n`);
process.stdout.write(JSON.stringify({ permission: 'allow' })); // fail-open
});
Con "permission": "ask", Kursor wyświetli wcześniej użytkownikowi okno dialogowe z potwierdzeniem
kontynuować, wyświetlając komunikat zdefiniowany w userMessage. Użytkownik może zatwierdzić
lub odmówić działania. Z "permission": "deny", akcja jest trwale zablokowana
a wiadomość jest wyświetlana zarówno użytkownikowi, jak i przekazywana agentowi jako kontekst.
Hook do ochrony określonych plików za pomocą beforeReadFile
Hak beforeReadFile otrzymuje pole file_path w stdin JSON.
Można go użyć, aby uniemożliwić agentowi odczytywanie plików zawierających dane uwierzytelniające lub klucze tajne, lub aby
maskuj zawartość wrażliwych plików, zanim zostaną one przekazane do modelu AI.
Hook afterFileEdit: automatyczne formatowanie i automatyczne linting
Hak poFileEdit i najczęściej stosowane w codziennej praktyce. Jest wykonywany za każdym razem, gdy agent zapisuje lub modyfikuje plik i otrzymuje JSON ze ścieżką do pliku przez standardowe wejście zmodyfikowane i szereg zmian z oryginalnymi i nowymi ciągami:
// Input stdin per afterFileEdit
{
"conversation_id": "abc123",
"generation_id": "def456",
"file_path": "/Users/dev/my-project/src/app/services/user.service.ts",
"edits": [
{
"old_string": "const data = undefined",
"new_string": "const data: UserData | null = null"
}
],
"hook_event_name": "afterFileEdit",
"workspace_roots": ["/Users/dev/my-project"]
}
Dzięki tym danym możesz automatycznie uruchomić swój ulubiony program formatujący na właśnie edytowanym pliku. Oto kompletny hak Node.js, który stosuje Prettier dla TypeScript/JavaScript i ESLint w celu naprawy automatyczne, z uwzględnieniem konfiguracji już istniejących w projekcie:
// .cursor/hooks/auto-format.js
// Hook: auto-format e linting dopo ogni modifica dell'agente
const readline = require('readline');
const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs');
async function readStdin() {
return new Promise((resolve) => {
const rl = readline.createInterface({ input: process.stdin });
let data = '';
rl.on('line', (line) => { data += line; });
rl.on('close', () => resolve(data));
});
}
// Estensioni supportate dal formatter
const PRETTIER_EXTENSIONS = new Set([
'.ts', '.tsx', '.js', '.jsx',
'.json', '.css', '.scss',
'.html', '.md', '.yaml', '.yml',
]);
const ESLINT_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx']);
function runFormatter(filePath, workspaceRoot) {
const ext = path.extname(filePath);
// Prettier: formatta il file se l'estensione e supportata
if (PRETTIER_EXTENSIONS.has(ext)) {
const prettierConfig = path.join(workspaceRoot, '.prettierrc');
const hasPrettierConfig =
fs.existsSync(prettierConfig) ||
fs.existsSync(path.join(workspaceRoot, 'prettier.config.js')) ||
fs.existsSync(path.join(workspaceRoot, '.prettierrc.json'));
if (hasPrettierConfig || fs.existsSync(
path.join(workspaceRoot, 'node_modules', '.bin', 'prettier')
)) {
try {
execSync(
`npx prettier --write "${filePath}"`,
{
cwd: workspaceRoot,
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 10000,
}
);
process.stderr.write(`[auto-format] Prettier OK: ${filePath}\n`);
} catch (e) {
process.stderr.write(
`[auto-format] Prettier warning: ${e.message}\n`
);
}
}
}
// ESLint --fix: applica fix automatici per TS/JS
if (ESLINT_EXTENSIONS.has(ext)) {
const hasEslint = fs.existsSync(
path.join(workspaceRoot, 'node_modules', '.bin', 'eslint')
);
const hasEslintConfig =
fs.existsSync(path.join(workspaceRoot, '.eslintrc.js')) ||
fs.existsSync(path.join(workspaceRoot, '.eslintrc.json')) ||
fs.existsSync(path.join(workspaceRoot, 'eslint.config.js'));
if (hasEslint && hasEslintConfig) {
try {
execSync(
`npx eslint --fix "${filePath}"`,
{
cwd: workspaceRoot,
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 15000,
}
);
process.stderr.write(`[auto-format] ESLint --fix OK: ${filePath}\n`);
} catch (e) {
// ESLint esce con codice 1 se ci sono errori non fixabili: non e un failure dell'hook
process.stderr.write(
`[auto-format] ESLint warning (unfixable errors): ${path.basename(filePath)}\n`
);
}
}
}
}
async function main() {
const rawInput = await readStdin();
let input;
try {
input = JSON.parse(rawInput);
} catch (e) {
process.stderr.write(`[auto-format] JSON parse error: ${e.message}\n`);
return; // afterFileEdit non richiede risposta JSON
}
const filePath = input.file_path;
const workspaceRoot = input.workspace_roots?.[0] || process.cwd();
if (!filePath) {
process.stderr.write('[auto-format] Nessun file_path ricevuto\n');
return;
}
// Salta file nelle node_modules o .cursor
if (filePath.includes('node_modules') || filePath.includes('.cursor')) {
return;
}
runFormatter(filePath, workspaceRoot);
}
main();
afterFileEdit nie wymaga odpowiedzi JSON
W przeciwieństwie do haków zanim, hak afterFileEdit i zaprojektowany z myślą o efektach
zabezpieczenia: formatowanie, linting, aktualizacja indeksu. Nie powinien zwracać odpowiedzi JSON
na standardowe wyjście, ponieważ akcja (modyfikacja pliku) już nastąpiła. Hak spełnia swoje zadanie i
kończy; Kursor kontynuuje działanie niezależnie od kodu wyjścia.
Zatrzymaj haki: ostateczna weryfikacja i powiadomienia
Hak zatrzymywać się jest wykonywany, gdy agent uzna swoje zadanie za zakończone. Nie
otrzymuje szczegółowe informacje na temat konkretnych wykonanych czynności, ale ma do nich dostęp conversation_id i aj
workspace_roots. I idealny czas na zamknięcie akcji, takich jak powiadomienia, zatwierdzenia
automatycznie, aktualizując dokumentację lub przeprowadzając testy całego pakietu.
// Input stdin per stop hook
{
"conversation_id": "668320d2-2fd8-4888-b33c-2a466fec86e7",
"generation_id": "490b90b7-a2ce-4c2c-bb76-cb77b125df2f",
"hook_event_name": "stop",
"workspace_roots": ["/Users/dev/my-project"]
}
Oto dwa praktyczne przykłady hooków zatrzymujących: pierwszy wysyła powiadomienie na komputer w systemie macOS, drugi przeprowadza weryfikację git pracy wykonanej przez agenta:
// .cursor/hooks/notify-completion.sh
# Stop hook: notifica desktop al completamento del task
#!/bin/bash
# Leggi il JSON da stdin (anche se non lo usiamo qui)
INPUT=$(cat)
# Notifica su macOS
if [[ "$OSTYPE" == "darwin"* ]]; then
osascript -e 'display notification "Il task e completato!" with title "Cursor Agent" subtitle "Controlla le modifiche nel repository"'
fi
# Notifica su Linux con notify-send
if command -v notify-send &>/dev/null; then
notify-send "Cursor Agent" "Task completato! Controlla le modifiche."
fi
# Log su file per audit
echo "$(date '+%Y-%m-%d %H:%M:%S') - Agent task completed" >> ~/.cursor/agent-log.txt
exit 0
// .cursor/hooks/git-status-check.js
// Stop hook: mostra un riepilogo delle modifiche git dell'agente
const readline = require('readline');
const { execSync } = require('child_process');
async function readStdin() {
return new Promise((resolve) => {
const rl = readline.createInterface({ input: process.stdin });
let data = '';
rl.on('line', (line) => { data += line; });
rl.on('close', () => resolve(data));
});
}
async function main() {
const rawInput = await readStdin();
const input = JSON.parse(rawInput);
const workspaceRoot = input.workspace_roots?.[0] || process.cwd();
try {
// Mostra quanti file sono stati modificati dall'agente
const status = execSync('git status --short', {
cwd: workspaceRoot,
encoding: 'utf8',
});
const modifiedFiles = status
.split('\n')
.filter((line) => line.trim().length > 0);
if (modifiedFiles.length > 0) {
process.stderr.write(
`[git-check] Agent ha modificato ${modifiedFiles.length} file:\n`
);
modifiedFiles.forEach((f) => process.stderr.write(` ${f}\n`));
} else {
process.stderr.write('[git-check] Nessuna modifica in staging\n');
}
} catch (e) {
process.stderr.write(`[git-check] Git non disponibile: ${e.message}\n`);
}
}
main();
Praktyczne przypadki użycia dla zespołów programistycznych
Haki stają się szczególnie przydatne podczas pracy w zespołach lub nad projektami z wymaganiami rygorystyczne pod względem jakości. Przyjrzyjmy się najskuteczniejszym wzorcom wyłaniającym się ze społeczności Cursor w 2025 roku.
Wzór 1: Audyt bezpieczeństwa przed operacjami MCP
Jeśli używasz MCP z bazami danych lub zewnętrznymi interfejsami API, hook beforeMCPExecution umożliwia zalogowanie się
każdą operację przed jej wykonaniem. Wejściowy kod JSON zawiera nazwę narzędzia MCP oraz i
dokładne parametry przekazane agentowi:
// .cursor/hooks/mcp-audit.js
// Audit log per ogni chiamata MCP dell'agente
const readline = require('readline');
const fs = require('fs');
const path = require('path');
async function readStdin() {
return new Promise((resolve) => {
const rl = readline.createInterface({ input: process.stdin });
let data = '';
rl.on('line', (line) => { data += line; });
rl.on('close', () => resolve(data));
});
}
// Operazioni MCP che devono essere bloccate in produzione
const BLOCKED_MCP_TOOLS = [
'delete_database',
'drop_table',
'truncate_table',
'execute_raw_sql', // troppo generico, meglio bloccare e richiedere conferma
];
async function main() {
const rawInput = await readStdin();
const input = JSON.parse(rawInput);
const toolName = input.tool_name || 'unknown';
const toolInput = input.tool_input || '{}';
const workspaceRoot = input.workspace_roots?.[0] || '.';
// Log dell'operazione MCP
const auditEntry = {
timestamp: new Date().toISOString(),
tool: toolName,
input: toolInput,
conversation_id: input.conversation_id,
};
const auditFile = path.join(workspaceRoot, '.cursor', 'mcp-audit.log');
fs.appendFileSync(
auditFile,
JSON.stringify(auditEntry) + '\n'
);
// Blocca tool distruttivi
if (BLOCKED_MCP_TOOLS.includes(toolName)) {
process.stdout.write(
JSON.stringify({
permission: 'deny',
userMessage: `Operazione MCP bloccata: ${toolName}`,
agentMessage: `Il tool MCP '${toolName}' e bloccato dalla policy di progetto. ` +
`Usa operazioni più specifiche o chiedi conferma manuale all'utente.`,
})
);
return;
}
// Tutto il resto e consentito
process.stdout.write(JSON.stringify({ permission: 'allow' }));
}
main();
Wzorzec 2: Automatyczne uruchamianie testu po zmianach w teście
Innym bardzo przydatnym wzorcem jest automatyczne uruchamianie testów, gdy agent modyfikuje pliki specyfikacja lub test. Dzięki temu zmiany nie psują istniejącego pakietu:
// .cursor/hooks/run-tests.js
// Esegue i test quando l'agente modifica file .spec.ts o .test.ts
const readline = require('readline');
const { execSync } = require('child_process');
const path = require('path');
async function readStdin() {
return new Promise((resolve) => {
const rl = readline.createInterface({ input: process.stdin });
let data = '';
rl.on('line', (line) => { data += line; });
rl.on('close', () => resolve(data));
});
}
async function main() {
const rawInput = await readStdin();
const input = JSON.parse(rawInput);
const filePath = input.file_path || '';
const workspaceRoot = input.workspace_roots?.[0] || process.cwd();
// Esegui test solo per file spec/test
const isTestFile = filePath.includes('.spec.') || filePath.includes('.test.');
const isServiceOrComponent =
filePath.endsWith('.service.ts') || filePath.endsWith('.component.ts');
if (!isTestFile && !isServiceOrComponent) {
return; // Nessun test da eseguire per questo file
}
process.stderr.write(`[run-tests] Esecuzione test per: ${path.basename(filePath)}\n`);
try {
// Per Angular: esegui solo i test del file modificato (più veloce)
const relativePath = path.relative(workspaceRoot, filePath)
.replace(/\\/g, '/')
.replace(/\.ts$/, '');
execSync(
`npx ng test --include="**/${path.basename(relativePath)}.spec.ts" --watch=false --browsers=ChromeHeadless`,
{
cwd: workspaceRoot,
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 60000,
}
);
process.stderr.write('[run-tests] Test passati\n');
} catch (e) {
// I test falliti vengono loggati ma non bloccano l'hook
process.stderr.write(
`[run-tests] ATTENZIONE: alcuni test falliti per ${path.basename(filePath)}\n`
);
process.stderr.write(e.stdout?.toString() || e.message);
}
}
main();
Wzorzec 3: Dokumentacja zsynchronizowana z bazą kodu
Bardzo przydatny wzorzec w dokumentacji technicznej: gdy agent modyfikuje interfejs TypeScript lub plik routingu, hak zatrzymujący może sprawdzić, czy dokumentacja jest aktualna lub zgłosić co wymaga rewizji:
// .cursor/hooks/doc-sync-check.js
// Verifica se la documentazione e allineata dopo modifiche alle interfacce
const readline = require('readline');
const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs');
async function readStdin() {
return new Promise((resolve) => {
const rl = readline.createInterface({ input: process.stdin });
let data = '';
rl.on('line', (line) => { data += line; });
rl.on('close', () => resolve(data));
});
}
async function main() {
const rawInput = await readStdin();
const input = JSON.parse(rawInput);
const workspaceRoot = input.workspace_roots?.[0] || process.cwd();
try {
// Cerca file di interfacce modificati nell'ultimo commit
const changedFiles = execSync(
'git diff --name-only HEAD~1 HEAD 2>/dev/null || git diff --name-only --cached',
{ cwd: workspaceRoot, encoding: 'utf8' }
).split('\n').filter(Boolean);
const interfaceFiles = changedFiles.filter(
(f) => f.includes('.model.ts') || f.includes('.interface.ts') || f.includes('types.ts')
);
if (interfaceFiles.length > 0) {
const docsDir = path.join(workspaceRoot, 'docs');
const hasDocsDir = fs.existsSync(docsDir);
if (hasDocsDir) {
process.stderr.write(
`[doc-sync] ATTENZIONE: ${interfaceFiles.length} file di interfacce modificati.\n` +
`Considera di aggiornare la documentazione in /docs:\n` +
interfaceFiles.map((f) => ` - ${f}`).join('\n') + '\n'
);
}
}
} catch (e) {
process.stderr.write(`[doc-sync] Errore: ${e.message}\n`);
}
}
main();
Haki i tryb agenta: kompletny system kontroli
Integracja Hooków i Trybu Agenta tworzy pętlę informacji zwrotnej, która sprawia, że agent jest bezpieczniejszy i bezpieczniejszy niezawodny w czasie. Haki działają, gdy agent pracuje autonomicznie nad złożonymi zadaniami jak sieć bezpieczeństwa, która działa na każdym etapie cyklu wykonawczego.
Rozważmy następujący scenariusz ze świata rzeczywistego: używasz trybu agenta do refaktoryzacji modułu Angular. Agent wykonuje po kolei dziesiątki akcji: czyta pliki, modyfikuje komponenty, aktualizuje routing, wykonuje polecenia npm. Przy poprawnie skonfigurowanych hakach każdy krytyczny krok jest przechwytywany:
- przed wykonaniem Shell - Blokuj dowolne polecenie
npm installniezatwierdzony lub pchnij do chronionych gałęzi - przedReadFile - Uniemożliwia agentowi odczyt plików
.envlub pliki konfiguracyjne z sekretami - poFileEdit - Każdy edytowany plik jest automatycznie formatowany Ładniejsze i sprawdzone za pomocą ESLint przed przejściem do następnego kroku
- zatrzymywać się - Gdy agent zakończy, otrzymasz powiadomienie i podsumowanie git zmiany do przejrzenia
Wiadomości agentMessage odpowiedzi haka są przekazywane do modelu AI jako
dodatkowy kontekst. Dzięki temu agent może dostosować swoje zachowanie
na podstawie zdefiniowanych polityk: jeśli hook blokuje określoną akcję z komunikatem wyjaśniającym,
agent może wybrać alternatywne podejście bez interwencji człowieka.
Architektura kompletnego przepływu pracy z hakami
| Faza | Hak | Działanie |
|---|---|---|
| Przed poleceniami powłoki | przed wykonaniem Shell | Blokuj destrukcyjne polecenia, wymagaj potwierdzenia operacji git |
| Zanim zadzwoni MCP | przed wykonaniem MCPE | Dziennik audytu, blokuje niebezpieczne operacje DB |
| Przed odczytaniem plików | przedReadFile | ochrona plików .env, maskowanie sekretów |
| Po każdej zmianie | poFileEdit | Ładniejszy, ESLint --fix, aktualizacja indeksu |
| Zakończ zadanie | zatrzymywać się | Powiadomienia na pulpicie, status git, sprawdź dokumentację |
Debugowanie haków: kanał wyjściowy i rozwiązywanie problemów
Jedną z pierwszych rzeczy, które odkryjesz podczas pracy z hakami, jest to, że problemem są ciche błędy bardziej powszechne. Hak, który się nie uruchamia, zniekształcony kod JSON, nieobsługiwany limit czasu – wszystkie scenariusze może sprawić, że zachowanie agenta będzie nieprzewidywalne. Kursor udostępnia dedykowane narzędzie aby zdiagnozować te problemy.
Aby uzyskać dostęp do Kanał wyjściowy haków:
- Otwórz panel VS Code/Wyjście kursora (Widok > Wyjście lub
Ctrl+Shift+U) - Z rozwijanego menu na pasku panelu wybierz opcję „Haki”.
- Tutaj zobaczysz logi każdego wykonanego hooka: który skrypt został wywołany, kod wyjścia i wszystko co skrypt napisał na stderr
Najczęstsze problemy i sposoby ich rozwiązania:
# Problema 1: Hook non trovato
# Errore: "Cannot find command: node .cursor/hooks/auto-format.js"
# Causa: il path e relativo alla workspace root, non alla posizione di hooks.json
# Soluzione: verifica che il path nel command sia relativo alla root del progetto
# Problema 2: JSON malformato nella risposta
# Errore: "Malformed JSON response from hook, silently allowing"
# Causa: output non-JSON su stdout (print di debug, output di npm, ecc.)
# Soluzione: usa SEMPRE process.stderr per i log, process.stdout SOLO per la risposta JSON
# Problema 3: Hook troppo lento (timeout)
# Causa: operazione bloccante (es. test completo della suite) senza timeout
# Soluzione: aggiungi timeout alle chiamate execSync
try {
execSync(command, {
cwd: workspaceRoot,
timeout: 10000, // 10 secondi max
stdio: ['ignore', 'pipe', 'pipe'],
});
} catch (e) {
if (e.signal === 'SIGTERM') {
process.stderr.write('[hook] Timeout raggiunto, operazione saltata\n');
}
}
# Problema 4: Hook che fallisce per mancanza di node_modules
# Causa: il progetto non ha installato le dipendenze
# Soluzione: controlla sempre l'esistenza dei binari prima di usarli
const hasPrettier = fs.existsSync(
path.join(workspaceRoot, 'node_modules', '.bin', 'prettier')
);
if (!hasPrettier) return; // Salta silenziosamente se non disponibile
Przydatny wzorzec do początkowego debugowania i dodawania haka rejestrującego, który rejestruje każde zdarzenie do pliku, dzięki czemu będziesz mieć pełny ślad tego, co zrobił agent i w jakiej kolejności:
// .cursor/hooks/debug-logger.js
// Hook universale per debug: logga tutti gli eventi su file
const readline = require('readline');
const fs = require('fs');
const path = require('path');
async function readStdin() {
return new Promise((resolve) => {
const rl = readline.createInterface({ input: process.stdin });
let data = '';
rl.on('line', (line) => { data += line; });
rl.on('close', () => resolve(data));
});
}
async function main() {
const rawInput = await readStdin();
try {
const input = JSON.parse(rawInput);
const logEntry = {
time: new Date().toISOString(),
event: input.hook_event_name,
command: input.command, // per beforeShellExecution
file_path: input.file_path, // per beforeReadFile/afterFileEdit
tool_name: input.tool_name, // per beforeMCPExecution
conversation_id: input.conversation_id,
};
const logFile = path.join(
input.workspace_roots?.[0] || '.',
'.cursor',
'hooks-debug.log'
);
fs.appendFileSync(logFile, JSON.stringify(logEntry) + '\n');
} catch (e) {
// Silenzioso in caso di errore di parsing
}
}
main();
Najlepsze praktyki dotyczące wytrzymałych i łatwych w utrzymaniu haków
Po zapoznaniu się z konfiguracją, przykładami i debugowaniem, przyjrzyjmy się zasadom rządzącym konstrukcją haczyków profesjonalnej jakości.
Zasada 1: Fail-Open lub Fail-Safe, nigdy Fail-Silent
Musisz świadomie zdecydować, jak się zachować w przypadku błędu. Do haków zabezpieczających (blokada poleceń, ochrona plików), prawidłowe zachowanie i często zabezpieczenie przed awarią: w przypadku błędu zablokuj akcję ze względów bezpieczeństwa. Do haków automatyzujących (formatowanie, linting), wolisz otwieranie awaryjne: Jeśli formater nie jest dostępny, poczekaj, aż akcja się zakończy bez blokowania pracy agenta. Nigdy nie wybieraj opcji „fail-silent”: zawsze zapisuj błąd na stderr.
Zasada 2: Idempotencja
Hooki można wywoływać wielokrotnie w tym samym pliku, szczególnie afterFileEdit
w trybie agenta, który modyfikuje ten sam plik w kolejnych krokach. Twoje haki muszą być idempotentne:
dwukrotne uruchomienie Prettier na tym samym pliku powinno dać taki sam wynik, jak jednorazowe uruchomienie.
Unikaj operacji dołączających dane lub tworzących duplikaty.
Zasada 3: Wydajność i limity czasu
Hooki są synchroniczne w pętli agenta: powolne hooki spowalniają cały przepływ pracy. Niektóre praktyczne wskazówki:
- Dla
beforeShellExecutionebeforeReadFile: Odpowiedź w ciągu 2-3 sekund - Dla
afterFileEdit: Formatowanie i linting powinny zakończyć się w ciągu 10-15 sekund - Dla
stop: masz większy margines (30-60 sekund), ale powiadomienia i logi muszą być szybkie - Zawsze ustawiaj limit czasu dla połączeń
execSyncaby uniknąć nieokreślonego blokowania
Zasada 4: Podział obowiązków
Wolisz wiele małych, skupionych haczyków od jednego haczyka, który robi wszystko. Każdy hak w pliku
hooks.json i niezależne: możesz dodawać, usuwać lub debugować każdy bez
dotykaj innych. Jeden hak do formatowania, jeden do lintingu, jeden do testowania: każdy skrypt ma
wyraźną odpowiedzialność.
Zasada 5: Bezpieczeństwo samych haków
Haki wykonują dowolny kod: są możliwym wektorem ataku, jeśli
hooks.json projektu jest zagrożona. Nigdy nie dołączaj do swoich danych zakodowanych na stałe sekretów
skrypty hakowe. Użyj zmiennych środowiskowych lub plików konfiguracyjnych znajdujących się poza repozytorium.
Sprawdź ścieżki plików otrzymanych przez standardowe wejście przed użyciem ich w poleceniach powłoki, aby zapobiec ścieżce
przejście.
// Esempio: validazione del path per prevenire path traversal
function isPathSafe(filePath, workspaceRoot) {
const resolvedPath = require('path').resolve(filePath);
const resolvedRoot = require('path').resolve(workspaceRoot);
// Verifica che il file sia dentro la workspace root
return resolvedPath.startsWith(resolvedRoot + require('path').sep);
}
// Usa sempre questa validazione prima di operazioni su file
if (!isPathSafe(input.file_path, workspaceRoot)) {
process.stderr.write('[hook] Path non sicuro, operazione saltata\n');
return;
}
Haki a reguły kursora: kiedy używać których
Le Reguły kursora instruują agenta Jak napisz kod (styl, konwencje, wzorce do naśladowania). The Haczyki sprawdzają Co to się zdarza gdy agent wykonuje akcje (automatyczne formatowanie, sprawdzanie bezpieczeństwa, powiadomienia). Są one komplementarne: użyj Reguł do kierowania generowaniem kodu, Hooków do zagwarantowania jakość i bezpieczeństwo działań.
Wnioski
System Cursor’s Hooks stanowi milowy krok w kontroli nad agentem AI. Nie chodzi już o to, by mieć nadzieję, że agent postąpi właściwie: możesz zdefiniować precyzyjne zasady, niezawodna automatyka i poręcze zabezpieczające, które uruchamiają się automatycznie w każdym miejscu sesji dla każdego programisty w zespole.
Najbardziej efektywne wzorce wyłaniające się ze społeczności w 2025 roku łączą:
- przed wykonaniem Shell dla rygorystycznych zasad bezpieczeństwa i ochrony oddziałów git
- poFileEdit dla automatycznej jakości kodu (formatowanie + linting) bez zależą od dyscypliny każdego indywidualnego programisty
- zatrzymywać się w celu uzyskania informacji zwrotnej, powiadomień i weryfikacji po wykonaniu zadania
- Haki projektowe (
.cursor/hooks.json) zobowiązał się do udostępnienia repozytorium takie same zasady w całym zespole
Naturalny kolejny krok po Hooks i MCP (protokół kontekstu modelu): jeśli Hooki pozwalają kontrolować działania agenta, MCP pozwala na połączenie agenta do zewnętrznych źródeł danych, takich jak bazy danych, interfejsy API REST i narzędzia innych firm. W artykule Następnie zobaczymy, jak skonfigurować serwery MCP z Cursorem, aby stworzyć integrujące się przepływy pracy bezpośrednio bazy danych PostgreSQL, zdalne systemy plików i zewnętrzne API.
Kolejne kroki w serii
- Poprzedni artykuł: Tryb planowania i agenci w tle — jak planować wyd równolegle wykonywać złożone zadania
- Następny artykuł: MCP i kursor: Połącz IDE z bazą danych i API - zintegruj bazy danych i usługi zewnętrzne bezpośrednio w agencie
- Powiązany: Wprowadzenie do MCP - seria poświęcona Modelowy protokół kontekstowy
- Powiązany: Tryb agenta - jak delegować złożone zadania do agenta przed skonfigurowaniem haków







