100 lines
5.1 KiB
Bash
Executable File
100 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# claude-harness: установка глобальной части (CLAUDE.md, хуки, конфиг caveman, слияние settings.json).
|
||
# Идемпотентно. Запуск: bash install.sh | проверка окружения: bash install.sh --check
|
||
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
|
||
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
|
||
}
|
||
|
||
if [ "${1:-}" = "--check" ]; then check; exit $?; fi
|
||
|
||
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" "$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
|
||
|
||
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
|