#!/usr/bin/env bash # claude-harness: установка глобальной части (CLAUDE.md, хуки, конфиг caveman, слияние settings.json). # Идемпотентно. Запуск: bash install.sh [--no-permissions] | проверка окружения: bash install.sh --check # --no-permissions — не дописывать allow-правила цикла фичи (global/permissions.json) в settings.json. set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" CC="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" check() { local ok=0 echo "== Проверка окружения" for bin in claude git python3 node npm tea openspec codegraph; do if command -v "$bin" >/dev/null 2>&1; then echo " ok $bin ($(command -v "$bin"))"; else echo " MISS $bin"; ok=1; fi done if [ -f "$CC/plugins/installed_plugins.json" ]; then for p in superpowers caveman; do if grep -q "\"$p@" "$CC/plugins/installed_plugins.json"; then echo " ok plugin $p"; else echo " MISS plugin $p (/plugin install ...)"; fi done for p in beads template-bridge; do grep -q "\"$p@" "$CC/plugins/installed_plugins.json" && echo " WARN plugin $p установлен — конфликтует с обвязкой, удалить" || true done else echo " WARN не найден $CC/plugins/installed_plugins.json" fi if [ -f "$CC/settings.json" ] && grep -q 'codegraph prompt-hook' "$CC/settings.json"; then echo " WARN codegraph prompt-hook в settings.json — рекомендуется отключить на время пилота" fi local pr; pr=$(python3 - "$CC/settings.json" "$HERE/global/permissions.json" <<'PY' 2>/dev/null import json, sys try: s = json.load(open(sys.argv[1], encoding="utf-8")) except Exception: s = {} have = (s.get("permissions") or {}).get("allow") or [] need = json.load(open(sys.argv[2], encoding="utf-8"))["permissions"]["allow"] miss = [r for r in need if r not in have] print("%d/%d %s" % (len(need) - len(miss), len(need), " ".join(miss))) PY ) || true case "$pr" in "") echo " WARN permissions обвязки: не прочитаны ($CC/settings.json)" ;; *" ") echo " ok permissions обвязки: ${pr% }" ;; *) echo " WARN permissions обвязки: ${pr%% *}, нет: ${pr#* } — bash install.sh (или осознанно --no-permissions)" ;; esac echo "== Установленная часть обвязки ($CC)" for f in CLAUDE.md hooks/guard-main.sh hooks/require-plan.sh hooks/harness_target.py hooks/harness-check.sh hooks/role-hint.sh \ roles/dev.md roles/admin.md roles/analyst.md roles/qa.md skills/role/SKILL.md agents/briefing.md \ harness/adopt-project.sh harness/new-project.sh harness/link-roles.sh skills/harness-doctor/SKILL.md; do [ -f "$CC/$f" ] && echo " ok $f" || { echo " MISS $f (bash install.sh)"; ok=1; } done return $ok } PERMS=1 for a in "$@"; do case "$a" in --check) check; exit $? ;; --no-permissions) PERMS=0 ;; *) echo "ERR: неизвестный аргумент $a (есть --check, --no-permissions)"; exit 1 ;; esac done mkdir -p "$CC/hooks" "$HOME/.config/caveman" stamp=$(date +%Y%m%d-%H%M%S) # 1. Глобальный CLAUDE.md if [ -f "$CC/CLAUDE.md" ] && ! cmp -s "$HERE/global/CLAUDE.md" "$CC/CLAUDE.md"; then cp "$CC/CLAUDE.md" "$CC/CLAUDE.md.bak.$stamp"; echo "backup: CLAUDE.md.bak.$stamp" fi cp "$HERE/global/CLAUDE.md" "$CC/CLAUDE.md" # 2. Хуки cp "$HERE/hooks/guard-main.sh" "$HERE/hooks/require-plan.sh" "$HERE/hooks/harness-check.sh" "$HERE/hooks/role-hint.sh" \ "$HERE/hooks/harness_target.py" "$CC/hooks/" # 2a. Скрипты бутстрапа проекта — агент запускает их из сессии mkdir -p "$CC/harness" rm -rf "$CC/harness/project-template" cp -R "$HERE/adopt-project.sh" "$HERE/new-project.sh" "$HERE/link-roles.sh" "$HERE/project-template" "$HERE/global/permissions.json" "$CC/harness/" chmod +x "$CC/harness/"*.sh # 2b. Skill диагностики mkdir -p "$CC/skills" rm -rf "$CC/skills/harness-doctor" && cp -R "$HERE/skills/harness-doctor" "$CC/skills/harness-doctor" chmod +x "$CC/skills/harness-doctor/scripts/"*.sh # 2c. Роли, skill /role, агент briefing mkdir -p "$CC/roles" "$CC/skills/role" "$CC/agents" cp "$HERE/roles/"*.md "$CC/roles/" cp "$HERE/skills/role/SKILL.md" "$CC/skills/role/SKILL.md" cp "$HERE/agents/briefing.md" "$CC/agents/briefing.md" chmod +x "$CC/hooks/"*.sh # 3. caveman off by default cp "$HERE/caveman/config.json" "$HOME/.config/caveman/config.json" # 4. settings.json: слить блок hooks, вычистить хвосты beads/template-bridge SETTINGS="$CC/settings.json" [ -f "$SETTINGS" ] || echo '{}' > "$SETTINGS" cp "$SETTINGS" "$SETTINGS.bak.$stamp" python3 - "$SETTINGS" "$HERE/hooks/settings.hooks.json" <<'PY' import json, sys sp, hp = sys.argv[1], sys.argv[2] s = json.load(open(sp)); h = json.load(open(hp)) hooks = s.setdefault("hooks", {}) pre = hooks.setdefault("PreToolUse", []) def ours(entry): return any(("guard-main.sh" in x.get("command","") or "require-plan.sh" in x.get("command","")) for x in entry.get("hooks",[])) pre[:] = [e for e in pre if not ours(e)] pre.extend(h["hooks"]["PreToolUse"]) ss = hooks.setdefault("SessionStart", []) ours_ss = ("harness-check.sh", "role-hint.sh") ss[:] = [e for e in ss if not any(any(n in x.get("command","") for n in ours_ss) for x in e.get("hooks",[]))] ss.extend(h["hooks"]["SessionStart"]) for ev in ("SessionStart","PreCompact"): if ev in hooks: for e in hooks[ev]: e["hooks"] = [x for x in e.get("hooks",[]) if "bd prime" not in x.get("command","") and "WORKFLOW REMINDER" not in x.get("command","")] hooks[ev] = [e for e in hooks[ev] if e.get("hooks")] if not hooks[ev]: del hooks[ev] json.dump(s, open(sp,"w"), ensure_ascii=False, indent=2) print("settings.json: hooks merged") PY # 5. settings.json: allow-правила цикла фичи — только дописываются, чужие не трогаются if [ "$PERMS" = 1 ]; then python3 - "$SETTINGS" "$HERE/global/permissions.json" <<'PY' import json, sys sp, pp = sys.argv[1], sys.argv[2] s = json.load(open(sp)); need = json.load(open(pp))["permissions"]["allow"] allow = s.setdefault("permissions", {}).setdefault("allow", []) new = [r for r in need if r not in allow] allow.extend(new) json.dump(s, open(sp, "w"), ensure_ascii=False, indent=2) print("settings.json: permissions +%d%s" % (len(new), (" (" + ", ".join(new) + ")") if new else " (все на месте)")) PY else echo "settings.json: permissions пропущены (--no-permissions)" fi echo "done: $CC/CLAUDE.md, $CC/skills/harness-doctor, $CC/hooks/{guard-main,require-plan,harness-check,role-hint}.sh + harness_target.py, $CC/roles/, $CC/skills/role, $CC/agents/briefing.md, $CC/harness/, ~/.config/caveman/config.json" echo "Плагины Claude Code ставятся вручную: /plugin install superpowers@claude-plugins-official" check || true