claude-harness/tests/test-install.sh
andrey.epifantsev 897fa0f3a7 feat: install.sh ставит allow-правила цикла фичи, --no-permissions для отказа (#24, 0.5.0)
Пересмотр решения владельца 2: allow-правила (git worktree, git branch
-m/-D, push фичевых и chore-веток, push --delete, tea issues/pulls
create/list/edit/comments) ставит install.sh в ~/.claude/settings.json,
а не владелец руками. Список — global/permissions.json; merge и push в
main в нём нет. Правила только дописываются, чужие не трогаются; копия
списка — ~/.claude/harness/permissions.json для doctor.sh.

- install.sh: шаг 5 — слияние permissions.allow; --no-permissions;
  неизвестный аргумент — ошибка; --check показывает permissions N/M.
- doctor.sh, «Машина»: ok / WARN со списком недостающих / MISS.
- adopt/new-project: строка push --delete — для --no-permissions.
- harness-doctor: агент чинит install.sh --no-permissions, права ставит
  владелец (иначе обход отказа классификатора auto mode).
- EnterWorktree не входит: вход в worktree вне .claude/worktrees/
  спрашивает подтверждение при любых правилах (#25).
- README, HARNESS.md, roles/dev.md, CLAUDE.md, ретро (пометка о
  пересмотре решения 2), CHANGELOG 0.5.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 00:01:57 +04:00

51 lines
5.2 KiB
Bash

# install.sh: новые файлы ставятся, повторный запуск не дублирует хуки, --check их видит.
TH="$TMP/home"; CLAUDE_CONFIG_DIR="$TH/.claude"; mkdir -p "$CLAUDE_CONFIG_DIR"
echo '{"permissions":{"allow":["Bash(echo:*)"]},"hooks":{"SessionStart":[{"matcher":"startup","hooks":[{"type":"command","command":"\"$HOME/.claude/hooks/harness-check.sh\""}]}]}}' > "$CLAUDE_CONFIG_DIR/settings.json"
HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" >/dev/null 2>&1; HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" >/dev/null 2>&1
CC="$CLAUDE_CONFIG_DIR"
for r in dev admin analyst qa; do assert_file "roles/$r.md установлен" "$CC/roles/$r.md"; done
assert_file "skills/role/SKILL.md установлен" "$CC/skills/role/SKILL.md"
assert_file "agents/briefing.md установлен" "$CC/agents/briefing.md"
assert_file "hooks/role-hint.sh установлен" "$CC/hooks/role-hint.sh"
assert_eq "hooks/role-hint.sh исполняемый" "$([ -x "$CC/hooks/role-hint.sh" ] && echo x)" "x"
assert_file "hooks/harness_target.py установлен" "$CC/hooks/harness_target.py"
assert_file "корпус хуков установлен" "$CC/skills/harness-doctor/fixtures/hook-cases.jsonl"
assert_file "раннер корпуса установлен" "$CC/skills/harness-doctor/scripts/hook_corpus.py"
assert_file "harness/link-roles.sh установлен" "$CC/harness/link-roles.sh"
assert_file "harness/project-template/workspace" "$CC/harness/project-template/workspace/CLAUDE.md"
assert_file "harness/project-template/role-repo" "$CC/harness/project-template/role-repo/ROLE.md"
cnt=$(python3 -c "import json,sys; s=json.load(open(sys.argv[1])); print(sum(1 for e in s['hooks']['SessionStart'] for h in e.get('hooks',[]) if 'role-hint.sh' in h['command']))" "$CC/settings.json")
assert_eq "role-hint.sh в settings.json один раз после двух установок" "$cnt" "1"
cnt=$(python3 -c "import json,sys; s=json.load(open(sys.argv[1])); print(sum(1 for e in s['hooks']['SessionStart'] for h in e.get('hooks',[]) if 'harness-check.sh' in h['command']))" "$CC/settings.json")
assert_eq "harness-check.sh в settings.json один раз" "$cnt" "1"
out=$(HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" --check 2>&1)
assert_grep "--check: roles" 'roles/' <(printf '%s' "$out")
assert_grep "--check: skill role" 'skills/role' <(printf '%s' "$out")
assert_grep "--check: agent briefing" 'agents/briefing' <(printf '%s' "$out")
assert_grep "--check: harness_target.py" 'ok +hooks/harness_target\.py' <(printf '%s' "$out")
rm "$CC/agents/briefing.md"
out=$(HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" --check 2>&1)
assert_grep "--check: MISS briefing" 'MISS +.*briefing' <(printf '%s' "$out")
# allow-правила цикла фичи (#24): ставятся из global/permissions.json, чужие не трогаются, --no-permissions — отказ
P="$ROOT/global/permissions.json"
assert_file "global/permissions.json есть" "$P"
assert_file "harness/permissions.json установлен" "$CC/harness/permissions.json"
perm(){ python3 -c 'import json,sys; s=json.load(open(sys.argv[1])); a=s.get("permissions",{}).get("allow",[]); r=json.load(open(sys.argv[2]))["permissions"]["allow"]; print(sum(a.count(x)==1 for x in r), len(r), a.count("Bash(echo:*)"), len(a)-len(set(a)))' "$1" "$P"; }
read -r have total own dup <<< "$(perm "$CC/settings.json")"
assert_eq "список правил обвязки прочитан и не пуст" "$([ "${total:-0}" -gt 0 ] 2>/dev/null && echo да)" "да"
assert_eq "правила обвязки в settings.json — все, по одному разу после двух установок" "$have" "$total"
assert_eq "чужое правило сохранено" "$own" "1"
assert_eq "дублей в permissions.allow нет" "$dup" "0"
assert_eq "в списке нет merge и push в main" "$(grep -cE 'pulls merge|push origin main|"Bash\(git push:\*\)"|"Bash\(git:\*\)"|"Bash"' "$P")" "0"
assert_eq "EnterWorktree в списке нет: allow-правило не снимает запрос (#25)" "$(grep -c 'EnterWorktree' "$P")" "0"
out=$(HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" --check 2>&1)
assert_grep "--check: правила обвязки на месте" 'ok +permissions обвязки: [0-9]+/[0-9]+' <(printf '%s' "$out")
T2="$TMP/home2"; C2="$T2/.claude"; mkdir -p "$C2"; echo '{}' > "$C2/settings.json"
out=$(HOME="$T2" CLAUDE_CONFIG_DIR="$C2" bash "$ROOT/install.sh" --no-permissions 2>&1); rc=$?
assert_eq "--no-permissions: exit 0" "$rc" "0"
assert_grep "--no-permissions: говорит, что правила пропущены" 'permissions.*--no-permissions' <(printf '%s' "$out")
read -r have total own dup <<< "$(perm "$C2/settings.json")"
assert_eq "--no-permissions: правил обвязки не добавлено" "$have" "0"
assert_file "--no-permissions: хуки всё равно ставятся" "$C2/hooks/guard-main.sh"
assert_grep "--no-permissions: --check предупреждает" 'WARN +permissions обвязки: 0/' <(printf '%s' "$out")