diff --git a/skills/harness-doctor/SKILL.md b/skills/harness-doctor/SKILL.md index 254f394..53e2a05 100644 --- a/skills/harness-doctor/SKILL.md +++ b/skills/harness-doctor/SKILL.md @@ -25,6 +25,8 @@ description: Диагностика обвязки Claude Code (claude-harness) - `MISS`/`WARN hook …` → `bash <пакет>/install.sh`; «не зарегистрирован» — settings.json правился руками, тот же install.sh сольёт заново. - `WARN в окружении есть *_API_KEY` → ключ виден всем сабагентам и делает интеграционные тесты платными при каждом `npm test`. Убрать из профиля shell, передавать только в строке запуска. Это находка для пользователя, агент сам профиль не правит. - Глобальный CLAUDE.md без «маркировка: да» или «projectPath: да» → старая версия, `install.sh`. +- `MISS roles/.md`, `MISS skill role`, `MISS agent briefing`, `MISS link-roles.sh` → `bash <пакет>/install.sh` (версия ≥ 0.3.0). +- `WARN глобальный CLAUDE.md > 3 КБ` → в `~/.claude/CLAUDE.md` дописано лишнее; процесс роли живёт в `~/.claude/roles/.md`, перенести туда. **Самотест хуков** — любое `WARN … (ожидалось N)` означает, что хук установлен, но не делает того, что должен. Причины по убыванию частоты: правлен руками; `python3` не в PATH сессии; settings.json зовёт другой путь. Чинится `install.sh`; если и после него WARN — приложить строку к находке, это дефект пакета. @@ -38,9 +40,11 @@ description: Диагностика обвязки Claude Code (claude-harness) - «SDD workspace: …» при отсутствии активной фичи → хвост незакрытой фичи; проверить чеклист закрытия. - `WARN .bounded не в .gitignore` → добавить строку, иначе уедет в main. - `WARN tea не отвечает` → `tea logins list`; токен нужен со scopes read:user, write:repository, write:issue. +- «workspace (не git)» → режим нескольких репозиториев. `WARN … битая ссылка` → репозиторий направления переименован или файл удалён: `bash ~/.claude/harness/link-roles.sh `. `WARN нет /.claude/roles/.md` → создать проектный файл роли из `~/.claude/harness/project-template/role-repo/ROLE.md`. +- `WARN индекс auto-memory > 3 КБ` → индекс снова стал журналом состояния; свернуть закрытое в `registry-closed.md`, уроки — в `notes/` направления (политика памяти в глобальном CLAUDE.md). ## Чинится само (без вопроса) -`install.sh`, `codegraph init` в worktree, `.claude/code-paths`, строка в `.gitignore` на ветке. Всё остальное — находка пользователю: профиль shell, плагины, токены, удаление чего-либо. +`install.sh`, `link-roles.sh` в workspace, `codegraph init` в worktree, `.claude/code-paths`, строка в `.gitignore` на ветке. Всё остальное — находка пользователю: профиль shell, плагины, токены, удаление чего-либо. ## Чего не делать Не «улучшать» хуки по месту: их источник — пакет claude-harness, правка идёт туда и ставится install.sh. Не отключать хук ради того, чтобы «прошло». diff --git a/skills/harness-doctor/scripts/doctor.sh b/skills/harness-doctor/scripts/doctor.sh index 4ba4fc5..719cd14 100755 --- a/skills/harness-doctor/scripts/doctor.sh +++ b/skills/harness-doctor/scripts/doctor.sh @@ -16,13 +16,19 @@ if [ -f "$CC/plugins/installed_plugins.json" ]; then for p in beads template-bridge; do grep -q "\"$p@" "$CC/plugins/installed_plugins.json" && warn "plugin $p установлен (конфликт)"; done else warn "нет $CC/plugins/installed_plugins.json"; fi [ -f "$CC/CLAUDE.md" ] && ok "глобальный CLAUDE.md ($(grep -c . "$CC/CLAUDE.md") строк; маркировка: $(grep -q 'R-n' "$CC/CLAUDE.md" && echo да || echo нет); codegraph projectPath: $(grep -q projectPath "$CC/CLAUDE.md" && echo да || echo нет))" || miss "глобальный CLAUDE.md" -for h in guard-main require-plan harness-check; do +for h in guard-main require-plan harness-check role-hint; do f="$CC/hooks/$h.sh" if [ -x "$f" ]; then grep -q "$h.sh" "$CC/settings.json" 2>/dev/null && ok "hook $h.sh установлен и зарегистрирован" || warn "hook $h.sh есть, но не зарегистрирован в settings.json" else miss "hook $h.sh"; fi done [ -x "$CC/harness/adopt-project.sh" ] && ok "~/.claude/harness/ (adopt/new-project)" || miss "~/.claude/harness/adopt-project.sh" +[ -x "$CC/harness/link-roles.sh" ] && ok "~/.claude/harness/link-roles.sh" || miss "~/.claude/harness/link-roles.sh" +rmiss=(); for r in dev admin analyst qa; do [ -f "$CC/roles/$r.md" ] || rmiss+=("$r"); done +if [ ${#rmiss[@]} -eq 0 ]; then ok "roles/ (dev admin analyst qa)"; else for r in "${rmiss[@]}"; do miss "roles/$r.md"; done; fi +[ -f "$CC/skills/role/SKILL.md" ] && ok "skill role" || miss "skill role (~/.claude/skills/role/SKILL.md)" +[ -f "$CC/agents/briefing.md" ] && ok "agent briefing" || miss "agent briefing (~/.claude/agents/briefing.md)" +if [ -f "$CC/CLAUDE.md" ]; then n=$(wc -c < "$CC/CLAUDE.md" | tr -d ' '); [ "$n" -le 3072 ] && ok "глобальный CLAUDE.md $n Б (≤ 3 КБ)" || warn "глобальный CLAUDE.md $n Б > 3 КБ"; fi [ -f "$HOME/.config/caveman/config.json" ] && ok "caveman config: $(tr -d ' \n' < "$HOME/.config/caveman/config.json" | cut -c1-40)" || warn "нет ~/.config/caveman/config.json" grep -q 'codegraph prompt-hook' "$CC/settings.json" 2>/dev/null && warn "codegraph prompt-hook в settings.json" env | grep -E '^(AITUNNEL|OPENAI|GROQ|ANTHROPIC)_API_KEY=' | cut -d= -f1 | while read -r k; do warn "в окружении есть $k — виден всем агентам, тесты с ключом платные"; done @@ -45,6 +51,12 @@ if [ -x "$CC/hooks/require-plan.sh" ]; then hk require-plan "{\"cwd\":\"$T\",\"tool_input\":{\"file_path\":\"$T/api/src/a.py\"}}" 2 "api/src/ без плана" touch "$T/.bounded"; hk require-plan "{\"cwd\":\"$T\",\"tool_input\":{\"file_path\":\"$T/src/a.ts\"}}" 0 "src/ с .bounded" fi +if [ -x "$CC/hooks/role-hint.sh" ]; then + o=$(printf '{"session_title":"admin: спринт"}' | bash "$CC/hooks/role-hint.sh" 2>/dev/null) + case "$o" in *"Роль сессии: admin"*) ok "role-hint: заголовок admin → подсказка" ;; *) warn "role-hint: заголовок admin → '$o' (ожидалась подсказка)" ;; esac + o=$(printf '{"cwd":"/tmp","source":"resume"}' | bash "$CC/hooks/role-hint.sh" 2>/dev/null) + [ -z "$o" ] && ok "role-hint: без заголовка → тишина" || warn "role-hint: без заголовка → '$o' (ожидалась тишина)" +fi rm -rf "$T" echo "## Проект: $(cd "$DIR" 2>/dev/null && pwd)" @@ -74,4 +86,23 @@ if root=$(git -C "$DIR" rev-parse --show-toplevel 2>/dev/null); then info "remote: $(git -C "$root" remote get-url origin)" (cd "$root" && tea issues ls --limit 5 2>/dev/null | sed 's/^/ /') || warn "tea не отвечает (логин/токен?)" fi -else info "не git-репозиторий"; fi +elif [ -f "$DIR/CLAUDE.md" ] && grep -q '^| Каталог | Репозиторий | Роль |' "$DIR/CLAUDE.md"; then + ws=$(cd "$DIR" && pwd) + info "workspace (не git): $ws" + n=$(wc -c < "$ws/CLAUDE.md" | tr -d ' '); [ "$n" -le 5120 ] && ok "CLAUDE.md workspace $n Б (≤ 5 КБ)" || warn "CLAUDE.md workspace $n Б > 5 КБ" + for kind in roles agents agent-memory; do + for l in "$ws/.claude/$kind"/*; do + [ -e "$l" ] || [ -L "$l" ] || continue + name="$kind/$(basename "$l")" + if [ -L "$l" ]; then [ -e "$l" ] && ok "$name → $(readlink "$l")" || warn "$name — битая ссылка на $(readlink "$l")" + else info "$name — обычный файл (не ссылка)"; fi + done + done + grep -E '^\|' "$ws/CLAUDE.md" | awk -F'|' '{gsub(/ /,"",$4); gsub(/^ +| +$/,"",$2); if ($4=="admin"||$4=="analyst"||$4=="qa") print $2, $4}' | while read -r d r; do + [ -f "$ws/${d%/}/.claude/roles/$r.md" ] || warn "нет ${d%/}/.claude/roles/$r.md — запусти link-roles.sh после создания" + done + # slug каталога памяти Claude Code: все не-алфавитно-цифровые символы пути → «-» (например, /Users/a.b/x → -Users-a-b-x) + slug=$(printf '%s' "$ws" | sed 's#[^A-Za-z0-9]#-#g'); mem="$CC/projects/$slug/memory/MEMORY.md" + if [ -f "$mem" ]; then n=$(wc -c < "$mem" | tr -d ' '); [ "$n" -le 3072 ] && ok "индекс auto-memory $n Б (≤ 3 КБ)" || warn "индекс auto-memory $n Б > 3 КБ ($mem)"; else info "индекс auto-memory: нет ($mem)"; fi + [ -x "$CC/harness/link-roles.sh" ] && info "обновить ссылки: bash $CC/harness/link-roles.sh $ws" +else info "не git-репозиторий и не workspace (нет таблицы репозиториев в CLAUDE.md)"; fi diff --git a/tests/test-doctor.sh b/tests/test-doctor.sh new file mode 100644 index 0000000..f52b8b7 --- /dev/null +++ b/tests/test-doctor.sh @@ -0,0 +1,28 @@ +# 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 "$CC/hooks/"; chmod +x "$CC/hooks/"*.sh +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" "$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 "проект: режим 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") +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")