Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/check-standard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export LC_ALL=C
MAX_BYTES=$((5 * 1024 * 1024))
# Besides letters, digits and . _ - this allows the route symbols frameworks put in file names:
# [slug] (group) @slot +page $param {-$optional}.
# shellcheck disable=SC2016 # the $ is a literal character here, not an expansion
NAME_RE='^[][A-Za-z0-9._@+()${}-]+$'
KEBAB_RE='^[a-z0-9]+(-[a-z0-9]+)*(\.[a-z0-9]+)*$'
TITLE_RE='^(feat|fix|docs|chore|refactor|test|perf|ci|build|style|revert)(\([a-z0-9._/-]+\))?!?: [^ ]'
Expand Down Expand Up @@ -74,6 +75,7 @@ kebab_of() { # suggested kebab-case spelling of a name

# --- exceptions ---------------------------------------------------------------------------

# shellcheck disable=SC2094 # report only names the file in a message; it never writes to it
load_exceptions() {
[ -f "$EXCEPTIONS_FILE" ] || return 0
local line pattern reason n=0
Expand Down Expand Up @@ -141,7 +143,7 @@ set_kind() { # NAME — sets $kind for file types with their own rule, or emptie
}

check_path() { # PATH — name and file-type rules for one repository-relative path
local path=$1 rest=$1 comp first= depth=0
local path=$1 rest=$1 comp first='' depth=0
is_excepted "$path" && return 0
while :; do
comp=${rest%%/*}
Expand Down Expand Up @@ -304,7 +306,7 @@ run_staged() {
}

run_hook() { # blocks Claude Code from creating a file whose name breaks the standard
local input file_path script_dir root_l root_p rel cur rest comp match
local input file_path script_dir root_l root_p rel cur rest comp match entry
input=$(cat)
if ! command -v jq >/dev/null 2>&1; then
echo "check-standard: jq is not installed, so the file-name check was skipped." >&2
Expand Down Expand Up @@ -332,7 +334,14 @@ run_hook() { # blocks Claude Code from creating a file whose name breaks the sta
rest=$rel
while [ "$errors" -eq 0 ]; do # a new name must not clash by letter case with an existing one
comp=${rest%%/*}
match=$(ls -A "$cur" 2>/dev/null | grep -ixF -- "$comp" | grep -vxF -- "$comp" | head -n 1)
match=
shopt -s nocasematch
for entry in "$cur"/* "$cur"/.[!.]* "$cur"/..?*; do
[ -e "$entry" ] || continue
entry=${entry##*/}
if [ "$entry" != "$comp" ] && [[ $entry == "$comp" ]]; then match=$entry; break; fi
done
shopt -u nocasematch
if [ -n "$match" ]; then
report error "$rel" "\"$comp\" differs only by letter case from the existing \"$match\". Use \"$match\"."
break
Expand Down
5 changes: 3 additions & 2 deletions tests/check-standard.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ expect_clean() {

echo "== whole repository =="
new_repo; run; expect_clean "clean repository passes"
cd "$TMP"; run; expect "outside a git repository fails" 1 "git repository"
cd "$TMP" || exit 1; run; expect "outside a git repository fails" 1 "git repository"

new_repo; rm README.md; run; expect "missing README.md fails" 1 "README.md"
new_repo; printf '# no import\n' > CLAUDE.md; run; expect "CLAUDE.md without @AGENTS.md fails" 1 "@AGENTS.md"
Expand All @@ -98,6 +98,7 @@ touch "src/app/[slug]/page.tsx" "src/app/(marketing)/page.tsx" "src/app/@modal/d
src/adlete3d/body/__init__.py src/components/Button.tsx
run; expect_clean "framework names inside src pass"
new_repo; mkdir -p "src/routes/{-\$locale}"
# shellcheck disable=SC2016 # literal $ in TanStack Router file names
touch 'src/routes/$username.tsx' 'src/routes/{-$locale}/index.tsx' src/routes/__root.tsx
run; expect_clean "TanStack Router names inside src pass"
new_repo; blob=$(git hash-object -w /dev/null)
Expand Down Expand Up @@ -160,7 +161,7 @@ new_repo; touch "docs/old notes.md"; git add -A; git_q commit -qm legacy
echo change >> "docs/old notes.md"; git add -A; run --staged; expect_clean "editing a legacy file does not block the commit"
new_repo; head -c 6000000 /dev/zero > src/model.bin; git add -A; run --staged; expect "file over 5 MB blocks the commit" 1 "src/model.bin"
new_repo; mkdir tasks; touch tasks/todo.md; git add -A; run --staged; expect "new unknown top-level folder warns but passes" 0 "tasks/"
dir=$(mktemp -d "$TMP/empty.XXXXXX"); cd "$dir"; git init -q -b main; touch "Bad Name.md"; git add -A
dir=$(mktemp -d "$TMP/empty.XXXXXX"); cd "$dir" || exit 1; git init -q -b main; touch "Bad Name.md"; git add -A
run --staged; expect "first commit of an empty repository is checked" 1 "Bad Name.md"

echo "== Claude Code hook =="
Expand Down
Loading