05 - Cârlige pentru cursor: Automatizați fluxul de lucru cu AI Guardrails
Există o problemă structurală în lucrul cu agenți AI autonomi: nu îi poți controla pe toți acțiune în timp real, dar nici măcar nu poți avea încredere orbește în ceea ce fac ei. Un agent care scrie cod, executa comenzi în terminal și edita fișiere în secvență automată poate fi extraordinar productiv, dar și periculos de imprevizibil dacă nu există mecanisme de control în vigoare.
Răspunsul cursorului la această problemă se numește Cârlige. Introdus cu Cursor 1,7 in 2025, sistemul Hooks vă permite să injectați logica personalizată în puncte precise din ciclul de viață al agentului: înainte de a executa o comandă shell, după ce modifică un fișier, înainte de un apel MCP, când lucrarea lui este terminată. Ei sunt, în esență, zei balustrade automate ca poti configurați o dată și care se declanșează de fiecare dată când agentul lucrează în baza de cod.
Acest articol explorează sistemul Hooks în profunzime: de la configurația de bază până la modele caracteristici avansate pentru formatare automată, validare de securitate, automatizare a testelor și sincronizare a documentației. La sfârşit veți putea construi un flux de lucru robust în care agentul AI este productiv, dar întotdeauna sub controlul dumneavoastră.
Ce veți învăța în acest articol
- Ce este sistemul Cursor's Hooks și cum diferă acesta de scripturile simple
- Tipurile de cârlig disponibile: beforeShellExecution, beforeMCPExecution, beforeReadFile, afterFileEdit, stop
- Cum să configurați hooks globale și la nivel de proiect cu hooks.json
- Cum să construiți cârlige care blochează acțiunile periculoase cu cod de ieșire și răspuns JSON
- Automatizare post-editare: formatare automată cu Prettier/ESLint prin afterFileEdit
- Opriți cârligele pentru verificarea finală și notificări la finalizarea sarcinii
- Modele practice: verificare de securitate, test automat, documentație de sincronizare
- Cum se integrează hook-urile cu Modul Agent pentru un flux de lucru complet și sigur
- Cârlige de depanare: canal de ieșire, înregistrare, depanare erori comune
- Cele mai bune practici pentru cârlige idempotente, performante și care pot fi întreținute
Poziția în serie: Cursor IDE și AI-Native Development
| # | Articol | Nivel |
|---|---|---|
| 1 | Cursor IDE: Ghid complet pentru dezvoltatori | Începător |
| 2 | Reguli cursor: Configurarea AI pentru proiectul dvs | Intermediar |
| 3 | Modul agent: Modificați baza de cod cu o comandă | Intermediar |
| 4 | Modul Plan și agenți de fundal | Avansat |
| 5 | Cârlige de cursor: automatizează fluxul de lucru (ești aici) | Intermediar |
| 6 | MCP și Cursor: Conectați IDE la baza de date și la API | Avansat |
| 7 | Depanare cu Cursor AI: de trei ori mai rapidă | Intermediar |
| 8 | Cursor vs Windsurf vs Copilot în 2026 | Începător |
| 9 | Flux de lucru profesional: Proiect unghiular cu cursor | Avansat |
Ce este un Hook în Cursor
Un cârlig în Cursor și a proces extern care rulează automat ca răspuns la evenimente specifice ciclului de viață al agentului. Când agentul este pe cale să execute o comandă shell, Cursorul vă apelează cârligul, transmițându-i informații de acțiune în JSON prin stdin. Cârligul tău poate analizați aceste informații și răspundeți cu o directivă: permiteți acțiunea, blocați-o sau cere utilizatorului confirmare.
Este esențial să înțelegeți că cârlige nu sunt simple scripturi shell care se întoarce fundal. Sunt procese sincrone integrate în ciclul agentului: agentul se oprește, așteaptă răspunsul cârlig, apoi decide cum să procedeze pe baza acelui răspuns. Acest lucru le face instrumente puternic pentru validare, auditare și automatizare.
Cârligele disponibile în Cursor 1.7+ acoperă aceste evenimente ale ciclului de viață:
- înainte de ShellExecution - Execut înainte de fiecare comandă din terminal. Primește comanda exactă pe care agentul urmează să o execute. Poate bloca comenzi periculoase, le poate înregistra sau le poate modifica.
- înainte de MCPEexecuție - Execut înainte de fiecare apel către un server MCP. El primeste numele instrumentului și parametrii de intrare. Util pentru auditarea operațiunilor de baze de date sau API.
- beforeReadFile - Execut înainte ca agentul să citească conținutul unui fișier. Vă permite să controlați ce fișiere poate accesa agentul sau masca informațiile sensibile.
- afterFileEdit - Execut după fiecare modificare a unui fișier. Primește calea fișier și modificările efectuate (șir_vechi/șir_nou). Ideal pentru formatare automată și scame.
- Stop - Se execută când agentul își încheie sarcina. Util pentru notificări, comiteri automate, sincronizare a documentației și acțiuni de curățare.
Funcția beta din Evolution
Cursor Hooks au fost introduse ca o funcție beta cu Cursor 1.7 în octombrie 2025. documentația și API-urile se pot modifica în versiunile ulterioare. Înainte de a implementa cârlige într-un mediu de producție, verificați documentația oficială la cursor.com/docs/agent/hooks pentru orice actualizare.
Configurație: hooks.json global și la nivel de proiect
Cârligele cursorului sunt configurate prin fișier hooks.json. Există două niveluri de configurație, care funcționează în mod complementar:
- Cârlige globale (
~/.cursor/hooks.json) - Se aplică tuturor deschide proiecte cu Cursor pe acea mașină. Ideal pentru politici personale de securitate, notificări sau instrumente de audit pe care le doriți întotdeauna să fie active. - Cârlige de proiect (
.cursor/hooks.jsonîn rădăcina repo) - Da acestea se aplică numai proiectului curent și sunt angajate în depozit. Ele vă permit să împărtășiți aceleasi automatizari cu toata echipa.
Când ambele fișiere există, hook-urile sunt îmbinate: hook-urile globale sunt executate mai întâi,
apoi cele ale proiectului pentru fiecare eveniment. Formatul fișierului este simplu: un obiect JSON cu a
versiune și un obiect hooks care mapează fiecare eveniment la o serie de comenzi.
Iată un exemplu complet de hooks.json care configurează hook-uri pentru toate evenimentele majore:
// .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"
}
]
}
}
Câmpul command acceptă orice comandă executabilă: scriptul Node.js, bash, Python,
binare npm. Cursorul rulează comanda ca un proces separat, transmite datele evenimentului
stdin în format JSON și citește răspunsul la cârlig stdout.
Erorile și jurnalele de diagnosticare cresc stderr.
Structura de bază a JSON pe care o trimite cursorul prin stdin la fiecare cârlig include aceste câmpuri comune:
// 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...
}
Cârlig de pre-execuție: blocați acțiunile periculoase
Cârligele înainte sunt cei mai puternici, deoarece pot bloca acțiuni înainte ca acestea să se întâmple. Cel mai imediat caz de utilizare este protejarea fișierelor sau directoarelor critice împotriva modificărilor accidentale agent sau blocați comenzile shell distructive.
Pentru tip înainte de ShellExecution, JSON primit prin stdin include câmpul
command cu comanda exactă pe care agentul urmează să o execute și câmpul cwd
cu directorul de lucru curent:
// 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"]
}
Cârligul răspunde prin stdout cu un JSON care indică dacă acțiunea ar trebui permisă sau blocată.
Câmpul cheie e permission cu valori "allow", "deny"
o "ask". Iată un cârlig Node.js care blochează comenzile potențial distructive:
// .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
});
Cu "permission": "ask", Cursorul arată utilizatorului un dialog de confirmare înainte
continuați, afișând mesajul definit în userMessage. Utilizatorul poate aproba
sau refuza actiunea. Cu "permission": "deny", acțiunea este blocată permanent
iar mesajul este afișat atât utilizatorului, cât și transmis agentului ca context.
Conectați-vă pentru a proteja anumite fișiere cu beforeReadFile
Cârligul beforeReadFile primește un câmp file_path în JSON standard.
Îl puteți folosi pentru a împiedica agentul să citească fișiere cu acreditări sau secrete sau să
mascați conținutul fișierelor sensibile înainte ca acestea să fie transmise modelului AI.
Hook afterFileEdit: Auto-Format și Auto Litting
Cârligul afterFileEdit și cel mai folosit în practica zilnică. Se executa de fiecare dată când agentul scrie sau modifică un fișier și primește un JSON cu calea fișierului prin stdin modificat și o serie de modificări cu șirurile originale și noi:
// 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"]
}
Cu aceste date, puteți rula automat formatatorul preferat pe fișierul pe care tocmai l-ați editat. Iată un cârlig complet Node.js care aplică Prettier pentru TypeScript/JavaScript și ESLint pentru remediere automat, respectând configurațiile deja prezente în proiect:
// .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 nu necesită răspuns JSON
Spre deosebire de cârlige înainte, cârligul afterFileEdit și concepute pentru efecte
colateral: formatare, lining, actualizare index. Nu ar trebui să returneze un răspuns JSON
pe stdout deoarece acțiunea (modificarea fișierului) a avut loc deja. Cârligul își face treaba și
se termina; Cursorul continuă indiferent de codul de ieșire.
Stop Hooks: Verificare finală și notificări
Cârligul Stop este executat atunci când agentul consideră sarcina sa încheiată. Nu
primește detalii despre acțiunile specifice efectuate, dar are acces la conversation_id și ai
workspace_roots. Și momentul ideal pentru acțiuni de închidere precum notificări, comite
automat, actualizarea documentației sau efectuarea de teste pe întreaga suită.
// 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"]
}
Iată două exemple practice de stop hooks: primul trimite o notificare desktop pe macOS, al doilea efectuează o verificare git a muncii produse de agent:
// .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();
Cazuri practice de utilizare pentru echipele de dezvoltare
Cârligele devin deosebit de valoroase atunci când se lucrează în echipă sau la proiecte cu cerințe riguros în calitate. Să vedem cele mai eficiente modele care apar din comunitatea Cursor în 2025.
Model 1: Audit de securitate înainte de operațiunile MCP
Dacă utilizați MCP cu baze de date sau API-uri externe, cârligul beforeMCPExecution vă permite să vă autentificați
fiecare operație înainte de a fi executată. Intrarea JSON include numele instrumentului MCP și i
parametrii exacti transmisi agentului:
// .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();
Model 2: Runner automat de testare după modificările testului
Un alt model foarte util este acela de a rula automat teste atunci când agentul modifică fișierele specificație sau test. Acest lucru asigură că modificările nu distrug suita existentă:
// .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();
Model 3: Documentație sincronizată cu baza de cod
Un model foarte util pentru documentația tehnică: atunci când agentul modifică o interfață TypeScript sau un fișier de rutare, cârligul de oprire poate verifica dacă documentația este actualizată sau raportează ce trebuie revizuit:
// .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();
Modul cârlige și agent: un sistem de control complet
Integrarea dintre Hooks și Modul Agent creează o buclă de feedback care face agentul mai sigur și fiabile în timp. Când agentul lucrează autonom la sarcini complexe, cârligele funcționează ca o plasă de siguranță care acționează la fiecare pas al ciclului de execuție.
Luați în considerare acest scenariu din lumea reală: utilizați Modul Agent pentru a refactoriza un modul Angular. Agentul efectuează zeci de acțiuni în succesiune: citește fișiere, modifică componente, actualizează rutarea, execută comenzi npm. Cu cârligele configurate corect, fiecare pas critic este interceptat:
- înainte de ShellExecution - Blocați orice comandă
npm installneaprobat sau împingeți către ramuri protejate - beforeReadFile - Împiedică agentul să citească fișiere
.envsau fișiere de configurare cu secrete - afterFileEdit - Fiecare fișier editat este formatat automat cu Mai frumos și verificat cu ESLint înainte de a trece la pasul următor
- Stop - Când agentul finalizează, primiți o notificare și un rezumat al git se modifică pentru revizuire
Mesajele agentMessage în cârlig răspunsurile sunt transmise modelului AI ca
context suplimentar. Acest lucru permite agentului să adaptează-ți comportamentul
pe baza politicilor definite: dacă un cârlig blochează o anumită acțiune cu un mesaj explicativ,
agentul poate alege o abordare alternativă fără intervenția umană.
Arhitectura unui flux de lucru complet Hooks
| Fază | Cârlig | Acţiune |
|---|---|---|
| Înainte de comenzile shell | înainte de ShellExecution | Blocați comenzile distructive, necesită confirmare pentru operațiunile git |
| Înainte de apelurile MCP | înainte de MCPEexecuție | Jurnal de audit, blocați operațiunile DB periculoase |
| Înainte de a citi fișierele | beforeReadFile | Protecție fișier .env, mascare secretă |
| După fiecare schimbare | afterFileEdit | Mai frumos, ESLint --fix, actualizare index |
| Încheiați sarcina | Stop | Notificare desktop, stare git, verificați documentația |
Cârlige de depanare: canal de ieșire și depanare
Unul dintre primele lucruri pe care le descoperi când lucrezi cu cârlige este că erorile silentioase sunt problema mai frecvente. Un cârlig care nu se lansează, un JSON incorect, un timeout netratat - toate scenariile care poate face comportamentul agentului imprevizibil. Cursorul oferă un instrument dedicat pentru a diagnostica aceste probleme.
Pentru a accesa Cârlige Canal de ieșire:
- Deschideți panoul VS Code/Cursor Output (Vizualizare > Ieșire sau
Ctrl+Shift+U) - Selectați „Cârlige” din meniul drop-down din bara panoului
- Aici veți vedea jurnalele fiecărui hook executat: ce script a fost numit, codul de ieșire și tot ceea ce a scris scriptul pentru stderr
Cele mai frecvente probleme și cum să le rezolvi:
# 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
Un model util pentru depanarea inițială și adăugarea unui cârlig de înregistrare care înregistrează fiecare eveniment pentru a depune, astfel încât să aveți o urmă completă a ceea ce a făcut agentul și în ce ordine:
// .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();
Cele mai bune practici pentru cârlige robuste și ușor de întreținut
După ce ne uităm la configurație, exemple și depanare, să ne uităm la principiile care ghidează construcția de cârlige de calitate profesională.
Principiul 1: Fail-Open sau Fail-Safe, niciodată Fail-Silent
Trebuie să decideți în mod conștient cum să vă comportați în cazul unei erori. Pentru cârlige de securitate (blocare comandă, protecție fișier), comportament corect și deseori sigur: în caz de eroare, blocați acțiunea pentru siguranță. Pentru cârlige de automatizare (formatare, scame), preferi tu fail-deschide: Dacă formatatorul nu este disponibil, lăsați acțiunea să treacă fără a bloca munca agentului. Nu alegeți niciodată fail-silent: înregistrați întotdeauna eroarea în stderr.
Principiul 2: Idempotenta
Hook-urile pot fi apelate de mai multe ori pe același fișier, de exemplu afterFileEdit
în Modul Agent care modifică același fișier în pași succesivi. Cârligele tale trebuie să fie idempotente:
rularea Prettier de două ori pe același fișier ar trebui să dea același rezultat ca și rularea acestuia o dată.
Evitați operațiunile care atașează date sau creează duplicate.
Principiul 3: Performanță și timeout-uri
Cârligele sunt sincrone în bucla de agent: un cârlig lent încetinește întregul flux de lucru. Unii ghiduri practice:
- Pentru
beforeShellExecutionebeforeReadFile: Răspundeți în 2-3 secunde - Pentru
afterFileEdit: Formatarea și scame ar trebui să se finalizeze în 10-15 secunde - Pentru
stop: aveți o marjă mai mare (30-60 de secunde), dar notificările și jurnalele trebuie să fie rapide - Setați întotdeauna un timeout pentru apeluri
execSyncpentru a evita blocarea pe termen nedeterminat
Principiul 4: Separarea responsabilităților
Preferă multe cârlige mici, concentrate, unui singur cârlig care face totul. Fiecare cârlig din dosar
hooks.json și independent: puteți adăuga, elimina sau depana fiecare fără
atingeți-i pe ceilalți. Un cârlig pentru formatare, unul pentru lining, unul pentru testare: fiecare script are
o responsabilitate clară.
Principiul 5: Siguranța cârligelor înseși
Cârligele execută cod arbitrar: sunt un posibil vector de atac dacă
hooks.json a proiectului este compromisă. Nu include niciodată secrete codificate în tare
scripturi de cârlig. Utilizați variabile de mediu sau fișiere de configurare externe depozitului.
Verificați căile fișierelor primite prin stdin înainte de a le folosi în comenzile shell pentru a preveni calea
traversare.
// 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;
}
Reguli Hooks vs Cursor: Când să folosiți Care
Le Regulile cursorului ei instruiesc agentul asupra ca scrie codul (stil, convenții, tipare de urmat). The Cârlige ei verifică Ce se întâmplă când agentul efectuează acțiuni (formatare automată, validare de securitate, notificări). Ele sunt complementare: utilizați regulile pentru a ghida generarea codului, cârlige pentru a garanta calitatea si siguranta actiunilor.
Concluzii
Sistemul Cursor's Hooks reprezintă un salt cuantic în controlul pe care îl aveți asupra agentului AI. Nu mai este vorba despre speranța că agentul face ceea ce trebuie: poți defini politici precise, automatizări fiabile și balustrade de securitate care intră în joc automat, la fiecare sesiune, pentru fiecare dezvoltator din echipă.
Cele mai eficiente modele apărute din comunitate în 2025 combină:
- înainte de ShellExecution pentru politici stricte de securitate și protecție git a ramurilor
- afterFileEdit pentru calitate automată a codului (formatare + listing) fără depinde de disciplina fiecărui dezvoltator individual
- Stop pentru feedback, notificări și verificare post-sarcină
- Cârlige de proiect (
.cursor/hooks.json) angajat în depozit pentru a partaja aceleași politici cu întreaga echipă
Următorul pas natural după Hooks și MCP (Model Context Protocol): dacă Cârligele vă permit să controlați acțiunile agentului, MCP vă permite să vă conectați agentul către surse de date externe, cum ar fi baze de date, API-uri REST și instrumente terțe. În articol În continuare vom vedea cum să configurați serverele MCP cu Cursor pentru a crea fluxuri de lucru care să se integreze direct baze de date PostgreSQL, sisteme de fișiere la distanță și API-uri externe.
Următorii pași din serie
- Articolul precedent: Modul Plan și agenți de fundal - cum să planificați ed executa sarcini complexe în paralel
- Articolul următor: MCP și Cursor: Conectați IDE la baza de date și API - integrați baze de date și servicii externe direct în agent
- Înrudit: Introducere în MCP - seria dedicată Protocolul de context model
- Înrudit: Modul agent - modul de a delega sarcini complexe către agent înainte de a configura cârligele







