feat: install/doctor ставят и проверяют ядро и корпус хуков; документация 0.4.0 (#6)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
andrey.epifantsev 2026-10-16 01:59:26 +04:00
parent 3d54932709
commit 2fd2396c5c
10 changed files with 54 additions and 16 deletions

View File

@ -1,5 +1,15 @@
# Changelog
## 0.4.0 — 2026-10-16
- guard-main решает по цели записи, а не по cwd и тексту команды (#6, B1; RT-1, RT-2, RT-7). Цели — файл Edit/Write, в Bash — редиректы (кроме `/dev/null` и `N>&M`), команды записи с учётом `cd`, `git -C`, `sh -c`, git-мутации. Цель вне репозитория или в worktree на ветке — пропуск; неразрешимый путь — пропуск, git-мутация с неопределённым репозиторием — блок. `git push` блокируется, только если пушит в main; `git switch -c` в той же команде переводит последующие коммиты на новую ветку. `pip install` больше не блокируется. Спека — `openspec/changes/hooks-write-target/`.
- **Несовместимо:** исключение для openspec-only коммитов и `openspec archive` на main удалено — архив всегда отдельным PR (решение владельца 1, 11.10.2026).
- require-plan берёт корень, ветку, `.claude/code-paths`, `.bounded` и планы из репозитория правимого файла.
- Общее ядро `hooks/harness_target.py` (python3 stdlib); `guard-main.sh` и `require-plan.sh` — обёртки, регистрация в `settings.json` прежняя. Режим `targets` показывает, какие цели видит хук.
- Сообщения блокировки: `BLOCKED [guard-main]:` / `BLOCKED [require-plan]:`, цель, корень репозитория, выход.
- Регрессионный корпус хуков (B2, RT-6): 126 записей из транскриптов пилота + 3 живых случая, размечены по спеке (`docs/retro/scripts/hook_cases_build.py`), прогон в песочнице — `tests/test-hook-corpus.sh` по источникам и строка «корпус хуков» в `doctor.sh` по установленным копиям. В песочнице старые хуки дают на корпусе 41 блок (37 ложных), новые — 4 (архивные коммиты в main).
- `hook_blocks_export.py` и `retro_blocks.py` считают только ошибки хуков: из 126 выгруженных записей 49 оказались выводом команд со словом BLOCKED (O-1), счёт RT-1 в ретро завышен.
- Тесты: `test-hook-parse`, `test-hook-guard-main`, `test-hook-require-plan`, `test-hook-corpus`, `test-hook-export`.
## 0.3.0 — 2026-10-11
- Роли сессии `dev|admin|analyst|qa`: общий слой `roles/<r>.md` (`~/.claude/roles/`), проектный слой `<workspace>/.claude/roles/<r>.md`; формат из четырёх разделов (спека `docs/superpowers/specs/2026-10-11-roles-and-memory-design.md`).
- Skill `/role <r>`: читает оба слоя, раздел памяти роли, «Читать на старте»; строки `brief:` — фоновому агенту `briefing` (sonnet, только чтение).

View File

@ -4,17 +4,17 @@
Пакет обвязки для разработки с Claude Code: глобальный `CLAUDE.md` с процессом, три блокирующих хука и неблокирующий `role-hint`, скрипты подключения проекта и skill диагностики. Для одного разработчика, который ведёт фичи через superpowers, хранит задачи в Gitea и требования в OpenSpec. Главный сценарий: `bash install.sh` ставит глобальную часть в `~/.claude`, `adopt-project.sh` подключает существующий проект, `new-project.sh` создаёт новый; дальше проект живёт по `docs/HARNESS.md`. Репозиторий — источник; установленные копии в `~/.claude` по месту не правятся.
## Стек и ограничения
- Bash (`set -euo pipefail`) и markdown. JSON из stdin хуков разбирается встроенным `python3`; других зависимостей времени выполнения нет. Внешние CLI (`tea`, `openspec`, `codegraph`) проверяются, но не требуются для установки.
- Bash (`set -euo pipefail`), markdown и `python3` stdlib (3.8+). Логика guard-main и require-plan — `hooks/harness_target.py`, их `.sh` — обёртки; в остальных хуках и скриптах JSON разбирается встроенным `python3`. Других зависимостей времени выполнения нет. Внешние CLI (`tea`, `openspec`, `codegraph`) проверяются, но не требуются для установки.
- Хук блокирует вызов только через `exit 2` с сообщением в stderr; `exit 0` — пропустить. Любой сбой разбора входа должен приводить к пропуску, не к блокировке.
- `install.sh` идемпотентен: повторный запуск не ломает `settings.json` и делает бэкапы `CLAUDE.md` и `settings.json` с меткой времени.
- Хуки работают из `~/.claude/hooks/`, пути в `settings.hooks.json` через `$HOME`; `CLAUDE_CONFIG_DIR` переопределяет `~/.claude`.
- macOS и Linux; `python3` должен быть в PATH сессии Claude Code.
## Архитектура
- `hooks/` — поведение обвязки, единственный «код» с логикой. `guard-main.sh` (PreToolUse на Edit/Write/MultiEdit/Bash): запрет записи на main/master/detached HEAD, исключение для openspec-only коммитов. `require-plan.sh` (PreToolUse на Edit/Write/MultiEdit): правки в каталогах кода только при плане для ветки или файле `.bounded`. `harness-check.sh` (SessionStart): проверка, что проект подключён. `role-hint.sh` (SessionStart, все источники): подсказка роли по `session_title`. `settings.hooks.json` — блок, который `install.sh` сливает в `~/.claude/settings.json`.
- `hooks/` — поведение обвязки, единственный «код» с логикой. `guard-main.sh` (PreToolUse на Edit/Write/MultiEdit/Bash): запрет записи в репозиторий на main/master/detached HEAD по цели записи (файл, цели команды Bash, git-мутации), без исключений. `require-plan.sh` (PreToolUse на Edit/Write/MultiEdit): правки в каталогах кода репозитория файла только при плане для ветки или файле `.bounded`. Обе — обёртки над `harness_target.py`: `parse` (команда → цели, без ФС) → `resolve` (путь → корень и ветка) → `decide`; режим `targets` — отладка разбора. `harness-check.sh` (SessionStart): проверка, что проект подключён. `role-hint.sh` (SessionStart, все источники): подсказка роли по `session_title`. `settings.hooks.json` — блок, который `install.sh` сливает в `~/.claude/settings.json`.
- `install.sh`, `adopt-project.sh`, `new-project.sh` — установка и бутстрап. `install.sh` копирует `global/`, `hooks/`, `project-template/`, `adopt-project.sh`, `new-project.sh` и `skills/harness-doctor` в `~/.claude/{CLAUDE.md,hooks,harness,skills}`. `adopt-project.sh` работает на ветке `chore/harness-init` и вливает её ff в текущую ветку; это единственная разрешённая запись на main вне PR.
- `global/CLAUDE.md` — глобальные правила процесса, ставятся в `~/.claude/CLAUDE.md`. `project-template/` — `CLAUDE.md`, `CLAUDE.harness-section.md` (секция «OpenSpec и superpowers», по ней `harness-check` узнаёт подключённый проект), `ARCHITECTURE.md`, `gitignore`.
- `skills/harness-doctor/` — skill диагностики: `scripts/doctor.sh` снимает состояние машины и проекта и прогоняет самотест хуков; `SKILL.md` — чеклист прочтения отчёта.
- `skills/harness-doctor/` — skill диагностики: `scripts/doctor.sh` снимает состояние машины и проекта и прогоняет самотест хуков; `SKILL.md` — чеклист прочтения отчёта. `fixtures/hook-cases.jsonl` + `scripts/hook_corpus.py` — регрессионный корпус хуков (реальные команды с ожидаемым вердиктом, прогон в песочнице); лежит в skill, чтобы `doctor.sh` гонял его по установленным копиям.
- `roles/` — общий слой ролей (формат: четыре раздела, см. спеку ролей), `skills/role/` — skill `/role`, `agents/briefing.md` — агент выжимки, `link-roles.sh` — симлинки workspace → репозитории направлений. Ставятся `install.sh` в `~/.claude/{roles,skills/role,agents,harness}`.
- `tests/` — тесты пакета на bash: `run.sh` + `test-*.sh`, работают с источниками, `install.sh` ставят во временный `CLAUDE_CONFIG_DIR`.
- `caveman/config.json` — выключенный по умолчанию caveman. `examples/` — реальный `CLAUDE.md` проекта-пилота.
@ -28,12 +28,14 @@
- `bash skills/harness-doctor/scripts/doctor.sh [проект]` — отчёт и самотест хуков. Только чтение.
- `bash adopt-project.sh <папка> [--no-merge] [--push]` — подключить существующий проект. Из терминала, не из Claude Code.
- `bash new-project.sh <папка> [ssh-url]` — каркас нового проекта.
- `python3 docs/retro/scripts/retro_stats.py <каталог транскриптов>` и `retro_blocks.py` — статистика сессий и блокировок хуков для ретро; `hook_blocks_export.py <каталог> [...]` — выгрузка блокировок в JSONL (сырьё для тестов хуков).
- `python3 docs/retro/scripts/retro_stats.py <каталог транскриптов>` и `retro_blocks.py` — статистика сессий и блокировок хуков для ретро; `hook_blocks_export.py <каталог> [...]` — выгрузка блокировок в JSONL (сырьё для тестов хуков); `hook_cases_build.py > skills/harness-doctor/fixtures/hook-cases.jsonl` — сборка размеченного корпуса из выгрузки.
- `python3 skills/harness-doctor/scripts/hook_corpus.py hooks [--summary]` — корпус хуков по источникам. `python3 hooks/harness_target.py targets '<команда>' [cwd]` — какие цели записи видит хук.
## Тесты
- `tests/run.sh` — автоматические тесты источников (хуки через фиктивный JSON, скрипты во временных каталогах, размеры). Самотест хуков в `doctor.sh` проверяет установленные копии; перед ним `bash install.sh`.
- Ручная проверка хука: на main в любом репозитории попросить агента создать файл, ожидается `BLOCKED`.
- `tests/` покрывает `role-hint` и скрипты `install.sh`, `doctor.sh`, `link-roles.sh`; регрессионные тесты `guard-main` и `require-plan` на реальных командах из транскриптов (бэклог ретро B2) пока не сделаны.
- Хуки: `test-hook-parse` — разбор команды на цели; `test-hook-guard-main`, `test-hook-require-plan` — сценарии спеки `hook-guards` в песочнице из настоящих репозиториев; `test-hook-corpus` — корпус реальных команд (assert на запись, корпус должен совпадать со сборкой `hook_cases_build.py`); `test-hook-export` — экстрактор. Плюс `role-hint` и скрипты `install.sh`, `doctor.sh`, `link-roles.sh`.
- Правишь разбор или вердикт — сначала случай в `test-hook-parse`/`test-hook-guard-main`; меняется ожидаемый вердикт реальной команды — разметка в `hook_cases_build.py` и пересборка корпуса.
## Текущая работа
Бэклог обвязки — `docs/retro/2026-10-11.md`, раздел 4 (B1–B12), решения владельца — раздел 5. Находки ретро маркируются `RT-n`.

View File

@ -20,8 +20,9 @@
```
global/CLAUDE.md глобальные правила процесса → ~/.claude/CLAUDE.md
hooks/guard-main.sh запрет изменений на main/detached HEAD (Edit/Write/Bash); исключение — коммиты только в openspec/
hooks/require-plan.sh правки в src/ только при наличии плана или файла .bounded
hooks/guard-main.sh запрет изменений на main/master/detached HEAD (Edit/Write/Bash) по цели записи, без исключений
hooks/require-plan.sh правки в src/ (или .claude/code-paths) только при плане для ветки или файле .bounded
hooks/harness_target.py ядро обоих хуков: цели записи команды → репозиторий и ветка каждой цели
hooks/settings.hooks.json блок PreToolUse для ~/.claude/settings.json
caveman/config.json defaultMode: off → ~/.config/caveman/config.json
project-template/ CLAUDE.md, ARCHITECTURE.md, gitignore для нового проекта

View File

@ -69,13 +69,15 @@ 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/` (архивация) |
| Правки в коде только при плане или `.bounded` | `require-plan.sh`, PreToolUse на Edit/Write/MultiEdit | защищает любой `src/` на любой глубине (`api/src/`, `packages/*/src/`) или каталоги из `.claude/code-paths`; ищет план в `docs/superpowers/plans/` для текущей ветки или файл `.bounded` |
| Никаких изменений на main / master / detached HEAD | `guard-main.sh` → `harness_target.py`, PreToolUse на Edit/Write/MultiEdit/Bash | решает по цели записи, а не по cwd. Цели: файл Edit/Write; в Bash — редиректы (кроме `/dev/null` и `N>&M`), `tee`, `rm`, `mv`, `cp`, `sed -i`, `npm install`, `openspec archive`… с учётом `cd` и `git -C`; git-мутации (`commit`, `add`, `merge`, `reset`…; `push` — только если пушит в main; `switch -c` в той же команде меняет ветку). Цель вне репозитория или в worktree на ветке — пропуск; неразрешимый путь (`$VAR`) — пропуск; git-мутация с неопределённым репозиторием — блок. Исключений нет: архив OpenSpec — отдельным PR |
| Правки в коде только при плане или `.bounded` | `require-plan.sh` → `harness_target.py`, PreToolUse на Edit/Write/MultiEdit | по репозиторию правимого файла, а не по cwd: защищает любой `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` — без вопроса) |
| Подсказка роли по заголовку сессии | `role-hint.sh`, SessionStart (все источники) | читает `session_title`; если начинается с «dev, admin, analyst или qa» — одна строка-подсказка вызвать `/role`; ничего не блокирует |
| Тесты зелёные перед коммитом | git pre-commit в проекте (не входит в пакет) | обычный git-хук с командой тестов |
Exit 2 блокирует вызов и показывает агенту stderr, поэтому агент сам делает то, что просит сообщение. Оба обхода, замеченные на пилоте (запись через `echo >` вместо Write; push через detached HEAD), закрыты.
Exit 2 блокирует вызов и показывает агенту stderr, поэтому агент сам делает то, что просит сообщение. Сообщение начинается с `BLOCKED [guard-main]:` или `BLOCKED [require-plan]:` и называет цель, репозиторий и выход. Оба обхода, замеченные на пилоте (запись через `echo >` вместо Write; push через detached HEAD), закрыты.
Регрессия хуков — корпус `skills/harness-doctor/fixtures/hook-cases.jsonl`: реальные команды из транскриптов пилота с ожидаемым вердиктом. По источникам — `bash tests/run.sh hook`, по установленным копиям — строка «корпус хуков» в `doctor.sh`. Корпус собирается из сырой выгрузки `docs/retro/scripts/hook_cases_build.py`; разметка — в этом скрипте. Разбор одной команды: `python3 ~/.claude/hooks/harness_target.py targets '<команда>' <cwd>`.
## Дисциплина между сессиями

View File

@ -25,7 +25,7 @@ check() {
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-check.sh hooks/role-hint.sh \
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; }
@ -45,7 +45,8 @@ 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" "$CC/hooks/"
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"
@ -93,6 +94,6 @@ 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, $CC/roles/, $CC/skills/role, $CC/agents/briefing.md, $CC/harness/, ~/.config/caveman/config.json"
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

View File

@ -17,7 +17,7 @@
### Исполнение
- Работа только в worktree. Ветка называется по issue: `feat/<issue-id>-<slug>`. Пути worktree в планах и брифах — абсолютные.
- Пока работает фоновый SDD-контроллер, в главной сессии не делать `cd` в основной checkout: cwd оболочки общий, контроллер и implementer-ы получат `BLOCKED: shell write on main` и остановятся.
- Пока работает фоновый SDD-контроллер, в главной сессии не делать `cd` в основной checkout: cwd оболочки общий, и команды implementer-ов с относительными путями пойдут в main — guard-main их заблокирует, и они остановятся. Команды с абсолютными путями worktree и `git -C <worktree>` от cwd не зависят.
- Режим по умолчанию: subagent-driven-development. Контроллер SDD запускается фоновым сабагентом **на модели sonnet** (не на модели сессии); сессия только ждёт его отчёт. Для планов до 5 задач — executing-plans (Native). Режим не спрашивать, он задан здесь.
- Модели исполнителей по SDD Model Selection: задача с готовым кодом в плане — haiku, интеграция — sonnet, ревью всей ветки — opus. Модель в dispatch указывать всегда.
- Ревьюер задачи проверяет trailer коммита (`Co-Authored-By`, как указано в плане); чужой trailer — находка `T<n>.<k>`, переписывать историю нельзя (force-push запрещён).

View File

@ -30,6 +30,8 @@ description: Диагностика обвязки Claude Code (claude-harness)
**Самотест хуков** — любое `WARN … (ожидалось N)` означает, что хук установлен, но не делает того, что должен. Причины по убыванию частоты: правлен руками; `python3` не в PATH сессии; settings.json зовёт другой путь. Чинится `install.sh`; если и после него WARN — приложить строку к находке, это дефект пакета.
**Корпус хуков** — `ok корпус хуков: N/N`: установленные хуки дают на реальных командах пилота те же вердикты, что размечены. `WARN корпус хуков: N/M` и строки над ним — расхождения: `id`, класс, ожидалось, получено, команда. После `install.sh` расхождение — регрессия пакета; разобрать одну команду: `python3 ~/.claude/hooks/harness_target.py targets '<команда>' <cwd>`. `MISS корпус хуков` — нет `harness_target.py` или раннера, чинится `install.sh`.
**Проект**
- `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` там.

View File

@ -22,6 +22,7 @@ for h in guard-main require-plan harness-check role-hint; do
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
[ -f "$CC/hooks/harness_target.py" ] && ok "hooks/harness_target.py (ядро guard-main и require-plan)" || miss "hooks/harness_target.py (bash install.sh)"
[ -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
@ -43,6 +44,9 @@ 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"
mkdir -p "$T/openspec"; echo a > "$T/openspec/x.md"; git -C "$T" add openspec 2>/dev/null
hk guard-main "{\"cwd\":\"$T\",\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git commit -m archive\"}}" 2 "git commit openspec-only на main"
git -C "$T" reset -q 2>/dev/null
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
@ -58,8 +62,14 @@ if [ -x "$CC/hooks/role-hint.sh" ]; then
[ -z "$o" ] && ok "role-hint: без заголовка → тишина" || warn "role-hint: без заголовка → '$o' (ожидалась тишина)"
fi
rm -rf "$T"
HC="$CC/skills/harness-doctor/scripts/hook_corpus.py"
if [ -f "$HC" ] && [ -f "$CC/hooks/harness_target.py" ]; then
out=$(python3 "$HC" "$CC/hooks" --summary 2>&1); rc=$?
printf '%s\n' "$out" | grep '^MISMATCH' | sed 's/^MISMATCH / /'
[ "$rc" = 0 ] && ok "$(printf '%s\n' "$out" | tail -1)" || warn "$(printf '%s\n' "$out" | tail -1) — расхождения выше"
else miss "корпус хуков (skills/harness-doctor/scripts/hook_corpus.py, hooks/harness_target.py)"; fi
echo "## Проект: $(cd "$DIR" 2>/dev/null && pwd)"
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)")"

View File

@ -1,7 +1,8 @@
# 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"/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" "$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"
@ -19,6 +20,11 @@ assert_grep "машина: агент briefing" 'ok +agent briefing' <(printf '%
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 "проект: режим workspace" 'workspace' <(printf '%s' "$out")
assert_grep "проект: целая ссылка" 'ok +roles/admin.md' <(printf '%s' "$out")
assert_grep "проект: битая ссылка" 'WARN +agents/none.md' <(printf '%s' "$out")

View File

@ -8,6 +8,9 @@ assert_file "skills/role/SKILL.md установлен" "$CC/skills/role/SKILL.m
assert_file "agents/briefing.md установлен" "$CC/agents/briefing.md"
assert_file "hooks/role-hint.sh установлен" "$CC/hooks/role-hint.sh"
assert_eq "hooks/role-hint.sh исполняемый" "$([ -x "$CC/hooks/role-hint.sh" ] && echo x)" "x"
assert_file "hooks/harness_target.py установлен" "$CC/hooks/harness_target.py"
assert_file "корпус хуков установлен" "$CC/skills/harness-doctor/fixtures/hook-cases.jsonl"
assert_file "раннер корпуса установлен" "$CC/skills/harness-doctor/scripts/hook_corpus.py"
assert_file "harness/link-roles.sh установлен" "$CC/harness/link-roles.sh"
assert_file "harness/project-template/workspace" "$CC/harness/project-template/workspace/CLAUDE.md"
assert_file "harness/project-template/role-repo" "$CC/harness/project-template/role-repo/ROLE.md"
@ -19,6 +22,7 @@ out=$(HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh"
assert_grep "--check: roles" 'roles/' <(printf '%s' "$out")
assert_grep "--check: skill role" 'skills/role' <(printf '%s' "$out")
assert_grep "--check: agent briefing" 'agents/briefing' <(printf '%s' "$out")
assert_grep "--check: harness_target.py" 'ok +hooks/harness_target\.py' <(printf '%s' "$out")
rm "$CC/agents/briefing.md"
out=$(HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" --check 2>&1)
assert_grep "--check: MISS briefing" 'MISS +.*briefing' <(printf '%s' "$out")