I-1, I-2, M-3, M-4, M-5 (переоценены в Important), M-8; спека и design.md синхронизированы (M-7). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
36 lines
3.3 KiB
Bash
36 lines
3.3 KiB
Bash
# require-plan.sh: корень, ветка, code-paths, .bounded и планы — из репозитория правимого файла (спека hook-guards).
|
||
H="$ROOT/hooks/require-plan.sh"
|
||
S="$TMP/rp"; mkdir -p "$S/out"
|
||
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/a"; touch "$S/a/.bounded" # A: есть .bounded
|
||
mk "$S/b" # B: ни плана, ни .bounded
|
||
mk "$S/f"; git -C "$S/f" worktree add -q "$S/wt" -b feat/6-x; touch "$S/wt/.bounded"
|
||
mk "$S/c"; mkdir -p "$S/c/.claude"; printf 'hooks/ # код\n\nskills/\n' > "$S/c/.claude/code-paths"
|
||
mk "$S/p2" feat/6-x; mkdir -p "$S/p2/docs/superpowers/plans"
|
||
echo "Ветка: feat/6-x" > "$S/p2/docs/superpowers/plans/2026-10-16-a.md"; echo "другое" > "$S/p2/docs/superpowers/plans/2026-10-11-b.md"
|
||
mk "$S/p0" feat/7-y; mkdir -p "$S/p0/docs/superpowers/plans"
|
||
echo "Ветка: feat/6-x" > "$S/p0/docs/superpowers/plans/a.md"; echo "другое" > "$S/p0/docs/superpowers/plans/b.md"
|
||
mk "$S/p1" feat/7-y; mkdir -p "$S/p1/docs/superpowers/plans"; echo "x" > "$S/p1/docs/superpowers/plans/a.md"
|
||
mk "$S/pd"; mkdir -p "$S/pd/docs/superpowers/plans"; echo a > "$S/pd/docs/superpowers/plans/a.md"; echo b > "$S/pd/docs/superpowers/plans/b.md"
|
||
git -C "$S/pd" checkout -q --detach
|
||
j(){ python3 -c 'import json,sys; print(json.dumps({"cwd":sys.argv[1],"tool_name":"Edit","tool_input":{"file_path":sys.argv[2]}}))' "$@"; }
|
||
rc(){ j "$@" | bash "$H" 2>/dev/null; echo $?; }
|
||
assert_eq "файл в B без плана при cwd в A с .bounded" "$(rc "$S/a" "$S/b/src/a.py")" 2
|
||
assert_eq "файл в worktree с .bounded при cwd на main" "$(rc "$S/f" "$S/wt/src/a.py")" 0
|
||
assert_eq "src на глубине без плана" "$(rc "$S/b" "$S/b/api/src/a.py")" 2
|
||
assert_eq "имя файла с [скобками] в src без плана (M-4)" "$(rc "$S/b" "$S/b/src/[slug].ts")" 2
|
||
assert_eq "не код — пропуск" "$(rc "$S/b" "$S/b/docs/a.md")" 0
|
||
assert_eq "вне репозитория — пропуск" "$(rc "$S/b" "$S/out/src/a.py")" 0
|
||
assert_eq "code-paths: hooks/ без плана" "$(rc "$S/a" "$S/c/hooks/x.sh")" 2
|
||
assert_eq "code-paths: src/ не защищён" "$(rc "$S/c" "$S/c/src/a.py")" 0
|
||
assert_eq "два плана, один с веткой" "$(rc "$S/p2" "$S/p2/src/a.py")" 0
|
||
assert_eq "два плана без ветки" "$(rc "$S/p0" "$S/p0/src/a.py")" 2
|
||
assert_eq "один план — пропуск" "$(rc "$S/p1" "$S/p1/src/a.py")" 0
|
||
assert_eq "detached HEAD при планах — пропуск" "$(rc "$S/pd" "$S/pd/src/a.py")" 0
|
||
assert_eq "не JSON — пропуск" "$(printf 'not json' | bash "$H" 2>/dev/null; echo $?)" 0
|
||
assert_eq "без file_path — пропуск" "$(printf '{"cwd":"%s","tool_input":{}}' "$S/b" | bash "$H" 2>/dev/null; echo $?)" 0
|
||
m=$(j "$S/a" "$S/b/src/a.py" | bash "$H" 2>&1 >/dev/null)
|
||
assert_grep "сообщение: префикс и путь" '^BLOCKED \[require-plan\]: правка src/a.py' <(printf '%s' "$m")
|
||
assert_grep "сообщение: .bounded репозитория файла" "$(basename "$S")/b/\.bounded" <(printf '%s' "$m")
|
||
assert_grep "settings.hooks.json регистрирует require-plan" 'require-plan\.sh' "$ROOT/hooks/settings.hooks.json"
|