From 3103a8da1ba3da9a3d01165cf86c6ace834222c9 Mon Sep 17 00:00:00 2001 From: duthaho Date: Sun, 19 Apr 2026 12:11:56 +0700 Subject: [PATCH] feat: add hooks and rules --- .claude/hooks/auto-format.cjs | 42 +++++++++++++++++++++ .claude/hooks/block-dangerous-commands.cjs | 38 +++++++++++++++++++ .claude/hooks/notify.cjs | 40 ++++++++++++++++++++ .claude/rules/api.md | 16 ++++++++ .claude/rules/frontend.md | 17 +++++++++ .claude/rules/migrations.md | 14 +++++++ .claude/rules/security.md | 12 ++++++ .claude/rules/testing.md | 19 ++++++++++ .claude/settings.json | 43 ++++++++++++++++++---- 9 files changed, 233 insertions(+), 8 deletions(-) create mode 100644 .claude/hooks/auto-format.cjs create mode 100644 .claude/hooks/block-dangerous-commands.cjs create mode 100644 .claude/hooks/notify.cjs create mode 100644 .claude/rules/api.md create mode 100644 .claude/rules/frontend.md create mode 100644 .claude/rules/migrations.md create mode 100644 .claude/rules/security.md create mode 100644 .claude/rules/testing.md diff --git a/.claude/hooks/auto-format.cjs b/.claude/hooks/auto-format.cjs new file mode 100644 index 0000000..d564881 --- /dev/null +++ b/.claude/hooks/auto-format.cjs @@ -0,0 +1,42 @@ +#!/usr/bin/env node +/** + * PostToolUse hook: auto-formats files after Write or Edit. + * Detects file extension and runs the appropriate formatter. + * Fails open — formatting errors are silently ignored. + */ +"use strict"; + +const { execSync } = require("child_process"); +const path = require("path"); + +const FORMATTERS = { + ".py": (f) => `ruff check --fix "${f}"`, + ".ts": (f) => `npx eslint --fix "${f}"`, + ".tsx": (f) => `npx eslint --fix "${f}"`, + ".js": (f) => `npx eslint --fix "${f}"`, + ".jsx": (f) => `npx eslint --fix "${f}"`, +}; + +async function main() { + try { + let data = ""; + for await (const chunk of process.stdin) data += chunk; + const input = JSON.parse(data); + + const filePath = input?.tool_input?.file_path ?? ""; + if (!filePath) return; + + const ext = path.extname(filePath).toLowerCase(); + const formatter = FORMATTERS[ext]; + if (!formatter) return; + + execSync(formatter(filePath), { + stdio: "ignore", + timeout: 10000, + }); + } catch { + // Fail open — formatting errors should never block work + } +} + +main(); diff --git a/.claude/hooks/block-dangerous-commands.cjs b/.claude/hooks/block-dangerous-commands.cjs new file mode 100644 index 0000000..b4fde6f --- /dev/null +++ b/.claude/hooks/block-dangerous-commands.cjs @@ -0,0 +1,38 @@ +#!/usr/bin/env node +/** + * PreToolUse hook: blocks dangerous shell commands before execution. + * Exit 0 = allow, Exit 2 = block. + * Fails open on errors (exit 0) so a hook bug never stalls the session. + */ +"use strict"; + +const DANGEROUS_PATTERNS = [ + /rm\s+-rf\s+\//, // rm -rf / + /git\s+push\s+(-f|--force)\s+(origin\s+)?main/, // force push to main + /git\s+reset\s+--hard/, // hard reset + /DROP\s+(TABLE|DATABASE)/i, // SQL drop + /TRUNCATE\s+/i, // SQL truncate +]; + +async function main() { + try { + let data = ""; + for await (const chunk of process.stdin) data += chunk; + const input = JSON.parse(data); + + const cmd = input?.tool_input?.command ?? ""; + for (const pattern of DANGEROUS_PATTERNS) { + if (pattern.test(cmd)) { + console.error(`BLOCKED: dangerous command detected — ${cmd}`); + process.exit(2); + } + } + + process.exit(0); + } catch { + // Fail open — never block on hook errors + process.exit(0); + } +} + +main(); diff --git a/.claude/hooks/notify.cjs b/.claude/hooks/notify.cjs new file mode 100644 index 0000000..5ab44f8 --- /dev/null +++ b/.claude/hooks/notify.cjs @@ -0,0 +1,40 @@ +#!/usr/bin/env node +/** + * Notification hook: cross-platform desktop notification. + * Supports macOS (osascript), Linux (notify-send), and Windows (PowerShell). + * Fails open — notification errors are silently ignored. + */ +"use strict"; + +const { execSync } = require("child_process"); +const os = require("os"); + +function notify(title, message) { + const platform = os.platform(); + + const commands = { + darwin: `osascript -e 'display notification "${message}" with title "${title}"'`, + linux: `notify-send "${title}" "${message}"`, + win32: `powershell.exe -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show('${message}', '${title}', 'OK', 'Information')"`, + }; + + const cmd = commands[platform]; + if (cmd) { + execSync(cmd, { stdio: "ignore", timeout: 5000 }); + } +} + +async function main() { + try { + let data = ""; + for await (const chunk of process.stdin) data += chunk; + const input = JSON.parse(data); + + const message = input?.message ?? "Needs your attention"; + notify("Claude Code", message); + } catch { + // Fail open — notification errors should never block work + } +} + +main(); diff --git a/.claude/rules/api.md b/.claude/rules/api.md new file mode 100644 index 0000000..b38758a --- /dev/null +++ b/.claude/rules/api.md @@ -0,0 +1,16 @@ +--- +paths: + - "src/api/**" + - "**/routes/**" + - "**/endpoints/**" + - "**/controllers/**" +--- + +# API Development Rules + +- All endpoints must include input validation (Zod for TS, Pydantic for Python) +- Use standard error response format: `{ error: string, code: number, details?: any }` +- Return appropriate HTTP status codes — never use 200 for errors +- Include OpenAPI documentation comments on all endpoints +- Rate limiting required on all public endpoints +- Authentication middleware on all protected routes diff --git a/.claude/rules/frontend.md b/.claude/rules/frontend.md new file mode 100644 index 0000000..fbfd5e2 --- /dev/null +++ b/.claude/rules/frontend.md @@ -0,0 +1,17 @@ +--- +paths: + - "**/*.tsx" + - "**/*.jsx" + - "src/components/**" + - "src/app/**" +--- + +# Frontend Rules + +- One component per file, named with PascalCase +- Use Server Components by default in Next.js — add `'use client'` only when needed +- Tailwind utility classes for styling — avoid inline styles +- All interactive elements must be keyboard accessible +- Include `aria-label` on icon-only buttons +- Use semantic HTML (`