С Claude Code 2.1.206 вход в worktree вне .claude/worktrees/ спрашивает подтверждение при любых allow-правилах. EnterWorktree с именем создаёт worktree в <repo>/.claude/worktrees/<slug> на ветке worktree-<slug> от origin/main и входит без вопроса; ветка переименовывается в feat/<id>-<slug>. - roles/dev.md: «Исполнение» — EnterWorktree с именем; п. 1 закрытия — archive-<id>; п. 4 — ExitWorktree (keep) снимает блокировку сессии, затем git worktree remove и branch -D. - .claude/worktrees/ в .gitignore: adopt-project.sh, project-template, этот репозиторий; doctor.sh — WARN, если не игнорируется. - Спека hook-guards: сценарии «worktree внутри репозитория» для guard-main и require-plan; тесты закрепляют уже верное поведение. - HARNESS.md (шаг 4, п. 4, подтверждения), SKILL.md, CHANGELOG 0.5.1, CLAUDE.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
8.2 KiB
Bash
80 lines
8.2 KiB
Bash
# doctor.sh: новые проверки — файлы ролей, самотест role-hint, режим workspace, размеры.
|
||
D="$ROOT/skills/harness-doctor/scripts/doctor.sh"
|
||
CC="$TMP/cc"; mkdir -p "$CC/hooks" "$CC/roles" "$CC/skills/role" "$CC/agents" "$CC/harness" "$CC/projects"
|
||
cp "$ROOT"/hooks/*.sh "$ROOT/hooks/harness_target.py" "$CC/hooks/"; chmod +x "$CC/hooks/"*.sh
|
||
mkdir -p "$CC/skills"; cp -R "$ROOT/skills/harness-doctor" "$CC/skills/"
|
||
cp "$ROOT"/roles/*.md "$CC/roles/"; cp "$ROOT/skills/role/SKILL.md" "$CC/skills/role/"; cp "$ROOT/agents/briefing.md" "$CC/agents/"
|
||
cp "$ROOT/link-roles.sh" "$ROOT/adopt-project.sh" "$ROOT/global/permissions.json" "$CC/harness/"; cp "$ROOT/global/CLAUDE.md" "$CC/CLAUDE.md"
|
||
printf '{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"\\"$HOME/.claude/hooks/role-hint.sh\\""}]}]}}' > "$CC/settings.json"
|
||
# workspace с одной целой и одной битой ссылкой
|
||
W="$TMP/ws"; mkdir -p "$W/admin/.claude/roles" "$W/.claude/roles" "$W/.claude/agents"
|
||
echo '# Роль: admin — x' > "$W/admin/.claude/roles/admin.md"
|
||
printf '# p\n\n| Каталог | Репозиторий | Роль |\n|---|---|---|\n| admin/ | o/a | admin |\n' > "$W/CLAUDE.md"
|
||
ln -s ../../admin/.claude/roles/admin.md "$W/.claude/roles/admin.md"
|
||
ln -s ../../qa/.claude/agents/none.md "$W/.claude/agents/none.md"
|
||
slug=$(printf '%s' "$W" | sed 's#[^A-Za-z0-9]#-#g'); mkdir -p "$CC/projects/$slug/memory"; head -c 4000 /dev/zero | tr '\0' 'x' > "$CC/projects/$slug/memory/MEMORY.md"
|
||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1)
|
||
assert_grep "машина: roles" 'ok +roles/ \(dev admin analyst qa\)' <(printf '%s' "$out")
|
||
assert_grep "машина: skill role" 'ok +skill role' <(printf '%s' "$out")
|
||
assert_grep "машина: агент briefing" 'ok +agent briefing' <(printf '%s' "$out")
|
||
assert_grep "машина: link-roles.sh" 'ok +.*link-roles.sh' <(printf '%s' "$out")
|
||
assert_grep "самотест: role-hint admin" 'ok +role-hint: заголовок admin' <(printf '%s' "$out")
|
||
assert_grep "самотест: role-hint тишина" 'ok +role-hint: без заголовка' <(printf '%s' "$out")
|
||
assert_grep "машина: harness_target.py" 'ok +hooks/harness_target\.py' <(printf '%s' "$out")
|
||
assert_grep "самотест: openspec-only коммит на main — блок" 'ok +guard-main: git commit openspec-only на main → exit 2' <(printf '%s' "$out")
|
||
assert_grep "самотест: Write на main" 'ok +guard-main: Write на main → exit 2' <(printf '%s' "$out")
|
||
assert_grep "самотест: Write на ветке" 'ok +guard-main: Write на ветке → exit 0' <(printf '%s' "$out")
|
||
assert_grep "корпус хуков сходится" 'ok +корпус хуков: [0-9]+/[0-9]+' <(printf '%s' "$out")
|
||
assert_grep "заголовок раздела проекта (M-8)" '^## Проект: /' <(printf '%s' "$out")
|
||
assert_grep "машина: правил обвязки нет — WARN с подсказкой install.sh (#24)" 'WARN +permissions обвязки: 0/[0-9]+.*bash install\.sh' <(printf '%s' "$out")
|
||
assert_grep "проект: режим workspace" 'workspace' <(printf '%s' "$out")
|
||
assert_grep "проект: целая ссылка" 'ok +roles/admin.md' <(printf '%s' "$out")
|
||
assert_grep "проект: битая ссылка" 'WARN +agents/none.md' <(printf '%s' "$out")
|
||
assert_grep "проект: индекс памяти > 3 КБ" 'WARN +индекс auto-memory' <(printf '%s' "$out")
|
||
assert_grep "workspace: нет allow push --delete — WARN с готовой строкой (B3)" 'WARN +нет allow-правила для git push origin --delete .*"Bash\(git push origin --delete:\*\)"' <(printf '%s' "$out")
|
||
# allow-правило push --delete (B3, решение владельца 2): git-проект, проектные и глобальные настройки
|
||
P="$TMP/p"; git init -q -b main "$P" && git -C "$P" commit -q --allow-empty -m init
|
||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$P" 2>&1)
|
||
assert_grep "git-проект: нет allow push --delete — WARN" 'WARN +нет allow-правила для git push origin --delete' <(printf '%s' "$out")
|
||
assert_grep "git-проект: .claude/worktrees/ не в .gitignore — WARN (#25)" 'WARN +\.claude/worktrees/ не в \.gitignore' <(printf '%s' "$out")
|
||
printf '.bounded\n.claude/worktrees/\n' > "$P/.gitignore"
|
||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$P" 2>&1)
|
||
assert_eq "git-проект: .claude/worktrees/ в .gitignore — без WARN" "$(printf '%s' "$out" | grep -c 'worktrees/ не в')" "0"
|
||
rm -f "$P/.gitignore"
|
||
mkdir -p "$P/.claude"; printf '{"permissions":{"allow":["Bash(git push origin --delete:*)"]}}' > "$P/.claude/settings.json"
|
||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$P" 2>&1)
|
||
assert_grep "git-проект: правило в .claude/settings.json — ok" 'ok +allow git push origin --delete \(.*/p/\.claude/settings\.json\)' <(printf '%s' "$out")
|
||
printf '{"permissions":{"allow":["Bash(git push origin --delete feat/:*)"]}}' > "$P/.claude/settings.json"
|
||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$P" 2>&1)
|
||
assert_grep "git-проект: правило только для feat/ не покрывает — WARN" 'WARN +нет allow-правила для git push origin --delete' <(printf '%s' "$out")
|
||
printf '{"permissions":{"allow":["Bash(git push:*)"]}}' > "$P/.claude/settings.local.json"
|
||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$P" 2>&1)
|
||
assert_grep "git-проект: широкое правило в settings.local.json — ok" 'ok +allow git push origin --delete \(.*settings\.local\.json\)' <(printf '%s' "$out")
|
||
rm -f "$P/.claude/settings.local.json"; printf '{"permissions":' > "$P/.claude/settings.json"
|
||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$P" 2>&1); rc=$?
|
||
assert_eq "git-проект: битый settings.json — doctor не падает" "$rc" "0"
|
||
assert_grep "git-проект: битый settings.json — info" '^ - +.*settings\.json — не JSON' <(printf '%s' "$out")
|
||
rm -f "$P/.claude/settings.json"; cp "$CC/settings.json" "$TMP/settings.bak"
|
||
printf '{"permissions":{"allow":["Bash(git push origin --delete:*)"]},"hooks":{}}' > "$CC/settings.json"
|
||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1)
|
||
assert_grep "workspace: правило в глобальном settings.json — ok" 'ok +allow git push origin --delete \(.*/cc/settings\.json\)' <(printf '%s' "$out")
|
||
cp "$TMP/settings.bak" "$CC/settings.json"
|
||
# правила обвязки (#24): все на месте — ok; нет harness/permissions.json — MISS
|
||
python3 -c 'import json,sys; s=json.load(open(sys.argv[1])); s["permissions"]=json.load(open(sys.argv[2]))["permissions"]; json.dump(s,open(sys.argv[1],"w"))' "$CC/settings.json" "$ROOT/global/permissions.json"
|
||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1)
|
||
assert_grep "машина: правила обвязки на месте — ok" 'ok +permissions обвязки: [0-9]+/[0-9]+' <(printf '%s' "$out")
|
||
rm "$CC/harness/permissions.json"
|
||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1)
|
||
assert_grep "машина: нет harness/permissions.json — MISS" 'MISS +permissions обвязки' <(printf '%s' "$out")
|
||
cp "$TMP/settings.bak" "$CC/settings.json"
|
||
rm -f "$CC/roles/qa.md"
|
||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1)
|
||
assert_grep "машина: MISS roles/qa.md" 'MISS +roles/qa.md' <(printf '%s' "$out")
|
||
# workspace с «выровненной» шапкой таблицы (лишние пробелы) определяется так же
|
||
W6="$TMP/ws6"; mkdir -p "$W6/admin/.claude/roles" "$W6/.claude/roles"
|
||
printf '# p\n\n| Каталог | Репозиторий | Роль |\n|---|---|---|\n| admin/ | o/a | admin |\n| | x | qa |\n| - | y | qa |\n' > "$W6/CLAUDE.md"
|
||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W6" 2>&1)
|
||
assert_grep "workspace с выровненной шапкой" 'workspace \(не git\)' <(printf '%s' "$out")
|
||
assert_grep "выровненная шапка: нет файла роли admin" 'нет admin/\.claude/roles/admin\.md' <(printf '%s' "$out")
|
||
assert_eq "пустой и «-» каталоги пропущены" "$(printf '%s' "$out" | grep -c 'нет .*roles/qa\.md')" "0"
|