B1; RT-1, RT-2, RT-7; решение владельца 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
76 lines
6.6 KiB
Bash
76 lines
6.6 KiB
Bash
# guard-main.sh: решение по цели записи (спека hook-guards) в песочнице из настоящих git-репозиториев.
|
||
G="$ROOT/hooks/guard-main.sh"
|
||
S="$TMP/sb"; mkdir -p "$S/ws" "$HOME/.claude/projects/p/memory"
|
||
mk(){ git init -q "$1" && git -C "$1" symbolic-ref HEAD "refs/heads/${2:-main}" && git -C "$1" commit -q --allow-empty -m init; }
|
||
mk "$S/m" # репозиторий на main
|
||
mk "$S/f"; git -C "$S/f" worktree add -q "$S/wt" -b feat/6-x # основной checkout на main + worktree на ветке
|
||
mk "$S/ms" master # на master
|
||
mk "$S/d"; git -C "$S/d" checkout -q --detach # detached HEAD
|
||
j(){ python3 -c 'import json,sys; k="command" if sys.argv[2]=="Bash" else "file_path"; print(json.dumps({"cwd":sys.argv[1],"tool_name":sys.argv[2],"tool_input":{k:sys.argv[3]}}))' "$@"; }
|
||
rc(){ j "$@" | bash "$G" 2>/dev/null; echo $?; }
|
||
err(){ j "$@" | bash "$G" 2>&1 >/dev/null; }
|
||
# решение по цели, а не по cwd
|
||
assert_eq "чтение с 2>/dev/null на main" "$(rc "$S/m" Bash 'grep -rn "npm i\|npm install" . 2>/dev/null | head')" 0
|
||
assert_eq "fetch 2>&1 | tail на main" "$(rc "$S/m" Bash 'git fetch origin --prune 2>&1 | tail -3')" 0
|
||
assert_eq "Write вне репозитория при cwd на main" "$(rc "$S/m" Write "$HOME/.claude/projects/p/memory/x.md")" 0
|
||
assert_eq "Edit в worktree при cwd на main" "$(rc "$S/f" Edit "$S/wt/a.md")" 0
|
||
assert_eq "Edit в репозитории на main при cwd в worktree" "$(rc "$S/wt" Edit "$S/m/a.md")" 2
|
||
assert_eq "Write на master" "$(rc "$S/ws" Write "$S/ms/a.md")" 2
|
||
assert_eq "Write при detached HEAD" "$(rc "$S/ws" Write "$S/d/a.md")" 2
|
||
# файловые цели Bash
|
||
assert_eq "echo > в main" "$(rc "$S/m" Bash 'echo x > notes.md')" 2
|
||
assert_eq "make 2>build.log в main" "$(rc "$S/m" Bash 'make 2>build.log')" 2
|
||
assert_eq "sed -i по файлу main" "$(rc "$S/ws" Bash "sed -i 's/a/b/' $S/m/docs/HARNESS.md")" 2
|
||
assert_eq "sed -i '' по файлу памяти" "$(rc "$S/m" Bash "sed -i '' 's/a/b/' $HOME/.claude/projects/p/memory/x.md")" 0
|
||
assert_eq "docker … sh -c > /tmp" "$(rc "$S/m" Bash "docker compose exec -T db sh -c 'psql -c \"select 1\" > /tmp/x'")" 0
|
||
assert_eq "pip install на main" "$(rc "$S/m" Bash 'pip install requests')" 0
|
||
assert_eq "npm install на main" "$(rc "$S/m" Bash 'npm install')" 2
|
||
assert_eq "цель в переменной" "$(rc "$S/m" Bash 'echo x > "$OUT"')" 0
|
||
assert_eq "цель через \$HOME в main" "$(HOME="$S" rc "$S/ws" Bash 'echo x > $HOME/m/f')" 2
|
||
# каталог внутри команды
|
||
assert_eq "cd в worktree и запись" "$(rc "$S/f" Bash "cd $S/wt && echo x > f")" 0
|
||
assert_eq "cd в main и touch" "$(rc "$S/wt" Bash "cd $S/m && touch f")" 2
|
||
assert_eq "mkdir -p .wt/ в не-git workspace" "$(rc "$S/ws" Bash 'mkdir -p .wt/6-x')" 0
|
||
assert_eq "bash -c с записью в main" "$(rc "$S/m" Bash "bash -c 'echo x > f'")" 2
|
||
# git
|
||
assert_eq "add && commit в main" "$(rc "$S/m" Bash 'git add -A && git commit -m x')" 2
|
||
assert_eq "commit -C worktree при cwd на main" "$(rc "$S/f" Bash "git -C $S/wt commit -m x")" 0
|
||
assert_eq "worktree add -C из workspace" "$(rc "$S/ws" Bash "git -C $S/m worktree add ../.wt/x -b feat/x")" 0
|
||
assert_eq "pull --ff-only на main" "$(rc "$S/m" Bash 'git pull --ff-only')" 0
|
||
assert_eq "stash list на main" "$(rc "$S/m" Bash 'git stash list')" 0
|
||
assert_eq "-C в переменной — блок" "$(rc "$S/ws" Bash 'git -C "$REPO" commit -m x')" 2
|
||
assert_grep "-C в переменной — подсказка git -C" 'git -C <абсолютный путь>' <(err "$S/ws" Bash 'git -C "$REPO" commit -m x')
|
||
assert_eq "switch -c && commit на main" "$(rc "$S/m" Bash 'git switch -q -c chore/archive-x && git add openspec && git commit -m a')" 0
|
||
assert_eq "switch -c && touch на main" "$(rc "$S/m" Bash 'git switch -q -c feat/16 && touch .bounded')" 0
|
||
assert_eq "switch в переменную && commit — блок" "$(rc "$S/m" Bash 'git switch "$B" && git commit -m x')" 2
|
||
assert_eq "switch в переменную && touch — пропуск" "$(rc "$S/m" Bash 'git switch "$B" && touch f')" 0
|
||
assert_eq "commit вне репозитория" "$(rc "$S/ws" Bash 'git commit -m x')" 0
|
||
# push
|
||
assert_eq "push с main" "$(rc "$S/m" Bash 'git push')" 2
|
||
assert_eq "push --delete фичи с main" "$(rc "$S/m" Bash 'git push origin --delete feat/6-x')" 0
|
||
assert_eq "push фичи с main" "$(rc "$S/m" Bash 'git push origin feat/6-x')" 0
|
||
assert_eq "push HEAD:main из worktree" "$(rc "$S/wt" Bash 'git push origin HEAD:main')" 2
|
||
assert_eq "push из worktree" "$(rc "$S/wt" Bash 'git push -u origin HEAD')" 0
|
||
# без исключения для openspec (решение владельца 1)
|
||
mkdir -p "$S/m/openspec"; echo a > "$S/m/openspec/x.md"; git -C "$S/m" add openspec
|
||
assert_eq "commit только openspec/ в main" "$(rc "$S/m" Bash 'git commit -m archive')" 2
|
||
git -C "$S/m" reset -q
|
||
assert_eq "openspec archive в main" "$(rc "$S/m" Bash 'openspec archive x')" 2
|
||
# граничные случаи (Review Focus)
|
||
ln -s "$S/wt/CLAUDE.md" "$S/ws/CLAUDE.md"; ln -s "$S/m/CLAUDE.md" "$S/ws/MAIN.md"
|
||
assert_eq "запись через симлинк в worktree" "$(rc "$S/ws" Write "$S/ws/CLAUDE.md")" 0
|
||
assert_eq "запись через симлинк в main" "$(rc "$S/ws" Write "$S/ws/MAIN.md")" 2
|
||
assert_eq "пустой cwd и относительный путь — пропуск" "$(rc "" Write "a.md")" 0
|
||
assert_eq "запись внутри .git/ — не рабочее дерево" "$(rc "$S/m" Bash 'echo x >> .git/info/exclude')" 0
|
||
assert_eq "путь с пробелом в main" "$(rc "$S/m" Bash 'touch "a b.md"')" 2
|
||
# сбой разбора и сообщение
|
||
assert_eq "не JSON — пропуск" "$(printf 'not json' | bash "$G" 2>/dev/null; echo $?)" 0
|
||
assert_eq "незакрытая кавычка — пропуск" "$(rc "$S/m" Bash 'echo "x > f')" 0
|
||
mkdir -p "$TMP/nopy"
|
||
assert_eq "нет python3 — пропуск" "$(env PATH="$TMP/nopy" /bin/bash "$G" <<< "$(j "$S/m" Write "$S/m/a.md")" 2>/dev/null; echo $?)" 0
|
||
m=$(err "$S/ws" Write "$S/m/docs/HARNESS.md")
|
||
assert_grep "сообщение: префикс" '^BLOCKED \[guard-main\]:' <(printf '%s' "$m")
|
||
assert_grep "сообщение: путь отн. корня" 'docs/HARNESS.md' <(printf '%s' "$m")
|
||
assert_grep "сообщение: корень и worktree" "$(basename "$S")/m на main.*worktree" <(printf '%s' "$m")
|
||
assert_grep "settings.hooks.json регистрирует guard-main" 'guard-main\.sh' "$ROOT/hooks/settings.hooks.json"
|