05 - 커서 후크: AI Guardrails로 작업 흐름 자동화
자율 AI 에이전트로 작업하는 데에는 구조적 문제가 있습니다. 모든 단일 에이전트를 제어할 수는 없습니다. 실시간 행동을 취하지만 그들이 하는 일을 맹목적으로 믿을 수도 없습니다. 글을 쓰는 대리인 코드를 작성하고, 터미널에서 명령을 실행하고, 자동 순서로 파일을 편집하는 것은 매우 특별할 수 있습니다. 생산적이지만 제어 메커니즘이 없으면 위험할 정도로 예측할 수 없습니다.
이 문제에 대한 커서의 대답은 다음과 같습니다. 후크. Cursor 1.7 in 도입 2025년, Hooks 시스템을 사용하면 수명 주기의 정확한 지점에 사용자 정의 논리를 주입할 수 있습니다. 에이전트의 경우: 쉘 명령을 실행하기 전, 파일을 수정한 후, MCP 호출 전, 그의 일이 끝나면. 그들은 본질적으로 신들이다. 자동 난간 당신이 할 수 있다는 한 번 구성하면 에이전트가 코드베이스에서 작동할 때마다 실행됩니다.
이 글에서는 기본 구성부터 패턴까지 Hooks 시스템을 심층적으로 살펴봅니다. 자동 서식 지정, 보안 검증, 테스트 자동화 및 문서 동기화를 위한 고급 기능입니다. 마지막에 AI 에이전트가 생산적이지만 항상 제어할 수 있는 강력한 워크플로를 구축할 수 있습니다.
이 기사에서 배울 내용
- Cursor's Hooks 시스템은 무엇이며 간단한 스크립트와 어떻게 다른가요?
- 사용 가능한 후크 유형: beforeShellExecution, beforeMCPExecution, beforeReadFile, afterFileEdit, stop
- Hooks.json을 사용하여 전역 및 프로젝트 전체 후크를 설정하는 방법
- 종료 코드 및 JSON 응답을 사용하여 위험한 작업을 차단하는 후크를 구축하는 방법
- 사후 편집 자동화: afterFileEdit를 통해 Prettier/ESLint로 자동 포맷
- 작업 완료 시 최종 확인 및 알림을 위한 후크 중지
- 실용적인 패턴: 보안 검사, 자동 테스트 실행기, 동기화 문서
- 완전하고 안전한 작업 흐름을 위해 후크가 에이전트 모드와 통합되는 방법
- 디버깅 후크: 출력 채널, 로깅, 일반적인 오류 문제 해결
- 멱등성, 성능 및 유지 관리가 가능한 후크에 대한 모범 사례
시리즈 내 위치: 커서 IDE 및 AI 기반 개발
| # | Articolo | 수준 |
|---|---|---|
| 1 | 커서 IDE: 개발자를 위한 전체 가이드 | 초보자 |
| 2 | 커서 규칙: 프로젝트에 대한 AI 구성 | 중급 |
| 3 | 에이전트 모드: 명령으로 코드베이스 수정 | 중급 |
| 4 | 계획 모드 및 백그라운드 에이전트 | 고급의 |
| 5 | 커서 후크: 작업 흐름 자동화(현재 위치) | 중급 |
| 6 | MCP 및 커서: IDE를 데이터베이스 및 API에 연결 | 고급의 |
| 7 | Cursor AI를 사용한 디버깅: 3배 더 빨라짐 | 중급 |
| 8 | 2026년 커서 vs 윈드서핑 vs 코파일럿 | 초보자 |
| 9 | 전문적인 작업 흐름: 커서가 있는 Angular 프로젝트 | 고급의 |
커서의 후크란 무엇입니까?
커서의 후크와 외부 프로세스 응답하여 자동으로 실행됩니다. 특정 에이전트 수명주기 이벤트에 적용됩니다. 에이전트가 쉘 명령을 실행하려고 할 때 커서는 stdin을 통해 JSON의 작업 정보를 전달하는 후크를 호출합니다. 당신의 후크는 이 정보를 분석하고 지시문으로 대응합니다. 즉, 작업을 허용하거나 차단하거나 사용자에게 확인을 요청합니다.
후크를 이해하는 것이 중요합니다. 단순한 쉘 스크립트가 아닙니다 그 차례 배경입니다. 이는 에이전트 주기에 통합된 동기 프로세스입니다. 즉, 에이전트는 중지하고 다음을 기다립니다. 후크 응답을 받은 다음 해당 응답에 따라 진행 방법을 결정합니다. 이것은 그들을 도구로 만든다 검증, 감사 및 자동화에 강력합니다.
Cursor 1.7+에서 사용 가능한 후크는 다음 수명 주기 이벤트를 다룹니다.
- beforeShellExecution - 터미널의 모든 명령 전에 실행됩니다. 수신 에이전트가 실행하려는 정확한 명령입니다. 위험한 명령을 차단하거나 기록하거나 수정할 수 있습니다.
- MCP실행 전 - MCP 서버에 대한 모든 호출 전에 실행됩니다. 그는 받는다 도구 이름과 입력 매개변수. 데이터베이스 또는 API 작업을 감사하는 데 유용합니다.
- 이전읽기파일 - 에이전트가 파일의 내용을 읽기 전에 실행됩니다. 에이전트가 민감한 정보에 액세스하거나 마스킹할 수 있는 파일을 제어할 수 있습니다.
- afterFileEdit - 파일이 변경될 때마다 실행됩니다. 의 경로를 받습니다. 파일 및 변경 사항(old_string/new_string). 자동 서식 지정 및 Linting에 이상적입니다.
- 멈추다 - 에이전트가 작업을 완료하면 실행됩니다. 알림에 유용합니다. 자동 커밋, 문서 동기화 및 정리 작업.
Evolution의 베타 기능
Cursor Hooks는 2025년 10월 Cursor 1.7에서 베타 기능으로 도입되었습니다. 설명서와 API는 후속 릴리스에서 변경될 수 있습니다. Hook을 구현하기 전에 프로덕션 환경에서는 공식 문서를 확인하세요. cursor.com/docs/agent/hooks 모든 업데이트에 대해.
구성: 전역 및 프로젝트 수준 Hooks.json
커서 후크는 파일을 통해 구성됩니다. Hooks.json. 두 가지 수준이 있습니다. 보완적인 방식으로 작동하는 구성:
- 글로벌 후크 (
~/.cursor/hooks.json) - 모든 사람에게 적용됩니다. 해당 컴퓨터에서 커서로 프로젝트를 엽니다. 개인 보안 정책, 알림에 적합 또는 항상 활성화하려는 감사 도구. - 프로젝트 후크 (
.cursor/hooks.json저장소 루트에서) - 예 이는 현재 프로젝트에만 적용되며 저장소에 커밋됩니다. 그들은 당신이 공유할 수 있도록 허용합니다 팀 전체에 동일한 자동화가 적용됩니다.
두 파일이 모두 존재하면 후크가 병합됩니다. 전역 후크가 먼저 실행되고,
그런 다음 각 이벤트에 대한 프로젝트의 내용입니다. 파일 형식은 간단합니다.
버전과 객체 hooks 각 이벤트를 명령 배열에 매핑합니다.
다음은 완전한 예입니다. hooks.json 모든 주요 이벤트에 대한 후크를 구성합니다.
// .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"
}
]
}
}
분야 command Node.js 스크립트, bash, Python 등 모든 실행 가능한 명령을 허용합니다.
npm 바이너리. 커서는 명령을 별도의 프로세스로 실행하고 이벤트 데이터를 전달합니다.
표준입력 JSON 형식으로, 후크 응답을 읽습니다. 표준 출력.
오류 및 진단 로그가 올라갑니다. 표준 오류.
Cursor가 stdin을 통해 각 후크에 전송하는 JSON의 기본 구조에는 다음과 같은 공통 필드가 포함됩니다.
// 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...
}
실행 전 후크: 위험한 작업 차단
후크 ~ 전에 행동이 일어나기 전에 차단할 수 있기 때문에 가장 강력합니다. 가장 즉각적인 사용 사례는 중요한 파일이나 디렉터리가 실수로 수정되지 않도록 보호하는 것입니다. 에이전트 또는 파괴적인 쉘 명령을 차단합니다.
그 남자를 위해 beforeShellExecution, stdin을 통해 수신된 JSON에는 다음 필드가 포함됩니다.
command 에이전트가 실행하려는 정확한 명령과 필드 cwd
현재 작업 디렉토리:
// 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"]
}
후크는 작업이 허용되어야 하는지 차단되어야 하는지를 나타내는 JSON으로 stdout을 통해 응답합니다.
키 필드 e permission 가치가 있는 "allow", "deny"
o "ask". 잠재적으로 파괴적인 명령을 차단하는 Node.js 후크는 다음과 같습니다.
// .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
});
와 함께 "permission": "ask", 커서는 사용자에게 확인 대화 상자를 표시합니다.
계속해서 다음에 정의된 메시지를 표시합니다. userMessage. 사용자가 승인할 수 있음
아니면 그 행동을 거부하세요. 와 함께 "permission": "deny", 해당 작업이 영구적으로 차단되었습니다.
메시지는 사용자 모두에게 표시되고 컨텍스트로 에이전트에 전달됩니다.
beforeReadFile을 사용하여 특정 파일을 보호하는 후크
후크 beforeReadFile 필드를 받습니다 file_path stdin JSON에서.
이를 사용하여 에이전트가 자격 증명이나 비밀이 포함된 파일을 읽지 못하도록 할 수 있습니다.
AI 모델에 전달되기 전에 민감한 파일의 내용을 마스킹합니다.
FileEdit 후 후크: 자동 서식 및 자동 Linting
후크 afterFileEdit 일상적인 연습에서 가장 많이 사용됩니다. 실행된다 에이전트가 파일을 쓰거나 수정할 때마다 stdin을 통해 파일 경로가 포함된 JSON을 수신합니다. 수정된 내용과 원본 및 새 문자열의 변경 사항 배열:
// 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"]
}
이 데이터를 사용하면 방금 편집한 파일에 대해 자주 사용하는 포맷터를 자동으로 실행할 수 있습니다. 다음은 TypeScript/JavaScript에 Prettier를 적용하고 수정을 위해 ESLint를 적용하는 완전한 Node.js 후크입니다. 프로젝트에 이미 존재하는 구성을 고려하여 자동:
// .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에는 JSON 응답이 필요하지 않습니다.
후크와 달리 ~ 전에, 후크 afterFileEdit 효과를 위해 설계되었습니다.
보조 자료: 서식 지정, 린트, 색인 업데이트. JSON 응답을 반환하면 안 됩니다.
작업(파일 수정)이 이미 발생했기 때문에 stdout에서 발생합니다. 후크는 제 역할을 하고
끝; 종료 코드에 관계없이 커서가 계속됩니다.
후크 중지: 최종 확인 및 알림
후크 멈추다 에이전트가 작업이 완료된 것으로 간주하면 실행됩니다. 아니다
수행된 특정 작업에 대한 세부 정보를 받지만 다음 항목에 액세스할 수 있습니다. conversation_id 그리고 아이
workspace_roots. 알림, 커밋과 같은 작업을 종료하기에 이상적인 시간입니다.
자동, 문서 업데이트 또는 전체 제품군에 대한 테스트 실행.
// 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"]
}
다음은 중지 후크의 두 가지 실제 예입니다. 첫 번째는 macOS에 데스크톱 알림을 보내고 두 번째는 에이전트가 생성한 작업에 대해 git 확인을 수행합니다.
// .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();
개발팀을 위한 실제 사용 사례
후크는 팀으로 작업하거나 요구 사항이 있는 프로젝트에서 작업할 때 특히 유용합니다. 품질이 엄격합니다. 2025년 커서 커뮤니티에서 나타나는 가장 효과적인 패턴을 살펴보겠습니다.
패턴 1: MCP 작업 전 보안 감사
데이터베이스 또는 외부 API와 함께 MCP를 사용하는 경우 후크 beforeMCPExecution 로그인 할 수 있습니다
각 작업이 실행되기 전에. 입력 JSON에는 MCP 도구 이름과 i가 포함됩니다.
에이전트에 전달된 정확한 매개변수:
// .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();
패턴 2: 테스트 변경 후 자동 테스트 실행기
또 다른 매우 유용한 패턴은 에이전트가 파일을 수정할 때 자동으로 테스트를 실행하는 것입니다. 사양이나 테스트. 이렇게 하면 변경 사항으로 인해 기존 제품군이 중단되지 않습니다.
// .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();
패턴 3: 코드베이스와 동기화된 문서
기술 문서에 매우 유용한 패턴: 에이전트가 TypeScript 인터페이스를 수정하는 경우 또는 라우팅 파일을 사용하면 스톱 후크는 문서가 최신인지 또는 보고하는지 확인할 수 있습니다. 수정이 필요한 것:
// .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();
후크 및 에이전트 모드: 완벽한 제어 시스템
후크와 에이전트 모드의 통합은 에이전트를 더욱 안전하고 효율적으로 만드는 피드백 루프를 생성합니다. 시간이 지나도 신뢰할 수 있습니다. 에이전트가 복잡한 작업을 자율적으로 수행할 때 후크가 작동합니다. 실행 주기의 모든 단계에서 작동하는 안전망과 같습니다.
다음 실제 시나리오를 고려하십시오. 에이전트 모드를 사용하여 Angular 모듈을 리팩터링하고 있습니다. 대리인 파일 읽기, 구성 요소 수정, 라우팅 업데이트, 실행 등 수십 가지 작업을 순서대로 수행합니다. npm 명령. 후크가 올바르게 구성되면 모든 중요한 단계가 차단됩니다.
- beforeShellExecution - 모든 명령 차단
npm install승인되지 않음 또는 보호된 분기로 푸시 - 이전읽기파일 - 에이전트가 파일을 읽지 못하도록 방지합니다.
.env또는 비밀이 포함된 구성 파일 - afterFileEdit - 편집된 각 파일은 다음과 같이 자동으로 포맷됩니다. 다음 단계로 넘어가기 전에 ESLint로 더 예쁘고 확인해보세요
- 멈추다 - 에이전트가 완료되면 알림과 요약을 받습니다. git에서 검토할 내용 변경
메시지 agentMessage 후크 응답은 다음과 같이 AI 모델에 전달됩니다.
추가 컨텍스트. 이를 통해 에이전트는 다음을 수행할 수 있습니다. 당신의 행동을 적응
정의된 정책에 따라: 후크가 설명 메시지와 함께 특정 작업을 차단하는 경우,
에이전트는 사람의 개입 없이 대체 접근 방식을 선택할 수 있습니다.
완전한 Hooks 워크플로의 아키텍처
| 단계 | Hook | 행동 |
|---|---|---|
| 쉘 명령 이전 | beforeShellExecution | 파괴적인 명령을 차단하고 Git 작업에 대한 확인이 필요합니다. |
| MCP 호출 전 | MCP실행 전 | 감사 로그, 위험한 DB 작업 차단 |
| 파일을 읽기 전에 | 이전읽기파일 | .env 파일 보호, 비밀 마스킹 |
| 모든 변경 후 | afterFileEdit | 더 예뻐지고, ESLint --fix, 인덱스 업데이트 |
| 작업 종료 | 멈추다 | 데스크탑 알림, git 상태, 문서 확인 |
디버깅 후크: 출력 채널 및 문제 해결
후크를 사용하여 작업할 때 가장 먼저 발견하는 것 중 하나는 자동 오류가 문제라는 것입니다. 더 일반적입니다. 실행되지 않는 후크, 잘못된 JSON, 처리되지 않은 시간 초과 등 모든 시나리오는 에이전트의 행동을 예측할 수 없게 만들 수 있습니다. 커서는 전용 도구를 제공합니다 이러한 문제를 진단합니다.
액세스하려면 후크 출력 채널:
- VS 코드/커서 출력 패널을 엽니다(보기 > 출력 또는
Ctrl+Shift+U) - 패널 표시줄의 드롭다운 메뉴에서 "후크"를 선택합니다.
- 여기서는 실행된 모든 후크의 로그를 볼 수 있습니다. 즉, 호출된 스크립트, 종료 코드 및 모든 것 스크립트가 stderr에 쓴 것
가장 일반적인 문제와 해결 방법:
# 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
초기 디버깅 및 각 이벤트를 기록하는 로깅 후크 추가에 유용한 패턴 그러면 에이전트가 수행한 작업과 순서에 대한 완전한 추적이 가능합니다.
// .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();
견고하고 유지 관리가 가능한 후크에 대한 모범 사례
구성, 예제, 디버깅을 살펴본 후 구성을 안내하는 원칙을 살펴보겠습니다. 전문적인 품질의 후크입니다.
원칙 1: 페일오픈(Fail-Open) 또는 페일세이프(Fail-Safe), 절대 페일 사일런트(Fail-Silent)가 아님
오류 발생 시 어떻게 행동할지 의식적으로 결정해야 합니다. 보안 후크용 (명령 잠금, 파일 보호), 올바른 동작 및 자주 안전 장치: 오류가 발생한 경우 안전을 위해 해당 작업을 차단하세요. 자동화 후크(포맷, 린팅)의 경우, 당신은 선호 페일오픈: 포맷터를 사용할 수 없는 경우 작업을 통과시키세요. 에이전트의 작업을 차단하지 않고. 절대 실패 침묵을 선택하지 마십시오. 항상 오류를 stderr에 기록하십시오.
원칙 2: 멱등성
Hooks는 동일한 파일에서 여러 번 호출될 수 있습니다. afterFileEdit
연속적인 단계에서 동일한 파일을 수정하는 에이전트 모드에서. 후크는 멱등원이어야 합니다.
동일한 파일에서 Prettier를 두 번 실행하면 한 번 실행하는 것과 동일한 결과가 나타납니다.
데이터를 추가하거나 중복 항목을 생성하는 작업을 피하세요.
원칙 3: 성능 및 시간 초과
후크는 에이전트 루프에서 동기식입니다. 느린 후크는 전체 워크플로 속도를 저하시킵니다. 일부 실용적인 지침:
- 을 위한
beforeShellExecutionebeforeReadFile: 2~3초 이내에 답변 - 을 위한
afterFileEdit: 포맷 및 린팅이 10~15초 내에 완료되어야 합니다. - 을 위한
stop: 더 많은 여유(30~60초)가 있지만 알림과 로그는 빨라야 합니다. - 항상 통화 시간 제한을 설정하세요.
execSync무기한 차단을 피하기 위해
원칙 4: 책임 분리
모든 것을 수행하는 단일 후크보다 작고 집중된 여러 개의 후크를 선호하십시오. 파일의 모든 후크
hooks.json 독립적: 별도의 작업 없이 각각을 추가, 제거 또는 디버깅할 수 있습니다.
다른 사람을 만지십시오. 서식 지정을 위한 후크 하나, 린팅용 후크 하나, 테스트용 후크: 각 스크립트에는
분명한 책임.
원칙 5: 후크 자체의 안전성
후크는 임의의 코드를 실행합니다. 다음과 같은 경우 공격 벡터가 될 수 있습니다.
hooks.json 프로젝트의 일부가 손상되었습니다. 하드코딩된 비밀을 절대로 포함하지 마세요.
후크 스크립트. 저장소 외부의 환경 변수 또는 구성 파일을 사용하십시오.
경로를 방지하기 위해 쉘 명령에서 사용하기 전에 stdin을 통해 수신된 파일의 경로를 확인하십시오.
순회.
// 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;
}
Hooks vs Cursor 규칙: 언제 어느 것을 사용해야 할까요?
Le 커서 규칙 그들은 상담원에게 다음과 같이 지시합니다. ~처럼 코드 작성(스타일, 규칙, 따라야 할 패턴). 그만큼 후크 그들은 확인한다 무엇 그런 일이 일어난다 에이전트가 작업(자동 서식 지정, 보안 유효성 검사, 알림)을 수행할 때. 상호 보완적입니다. 규칙을 사용하여 코드 생성을 안내하고 후크를 사용하여 보장합니다. 행동의 질과 안전.
결론
Cursor's Hooks 시스템은 AI 에이전트에 대한 제어 능력의 비약적인 발전을 나타냅니다. 더 이상 에이전트가 올바른 일을 하기를 바라는 것이 아닙니다. 정확한 정책을 정의할 수 있고, 매 순간 자동으로 작동하는 안정적인 자동화 및 보안 가드레일 팀의 각 개발자에 대한 세션입니다.
2025년에 커뮤니티에서 나타나는 가장 효과적인 패턴은 다음과 같습니다.
- beforeShellExecution 엄격한 보안 정책과 git 브랜치 보호를 위해
- afterFileEdit 자동 코드 품질(포맷 + 린팅)을 위해 각 개별 개발자의 규율에 따라 다름
- 멈추다 피드백, 알림 및 작업 후 확인을 위해
- 프로젝트 후크(
.cursor/hooks.json) 공유하기 위해 저장소에 커밋됨 팀 전체와 동일한 정책
Hooks와 the Hooks 이후의 자연스러운 다음 단계 MCP(모델 컨텍스트 프로토콜): 만약에 후크를 사용하면 에이전트의 작업을 제어할 수 있고 MCP를 사용하면 연결을 수행할 수 있습니다. 데이터베이스, REST API 및 타사 도구와 같은 외부 데이터 소스에 대한 에이전트입니다. 기사에서 다음으로 커서를 사용하여 MCP 서버를 구성하여 통합 워크플로를 만드는 방법을 살펴보겠습니다. PostgreSQL 데이터베이스, 원격 파일 시스템 및 외부 API를 직접 사용합니다.







