harness-doctor skill: doctor.sh + reading checklist
This commit is contained in:
parent
096f5586fb
commit
6ab7dbe774
@ -1,6 +1,8 @@
|
||||
# Changelog
|
||||
|
||||
## 0.2.0 — 2026-09-27
|
||||
- Skill `harness-doctor` (`doctor.sh` + чеклист прочтения): машина, самотест хуков, проект; ставится в `~/.claude/skills/`.
|
||||
- require-plan: защищает любой `src/` на любой глубине (было — только от корня), переопределение списком в `.claude/code-paths`.
|
||||
- SessionStart-хук `harness-check.sh`: при старте сессии в неподключённом проекте агент предлагает (или с `HARNESS_AUTO_ADOPT=1` — сразу выполняет) `adopt-project.sh`; скрипты бутстрапа ставятся в `~/.claude/harness/`.
|
||||
- `adopt-project.sh` — подключение обвязки к существующему репозиторию (ветка → бутстрап → ff-merge); `project-template/CLAUDE.harness-section.md`.
|
||||
- codegraph: `codegraph init` в worktree и `projectPath` в вызовах `codegraph_explore` — MCP-сервер видит только индекс папки запуска.
|
||||
|
||||
@ -77,3 +77,7 @@ bash new-project.sh ~/projects/my-app ssh://git@<gitea>:2222/<owner>/my-app.git
|
||||
## Обновление
|
||||
|
||||
`git pull && bash install.sh`. Бэкапы `CLAUDE.md` и `settings.json` создаются автоматически. Раз в два месяца сверяйтесь с `RELEASE-NOTES.md` superpowers: семантика skills между версиями меняется.
|
||||
|
||||
## Диагностика
|
||||
|
||||
`bash ~/.claude/skills/harness-doctor/scripts/doctor.sh [проект]` — снимок машины, самотест хуков, состояние проекта. В Claude Code тот же скилл `harness-doctor` агент вызывает сам при симптомах обвязки и чинит безопасное. Отчёт можно вставить в чат с Claude — чеклист прочтения в `skills/harness-doctor/SKILL.md`.
|
||||
|
||||
@ -70,7 +70,7 @@ Fix loop: раунды 1–3 продолжают того же implementer, 4
|
||||
| Правило | Файл | Механизм |
|
||||
|---|---|---|
|
||||
| Никаких изменений на main / detached HEAD | `guard-main.sh`, PreToolUse на Edit/Write/MultiEdit/Bash | проверяет ветку; для Bash блокирует команды, похожие на запись (`>`, `tee`, `sed -i`, `mv`, `rm`, `git commit/push`…), пропускает `git worktree add`; коммит/push в main разрешены только если изменены исключительно файлы в `openspec/` (архивация) |
|
||||
| Правки в `src/` только при плане или `.bounded` | `require-plan.sh`, PreToolUse на Edit/Write/MultiEdit | ищет план в `docs/superpowers/plans/` для текущей ветки или файл `.bounded` |
|
||||
| Правки в коде только при плане или `.bounded` | `require-plan.sh`, PreToolUse на Edit/Write/MultiEdit | защищает любой `src/` на любой глубине (`api/src/`, `packages/*/src/`) или каталоги из `.claude/code-paths`; ищет план в `docs/superpowers/plans/` для текущей ветки или файл `.bounded` |
|
||||
| Обвязка подключена к проекту | `harness-check.sh`, SessionStart | при старте сессии проверяет `openspec/`, `.codegraph/`, секцию в CLAUDE.md; если чего-то нет — пишет агенту инструкцию запустить `~/.claude/harness/adopt-project.sh` (спросив пользователя; `HARNESS_AUTO_ADOPT=1` — без вопроса) |
|
||||
| Тесты зелёные перед коммитом | git pre-commit в проекте (не входит в пакет) | обычный git-хук с командой тестов |
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# PreToolUse hook: edits under src/ require a plan for this branch or a .bounded marker.
|
||||
# PreToolUse hook: edits in code dirs (any src/ by default, or .claude/code-paths) require a plan for this branch or a .bounded marker.
|
||||
set -euo pipefail
|
||||
input=$(cat)
|
||||
read -r cwd fpath < <(printf '%s' "$input" | python3 -c '
|
||||
@ -9,10 +9,19 @@ print(d.get("cwd",""), ti.get("file_path") or ti.get("path") or "")' 2>/dev/null
|
||||
[ -n "$cwd" ] && [ -n "$fpath" ] || exit 0
|
||||
root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null || true)
|
||||
[ -n "$root" ] || exit 0
|
||||
case "$fpath" in
|
||||
"$root"/src/*|src/*) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
# Защищаемые пути. По умолчанию — любой каталог src/ на любой глубине (src/, api/src/, packages/*/src/).
|
||||
# Проект может переопределить списком префиксов (относительно корня, по одному на строку) в .claude/code-paths.
|
||||
rel=${fpath#"$root"/}
|
||||
protected=0
|
||||
if [ -f "$root/.claude/code-paths" ]; then
|
||||
while IFS= read -r p; do
|
||||
p=${p%%#*}; p=${p## }; p=${p%% }; [ -n "$p" ] || continue
|
||||
case "$rel" in "${p%/}"/*) protected=1; break ;; esac
|
||||
done < "$root/.claude/code-paths"
|
||||
else
|
||||
case "/$rel" in */src/*) protected=1 ;; esac
|
||||
fi
|
||||
[ "$protected" = 1 ] || exit 0
|
||||
[ -f "$root/.bounded" ] && exit 0
|
||||
branch=$(git -C "$cwd" branch --show-current 2>/dev/null || true)
|
||||
if ls "$root"/docs/superpowers/plans/*.md >/dev/null 2>&1; then
|
||||
|
||||
@ -44,6 +44,10 @@ cp "$HERE/hooks/guard-main.sh" "$HERE/hooks/require-plan.sh" "$HERE/hooks/harnes
|
||||
mkdir -p "$CC/harness"
|
||||
cp -R "$HERE/adopt-project.sh" "$HERE/new-project.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
|
||||
chmod +x "$CC/hooks/"*.sh
|
||||
|
||||
# 3. caveman off by default
|
||||
@ -76,6 +80,6 @@ json.dump(s, open(sp,"w"), ensure_ascii=False, indent=2)
|
||||
print("settings.json: hooks merged")
|
||||
PY
|
||||
|
||||
echo "done: $CC/CLAUDE.md, $CC/hooks/{guard-main,require-plan,harness-check}.sh, $CC/harness/, ~/.config/caveman/config.json"
|
||||
echo "done: $CC/CLAUDE.md, $CC/skills/harness-doctor, $CC/hooks/{guard-main,require-plan,harness-check}.sh, $CC/harness/, ~/.config/caveman/config.json"
|
||||
echo "Плагины Claude Code ставятся вручную: /plugin install superpowers@claude-plugins-official"
|
||||
check || true
|
||||
|
||||
@ -3,3 +3,4 @@
|
||||
- Когда brainstorming классифицировал задачу как architectural, структуру change создать через `openspec new change <id>` (или `/opsx:propose <id>`), а содержимое proposal.md, specs/ и design.md написать по итогам brainstorming. tasks.md из OpenSpec не заполнять — задачи живут в плане superpowers.
|
||||
- `/opsx:apply` не использовать: исполнение идёт через writing-plans и SDD/executing-plans.
|
||||
- `openspec archive <id>` — после merge PR, как часть чеклиста закрытия.
|
||||
- Хук require-plan защищает любой каталог `src/` на любой глубине. Если код лежит иначе — перечислить каталоги (префиксы от корня, по одному в строке) в `.claude/code-paths`.
|
||||
|
||||
@ -29,3 +29,4 @@
|
||||
- Когда brainstorming классифицировал задачу как architectural, структуру change создать через `openspec new change <id>` (или `/opsx:propose <id>`), а содержимое proposal.md, specs/ и design.md написать по итогам brainstorming. tasks.md из OpenSpec не заполнять — задачи живут в плане superpowers.
|
||||
- `/opsx:apply` не использовать: исполнение идёт через writing-plans и SDD/executing-plans.
|
||||
- `openspec archive <id>` — после merge PR, как часть чеклиста закрытия.
|
||||
- Хук require-plan защищает любой каталог `src/` на любой глубине. Если код лежит иначе — перечислить каталоги (префиксы от корня, по одному в строке) в `.claude/code-paths`.
|
||||
|
||||
46
skills/harness-doctor/SKILL.md
Normal file
46
skills/harness-doctor/SKILL.md
Normal file
@ -0,0 +1,46 @@
|
||||
---
|
||||
name: harness-doctor
|
||||
description: Диагностика обвязки Claude Code (claude-harness) — когда хук сработал не так, codegraph пустой, агент пишет в main, тесты не видят ключ, обвязка «не подключена» или просто непонятно, что установлено. Запускает doctor.sh, читает отчёт по чеклисту, чинит то, что чинится безопасно, остальное формулирует как находку.
|
||||
---
|
||||
|
||||
# harness-doctor
|
||||
|
||||
Один прогон → один отчёт → одинаковое прочтение агентом и человеком.
|
||||
|
||||
## Когда запускать
|
||||
- Любой симптом про обвязку: «хук отбрил/пропустил», «codegraph возвращает пусто», «agent запушил в main», «SessionStart ругается», «tea не отвечает», «тесты делают платные запросы».
|
||||
- Перед первой фичей в новом или только что подключённом проекте.
|
||||
- По просьбе пользователя «продиагностируй обвязку» / «doctor».
|
||||
|
||||
## Как
|
||||
1. `bash ~/.claude/skills/harness-doctor/scripts/doctor.sh [папка проекта]` — только чтение, ничего не меняет. Отчёт целиком показать пользователю без сокращений.
|
||||
2. Прочитать по чеклисту ниже. Каждую строку `MISS`/`WARN` либо починить (если в списке «чинится само»), либо назвать находкой с маркером `O-n` и предложить действие.
|
||||
3. Если отчёт нужен человеку вне Claude Code — он вставляет его как есть; тот же чеклист.
|
||||
|
||||
## Чеклист прочтения
|
||||
|
||||
**Машина**
|
||||
- `MISS` бинарник → установить (`npm i -g @fission-ai/openspec`, `brew install codegraph tea`); не обходить, обвязка без него неполная.
|
||||
- `MISS plugin superpowers` → `/plugin install superpowers@claude-plugins-official`. `WARN plugin beads/template-bridge` → удалить, конфликтуют со skills superpowers.
|
||||
- `MISS`/`WARN hook …` → `bash <пакет>/install.sh`; «не зарегистрирован» — settings.json правился руками, тот же install.sh сольёт заново.
|
||||
- `WARN в окружении есть *_API_KEY` → ключ виден всем сабагентам и делает интеграционные тесты платными при каждом `npm test`. Убрать из профиля shell, передавать только в строке запуска. Это находка для пользователя, агент сам профиль не правит.
|
||||
- Глобальный CLAUDE.md без «маркировка: да» или «projectPath: да» → старая версия, `install.sh`.
|
||||
|
||||
**Самотест хуков** — любое `WARN … (ожидалось N)` означает, что хук установлен, но не делает того, что должен. Причины по убыванию частоты: правлен руками; `python3` не в PATH сессии; settings.json зовёт другой путь. Чинится `install.sh`; если и после него WARN — приложить строку к находке, это дефект пакета.
|
||||
|
||||
**Проект**
|
||||
- `MISS CLAUDE.md`/`openspec/`/`.codegraph/` или `WARN без секции` → обвязка не подключена: `bash ~/.claude/harness/adopt-project.sh <root>` (сам делает ветку и ff-merge; дерево должно быть чистым).
|
||||
- «worktree; основной: …» + «MCP смотрит на индекс основного checkout» → в вызовах `codegraph_explore` передавать `projectPath=<root worktree>`; если индекса в worktree нет — `codegraph init` там.
|
||||
- «ветка: detached» → не работать, вернуться на ветку; guard-main блокирует detached HEAD намеренно.
|
||||
- «чистое дерево: НЕТ» в основном checkout при закрытии → `git pull --ff-only` не делать, перечислить файлы пользователю.
|
||||
- «планы: 0» и нет `.bounded` при правке кода → require-plan заблокирует; это не ошибка хука, нужен brainstorming или `.bounded`.
|
||||
- «code-paths: умолчание» при коде вне `src/` → создать `.claude/code-paths`.
|
||||
- «SDD workspace: …» при отсутствии активной фичи → хвост незакрытой фичи; проверить чеклист закрытия.
|
||||
- `WARN .bounded не в .gitignore` → добавить строку, иначе уедет в main.
|
||||
- `WARN tea не отвечает` → `tea logins list`; токен нужен со scopes read:user, write:repository, write:issue.
|
||||
|
||||
## Чинится само (без вопроса)
|
||||
`install.sh`, `codegraph init` в worktree, `.claude/code-paths`, строка в `.gitignore` на ветке. Всё остальное — находка пользователю: профиль shell, плагины, токены, удаление чего-либо.
|
||||
|
||||
## Чего не делать
|
||||
Не «улучшать» хуки по месту: их источник — пакет claude-harness, правка идёт туда и ставится install.sh. Не отключать хук ради того, чтобы «прошло».
|
||||
77
skills/harness-doctor/scripts/doctor.sh
Executable file
77
skills/harness-doctor/scripts/doctor.sh
Executable file
@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
# claude-harness doctor: снимок состояния обвязки на машине и в проекте + самотест хуков.
|
||||
# Запуск: bash doctor.sh [папка проекта] — ничего не меняет, только читает.
|
||||
set -uo pipefail
|
||||
CC="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
||||
DIR="${1:-.}"
|
||||
ok(){ printf ' ok %s\n' "$*"; }; miss(){ printf ' MISS %s\n' "$*"; }; warn(){ printf ' WARN %s\n' "$*"; }; info(){ printf ' - %s\n' "$*"; }
|
||||
|
||||
echo "## harness-doctor $(date +%F' '%T)"
|
||||
echo "## Машина"
|
||||
for b in claude git python3 node npm tea openspec codegraph; do
|
||||
command -v "$b" >/dev/null 2>&1 && ok "$b $( { $b --version 2>/dev/null || true; } | head -1 | cut -c1-40)" || miss "$b"
|
||||
done
|
||||
if [ -f "$CC/plugins/installed_plugins.json" ]; then
|
||||
for p in superpowers caveman; do grep -q "\"$p@" "$CC/plugins/installed_plugins.json" && ok "plugin $p" || miss "plugin $p"; done
|
||||
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
|
||||
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"
|
||||
[ -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
|
||||
|
||||
echo "## Самотест хуков (фиктивные вызовы, ничего не пишется)"
|
||||
T=$(mktemp -d); git -C "$T" init -q -b main 2>/dev/null; mkdir -p "$T/src"
|
||||
hk(){ # name json expected
|
||||
local r; printf '%s' "$2" | bash "$CC/hooks/$1.sh" >/dev/null 2>&1; r=$?
|
||||
[ "$r" = "$3" ] && ok "$1: $4 → exit $r" || warn "$1: $4 → exit $r (ожидалось $3)"
|
||||
}
|
||||
if [ -x "$CC/hooks/guard-main.sh" ]; then
|
||||
hk guard-main "{\"cwd\":\"$T\",\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$T/x\"}}" 2 "Write на main"
|
||||
hk guard-main "{\"cwd\":\"$T\",\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"echo hi > x\"}}" 2 "Bash echo > на main"
|
||||
hk guard-main "{\"cwd\":\"$T\",\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git worktree add ../w -b f\"}}" 0 "git worktree на main"
|
||||
git -C "$T" switch -q -c feat/1-x 2>/dev/null
|
||||
hk guard-main "{\"cwd\":\"$T\",\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$T/x\"}}" 0 "Write на ветке"
|
||||
fi
|
||||
if [ -x "$CC/hooks/require-plan.sh" ]; then
|
||||
hk require-plan "{\"cwd\":\"$T\",\"tool_input\":{\"file_path\":\"$T/src/a.ts\"}}" 2 "src/ без плана"
|
||||
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
|
||||
rm -rf "$T"
|
||||
|
||||
echo "## Проект: $(cd "$DIR" 2>/dev/null && pwd)"
|
||||
if root=$(git -C "$DIR" rev-parse --show-toplevel 2>/dev/null); then
|
||||
main=$(git -C "$root" rev-parse --git-common-dir 2>/dev/null); main=$(cd "$root" && cd "$(dirname "$main")" 2>/dev/null && pwd)
|
||||
info "git root: $root$( [ "$main" != "$root" ] && echo " (worktree; основной: $main)")"
|
||||
info "ветка: $(git -C "$root" branch --show-current 2>/dev/null || echo 'detached') чистое дерево: $([ -z "$(git -C "$root" status --porcelain)" ] && echo да || echo НЕТ)"
|
||||
git -C "$root" worktree list 2>/dev/null | sed 's/^/ /'
|
||||
[ -f "$root/CLAUDE.md" ] && { grep -q '^## OpenSpec и superpowers' "$root/CLAUDE.md" && ok "CLAUDE.md с секцией харнеса" || warn "CLAUDE.md без секции «OpenSpec и superpowers»"; } || miss "CLAUDE.md"
|
||||
[ -f "$root/ARCHITECTURE.md" ] && ok "ARCHITECTURE.md" || info "ARCHITECTURE.md нет"
|
||||
if [ -d "$root/openspec" ]; then
|
||||
ok "openspec: specs $(ls "$root/openspec/specs" 2>/dev/null | wc -l | tr -d ' '), changes $(ls -d "$root"/openspec/changes/*/ 2>/dev/null | grep -v archive | wc -l | tr -d ' '), archive $(ls "$root/openspec/changes/archive" 2>/dev/null | wc -l | tr -d ' ')"
|
||||
for c in "$root"/openspec/changes/*/; do [ "$(basename "$c")" = archive ] && continue; [ -d "$c" ] || continue
|
||||
info "change $(basename "$c"): decisions $(grep -c '^[0-9]\+\. ' "$c/design.md" 2>/dev/null || echo 0)"; done
|
||||
else miss "openspec/"; fi
|
||||
if [ -d "$root/.codegraph" ]; then
|
||||
[ -f "$root/.codegraph/codegraph.db" ] && ok "codegraph: индекс есть$( [ -f "$root/.codegraph/daemon.pid" ] && echo ', демон pid '$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1]))["pid"])' "$root/.codegraph/daemon.pid" 2>/dev/null))" || warn "codegraph: папка есть, индекса нет — codegraph init"
|
||||
[ "$main" != "$root" ] && [ -f "$main/.codegraph/codegraph.db" ] && info "MCP, скорее всего, смотрит на индекс основного checkout: в вызовах нужен projectPath=$root"
|
||||
else miss ".codegraph/ (codegraph init)"; fi
|
||||
info "планы: $(ls "$root"/docs/superpowers/plans/*.md 2>/dev/null | wc -l | tr -d ' ')$( [ -f "$root/.bounded" ] && echo ' .bounded: есть')"
|
||||
[ -d "$root/.superpowers/sdd" ] && info "SDD workspace: $(ls "$root/.superpowers/sdd" | tr '\n' ' ')"
|
||||
[ -f "$root/.claude/code-paths" ] && info "code-paths: $(grep -v '^#' "$root/.claude/code-paths" | tr '\n' ' ')" || info "code-paths: умолчание (любой src/)"
|
||||
[ -f "$root/.claude/readonly-paths" ] && info "readonly-paths: $(grep -v '^#' "$root/.claude/readonly-paths" | tr '\n' ' ')"
|
||||
git -C "$root" check-ignore -q .bounded 2>/dev/null || warn ".bounded не в .gitignore"
|
||||
[ -d "$root/.git/hooks" ] && [ -x "$root/.git/hooks/pre-commit" ] && ok "git pre-commit есть" || info "git pre-commit нет"
|
||||
if command -v tea >/dev/null 2>&1 && git -C "$root" remote get-url origin >/dev/null 2>&1; 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
|
||||
Loading…
x
Reference in New Issue
Block a user