05 - カーソル フック: AI ガードレールを使用してワークフローを自動化する
自律型 AI エージェントの操作には構造的な問題があります。すべてを制御することはできません。 リアルタイムのアクションですが、彼らの行動を盲目的に信頼することさえできません。書くエージェント コードを作成し、ターミナルでコマンドを実行し、自動シーケンスでファイルを編集することは、非常に簡単です。 生産的ですが、制御メカニズムが整備されていない場合は危険なほど予測不可能です。
この問題に対するカーソルの答えは次のように呼ばれます。 フック。 Cursor 1.7 で導入されました 2025 年、フック システムにより、ライフサイクルの正確な時点でカスタム ロジックを挿入できるようになります。 エージェントの: シェル コマンドを実行する前、ファイルを変更した後、MCP 呼び出しの前、 彼の仕事が終わったとき。彼らは本質的に神です 自動ガードレール できること 一度設定すれば、コードベースでエージェントが動作するたびに起動されます。
この記事では、基本構成からパターンまでフック システムを詳しく説明します。 自動フォーマット、セキュリティ検証、テストの自動化、ドキュメントの同期などの高度な機能。最後に AI エージェントが生産的でありながら常に制御下にある堅牢なワークフローを構築できるようになります。
この記事で学べること
- Cursor のフック システムとは何ですか?また、単純なスクリプトとの違いは何ですか?
- 使用可能なフックのタイプ: beforeShellExecution、beforeMCPExecution、beforeReadFile、afterFileEdit、stop
- hooks.json を使用してグローバルおよびプロジェクト全体のフックを設定する方法
- 終了コードと JSON 応答を使用して危険なアクションをブロックするフックを構築する方法
- 編集後の自動化: afterFileEdit を介した Prettier/ESLint による自動フォーマット
- 最終検証とタスク完了時の通知のためにフックを停止します。
- 実用的なパターン: セキュリティ チェック、自動テスト ランナー、ドキュメントの同期
- フックをエージェント モードと統合して完全で安全なワークフローを実現する方法
- デバッグフック: 出力チャネル、ロギング、一般的なエラーのトラブルシューティング
- 冪等でパフォーマンスが高く、保守しやすいフックのベスト プラクティス
シリーズ内での位置づけ: カーソル IDE と AI ネイティブ開発
| # | アイテム | レベル |
|---|---|---|
| 1 | カーソル IDE: 開発者向け完全ガイド | 初心者 |
| 2 | カーソル ルール: プロジェクトの AI の構成 | 中級 |
| 3 | エージェント モード: コマンドを使用してコードベースを変更する | 中級 |
| 4 | プランモードとバックグラウンドエージェント | 高度な |
| 5 | カーソルフック: ワークフローを自動化する (ここまでです) | 中級 |
| 6 | MCP とカーソル: IDE をデータベースと API に接続 | 高度な |
| 7 | Cursor AI を使用したデバッグ: 3 倍高速 | 中級 |
| 8 | 2026 年のカーソル vs ウィンドサーフィン vs 副操縦士 | 初心者 |
| 9 | プロフェッショナルなワークフロー: カーソルを使用した Angular プロジェクト | 高度な |
カーソルのフックとは何ですか
Cursor のフックと 外部プロセス 応答して自動的に実行されます 特定のエージェントのライフサイクル イベントに適用されます。エージェントがシェル コマンドを実行しようとしているとき、 カーソルはフックを呼び出し、標準入力経由でアクション情報を JSON で渡します。あなたのフックは、 この情報を分析し、アクションを許可するかブロックするかという指示で応答します。 ユーザーに確認を求めます。
フックを理解することが不可欠です これらは単純なシェルスクリプトではありません それが入る 背景。これらはエージェント サイクルに統合された同期プロセスです。エージェントは停止し、完了するまで待機します。 フック応答を返し、その応答に基づいて続行方法を決定します。これは彼らをツールにします 検証、監査、自動化に強力です。
Cursor 1.7 以降で使用できるフックは、次のライフサイクル イベントをカバーします。
- シェル実行前 - ターミナル内のすべてのコマンドの前に実行されます。 Receives the エージェントが実行しようとしている正確なコマンド。危険なコマンドをブロックしたり、ログに記録したり、変更したりできます。
- MCP実行前 - MCP サーバーへのすべての呼び出しの前に実行されます。彼は受け取ります ツールの名前と入力パラメータ。データベースまたは API 操作の監査に役立ちます。
- beforeReadFile - エージェントがファイルの内容を読み取る前に実行されます。 エージェントがアクセスできるファイルを制御したり、機密情報をマスクしたりできます。
- ファイル編集後 - ファイルが変更されるたびに実行されます。のパスを受け取ります。 ファイルと行われた変更 (old_string/new_string)。自動フォーマットとリンティングに最適です。
- 停止 - エージェントがタスクを終了したときに実行されます。通知に便利な、 自動コミット、ドキュメントの同期、クリーンアップアクション。
Evolution のベータ版機能
カーソル フックは、2025 年 10 月に Cursor 1.7 のベータ機能として導入されました。 ドキュメントと API は今後のリリースで変更される可能性があります。フックを実装する前に 運用環境では、次の公式ドキュメントを確認してください。 カーソル.com/docs/agent/hooks 更新情報については。
構成: グローバルおよびプロジェクトレベルのhooks.json
カーソルフックはファイル経由で設定されます フック.json。 2つのレベルがあります これらは補完的に機能します。
- グローバルフック (
~/.cursor/hooks.json) - すべてに適用されます そのマシン上でカーソルを使用してプロジェクトを開きます。個人のセキュリティ ポリシーや通知に最適 または、常にアクティブにしておきたい監査ツール。 - プロジェクトフック (
.cursor/hooks.jsonリポジトリのルート内) - はい これらは現在のプロジェクトにのみ適用され、リポジトリにコミットされます。共有できるようになります チーム全体で同じ自動化を行います。
両方のファイルが存在する場合、フックはマージされます。グローバル フックが最初に実行され、
次に各イベントのプロジェクトのもの。ファイル形式は単純です。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...
}
実行前フック: 危険なアクションをブロック
フック 前に これらは、アクションが発生する前にブロックできるため、最も強力です。 最も直接的な使用例は、重要なファイルまたはディレクトリを誤って変更されないように保護することです。 エージェント、または破壊的なシェル コマンドをブロックします。
男のために シェル実行前、標準入力経由で受信した 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 を標準出力経由で応答します。
キーフィールド 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
});
Con "permission": "ask", カーソルはユーザーに確認ダイアログを表示する前に
で定義されたメッセージを表示して続行します。 userMessage。ユーザーが承認できる
またはその行為を拒否します。と "permission": "deny"、アクションは永久にブロックされます
メッセージはユーザーに表示されるとともに、コンテキストとしてエージェントに渡されます。
beforeReadFile で特定のファイルを保護するフック
フック beforeReadFile フィールドを受け取ります file_path 標準入力 JSON 内。
これを使用すると、エージェントが資格情報やシークレットを含むファイルを読み取らないようにしたり、
機密ファイルの内容を AI モデルに渡す前にマスクします。
ファイル編集後のフック: 自動フォーマットと自動リンティング
フック ファイル編集後 そして日常の練習で最もよく使われます。実行される エージェントがファイルの書き込みまたは変更を行い、標準入力経由でファイル パスを含む 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"]
}
このデータを使用すると、編集したばかりのファイルに対してお気に入りのフォーマッタを自動的に実行できます。 これは、Prettier for TypeScript/JavaScript と 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 効果を考慮して設計されています
付随物: フォーマット、lint、インデックスの更新。 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"]
}
ここでは停止フックの 2 つの実際的な例を示します。1 つ目は macOS 上でデスクトップ通知を送信し、2 つ目は エージェントによって生成された作業の 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 年に Cursor コミュニティから出現する最も効果的なパターンを見てみましょう。
パターン 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: テスト変更後の自動テスト ランナー
もう 1 つの非常に便利なパターンは、エージェントがファイルを変更するときにテストを自動的に実行することです。 仕様とかテストとか。これにより、変更によって既存のスイートが破壊されないことが保証されます。
// .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コマンド。フックが正しく設定されていると、すべての重要なステップがインターセプトされます。
- シェル実行前 - あらゆるコマンドをブロックします
npm install承認されていません または保護されたブランチにプッシュします - beforeReadFile - エージェントによるファイルの読み取りを防止します。
.envまたはシークレットを含む設定ファイル - ファイル編集後 - 編集された各ファイルは自動的にフォーマットされます。 次のステップに進む前に、よりきれいになり、ESLint でチェックします
- 停止 - エージェントが完了すると、通知と概要が届きます。 git の変更をレビューする
メッセージ agentMessage フック内の応答は次のように AI モデルに渡されます。
追加のコンテキスト。これにより、エージェントは次のことが可能になります。 自分の行動を適応させる
定義されたポリシーに基づいて、フックが説明メッセージとともに特定のアクションをブロックする場合、
エージェントは人間の介入なしに別のアプローチを選択できます。
完全なフック ワークフローのアーキテクチャ
| 段階 | フック | アクション |
|---|---|---|
| シェルコマンドの前 | シェル実行前 | 破壊的なコマンドをブロックし、git 操作には確認が必要です |
| MCP コールの前に | MCP実行前 | 監査ログ、危険なDB操作をブロック |
| ファイルを読み取る前に | beforeReadFile | .env ファイル保護、シークレットマスキング |
| 変更するたびに | ファイル編集後 | Prettier、ESLint --fix、インデックス更新 |
| タスクの終了 | 停止 | デスクトップ通知、git ステータス、ドキュメントの確認 |
デバッグフック: 出力チャンネルとトラブルシューティング
フックを使用するときに最初に気づくことの 1 つは、サイレント エラーが問題であるということです。 より一般的です。起動しないフック、不正な形式の JSON、未処理のタイムアウト - すべてのシナリオ エージェントの動作が予測不能になる可能性があります。カーソルは専用ツールを提供します これらの問題を診断します。
にアクセスするには、 フック出力チャンネル:
- VS コード/カーソル出力パネルを開きます ([表示] > [出力] または
Ctrl+Shift+U) - パネルバーのドロップダウンメニューから「フック」を選択します。
- ここには、実行されたすべてのフックのログ (どのスクリプトが呼び出されたか、終了コードなど) が表示されます。 スクリプトが標準エラー出力に書き込んだもの
最も一般的な問題とその解決方法:
# 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: フェールオープンまたはフェールセーフ、決してフェールサイレントではない
エラーが発生した場合にどのように動作するかを意識的に決定する必要があります。セキュリティフック用 (コマンド ロック、ファイル保護)、正しい動作、および多くの場合 フェイルセーフ: エラーが発生した場合は、安全のためにアクションをブロックします。自動化フック (フォーマット、lint) の場合、 あなたが好む フェールオープン: フォーマッタが使用できない場合は、アクションを通過させます。 エージェントの作業を妨げることなく。決してフェイルサイレントを選択しないでください。常にエラーを標準エラー出力に記録します。
原則 2: 冪等性
フックは同じファイル上で複数回呼び出すことができます。 afterFileEdit
エージェント モードでは、連続する手順で同じファイルを変更します。フックは冪等である必要があります。
同じファイルに対して Prettier を 2 回実行すると、1 回実行した場合と同じ結果が得られます。
データを追加したり、重複を作成したりする操作は避けてください。
原則 3: パフォーマンスとタイムアウト
フックはエージェント ループ内で同期します。フックが遅いと、ワークフロー全体が遅くなります。一部 実践的なガイドライン:
- のために
beforeShellExecutionebeforeReadFile:2~3秒以内に返信 - のために
afterFileEdit: フォーマットとリンティングは 10 ~ 15 秒以内に完了します。 - のために
stop: 余裕はありますが (30 ~ 60 秒)、通知とログは高速でなければなりません - 通話には常にタイムアウトを設定する
execSync無期限のブロックを避けるために
原則 4: 責任の分離
すべてを行う 1 つのフックよりも、小さくて焦点を絞った多数のフックを使用することをおすすめします。ファイル内のすべてのフック
hooks.json 独立しています: それぞれを追加、削除、デバッグすることができます。
他人に触れること。フォーマット用のフックが 1 つ、リンティング用のフックが 1 つ、テスト用のフックが 1 つ: 各スクリプトには
明確な責任。
原則 5: フック自体の安全性
フックは任意のコードを実行します。フックは、次の場合に攻撃ベクトルとなる可能性があります。
hooks.json プロジェクトの内容が侵害されます。ハードコードされたシークレットを決して含めないでください。
フックスクリプト。リポジトリの外部にある環境変数または構成ファイルを使用します。
パスが失われるのを防ぐために、標準入力経由で受信したファイルをシェルコマンドで使用する前にそのパスを確認してください。
横断。
// 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;
}
フックとカーソル ルール: いつどちらを使用するか
Le カーソルのルール 彼らはエージェントに次のように指示します として コードを書く (スタイル、 慣例、従うべきパターン)。の フック 彼らはチェックします cosa それは起こります エージェントがアクション (自動フォーマット、セキュリティ検証、通知) を実行するとき。 これらは補完的です。ルールを使用してコード生成をガイドし、フックを使用して保証します。 アクションの品質と安全性。
結論
カーソルのフック システムは、AI エージェントに対する制御の飛躍的な進歩を表します。 もはや、エージェントが正しいことをすることを期待する必要はありません。正確なポリシーを定義できます。 信頼性の高い自動化とセキュリティ ガードレールが、いつでも自動的に機能します。 チームの各開発者向けのセッション。
2025 年にコミュニティから出現する最も効果的なパターンは、次の組み合わせです。
- シェル実行前 厳格なセキュリティ ポリシーと git ブランチ保護のため
- ファイル編集後 自動コード品質 (フォーマット + リンティング) を行わずに 個々の開発者の専門分野に依存します
- 停止 フィードバック、通知、タスク後の検証用
- プロジェクトフック (
.cursor/hooks.json) 共有するためにリポジトリにコミット チーム全体で同じポリシーを持っている
フックと MCP (モデル コンテキスト プロトコル): 場合 フックを使用するとエージェントのアクションを制御でき、MCP を使用すると接続が可能になります。 エージェントからデータベース、REST API、サードパーティ ツールなどの外部データ ソースに接続します。記事の中で 次に、Cursor を使用して MCP サーバーを構成し、統合するワークフローを作成する方法を見ていきます。 PostgreSQL データベース、リモート ファイルシステム、外部 API に直接接続できます。







