The #311 fix-as-you-touch rule is already enforced by a pre-commit hook and a blocking CI job, and both missed the same mistake twice on 2026-07-17: PR #405 shipped 6 BOM'd files, PR #402 shipped 19. The gap is that the pre-commit hook is routinely skipped — worktree hook friction makes `git commit --no-verify` the normal path here — which leaves CI, a ~10-minute round trip, as the first thing that tells you. This guard fires on Bash before git runs, so --no-verify can't skip it. It denies `git commit`/`git push` when a .cs the branch touches still carries a BOM, names the files, and gives the strip command. Why a hook rather than a note: a memory describing this exact trap did not prevent either failure — the PR #402 session re-added a BOM an hour after writing that memory down, because the usual ways of touching a legacy file re-add it silently (Python io.open(..., encoding='utf-8-sig') WRITES a BOM back; sed/perl round-trips keep it). A check that runs beats one you have to remember. Follows the existing gate pattern: a fail-open script in .claude/hooks/ registered on the PreToolUse/Bash matcher alongside bash-guard and worktree-guard. Any parse or lookup trouble → allow; this must never be the reason a commit can't happen, and CI remains the backstop. Generated *.Designer.cs / TvContextModelSnapshot.cs are exempt, matching what dotnet format itself skips. Scoped to this repo, and it resolves the target tree from an in-command `cd` because commits here run as `cd <worktree> && git ...` and the harness resets the shell cwd between calls. Verified against a throwaway fixture: denies commit and push for a BOM'd touched file (naming it); allows a clean tree, a BOM in generated files only, `git status` with a BOM present, and `echo 'run git push later'` (no false-trip on the words). Proved live via a sentinel — it fires on the Bash matcher — sentinel removed. docs/contributing.md updated: three enforcement layers, why the third exists, the utf-8-sig re-add trap, a by-hand check, and the bash-not-zsh caveat for `dotnet format --include` (mapfile is bash-only; under zsh the file list is empty and the tool looks like it silently did nothing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
91 lines
2.3 KiB
JSON
91 lines
2.3 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"hooks": {
|
|
"PreToolUse": [
|
|
{
|
|
"matcher": "Bash",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pretooluse-bash-guard.sh\"",
|
|
"timeout": 10
|
|
},
|
|
{
|
|
"type": "command",
|
|
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pretooluse-worktree-guard.sh\"",
|
|
"timeout": 10
|
|
},
|
|
{
|
|
"type": "command",
|
|
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pretooluse-bom-guard.sh\"",
|
|
"timeout": 10
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"matcher": "mcp__plugin_playwright_playwright__browser_navigate|mcp__claude-in-chrome__navigate",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pretooluse-nav-guard.sh\"",
|
|
"timeout": 10
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"matcher": "Agent|Task",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pretooluse-agent-ram.sh\"",
|
|
"timeout": 10
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"matcher": "mcp__gitea__pull_request_write",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pretooluse-merge-consent.sh\"",
|
|
"timeout": 15
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"matcher": "Write|Edit|MultiEdit",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/design-sync-reminder.sh\" start",
|
|
"timeout": 10
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"PostToolUse": [
|
|
{
|
|
"matcher": "Bash",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/posttooluse-worktree-marker.sh\"",
|
|
"timeout": 10
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"Stop": [
|
|
{
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/design-sync-reminder.sh\" finish",
|
|
"timeout": 10
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|