Merge pull request 'install.sh ставит allow-правила цикла фичи, --no-permissions для отказа (#24, 0.5.0)' (#26) from feat/24-harness-permissions into main

This commit is contained in:
aep 2026-10-23 23:04:32 +03:00
commit 04369f1b0a
15 changed files with 145 additions and 21 deletions

View File

@ -1,5 +1,11 @@
# Changelog # Changelog
## 0.5.0 — 2026-10-23
- **`install.sh` ставит allow-правила цикла фичи** (#24; пересмотр решения владельца 2). Список — `global/permissions.json`: `git worktree`, `git branch -m/-D`, `git push -u origin feat/…`/`chore/…`, `git push origin --delete`, `tea issues`, `tea pulls create/list/edit`, `tea comments`. Merge и push в main в список не входят. Правила только дописываются в `permissions.allow` файла `~/.claude/settings.json`, чужие не трогаются; копия списка — `~/.claude/harness/permissions.json`. `--no-permissions` пропускает шаг. `--check` и `doctor.sh` показывают `permissions обвязки: N/M` и недостающие правила.
- `adopt-project.sh` и `new-project.sh` печатают строку `push --delete` для установки с `--no-permissions`. WARN проверки в `doctor.sh` подсказывает `bash install.sh`.
- `EnterWorktree` в список не входит. Вход в worktree вне `<repo>/.claude/worktrees/` спрашивает подтверждение при любых allow-правилах (Claude Code ≥ 2.1.206); перенос worktree — #25.
- harness-doctor: агент чинит `install.sh --no-permissions`, права ставит владелец.
## 0.4.2 — 2026-10-23 ## 0.4.2 — 2026-10-23
- Удаление ветки фичи на сервере (чеклист закрытия, п. 4; B3, RT-3, решение владельца 2): `doctor.sh` в разделе «Проект» ищет allow-правило для `git push origin --delete` в `.claude/settings.json`, `settings.local.json` проекта и в глобальном `settings.json` — `ok` с файлом или `WARN` с готовой строкой `"Bash(git push origin --delete:*)"`. Строку печатают `adopt-project.sh` и `new-project.sh` во всех режимах (раньше — только `adopt --workspace`); файл настроек они не пишут, правило добавляет владелец (#22). - Удаление ветки фичи на сервере (чеклист закрытия, п. 4; B3, RT-3, решение владельца 2): `doctor.sh` в разделе «Проект» ищет allow-правило для `git push origin --delete` в `.claude/settings.json`, `settings.local.json` проекта и в глобальном `settings.json` — `ok` с файлом или `WARN` с готовой строкой `"Bash(git push origin --delete:*)"`. Строку печатают `adopt-project.sh` и `new-project.sh` во всех режимах (раньше — только `adopt --workspace`); файл настроек они не пишут, правило добавляет владелец (#22).
- `HARNESS.md`: шаг 10 — чеклист из 8 пунктов в `roles/dev.md` и рецепт п. 4. `roles/dev.md`, п. 4: `git worktree remove` раньше `git branch -D` (ветку, выведенную в worktree, git не удаляет). - `HARNESS.md`: шаг 10 — чеклист из 8 пунктов в `roles/dev.md` и рецепт п. 4. `roles/dev.md`, п. 4: `git worktree remove` раньше `git branch -D` (ветку, выведенную в worktree, git не удаляет).

View File

@ -6,14 +6,14 @@
## Стек и ограничения ## Стек и ограничения
- Bash (`set -euo pipefail`), markdown и `python3` stdlib (3.8+). Логика guard-main и require-plan — `hooks/harness_target.py`, их `.sh` — обёртки; в остальных хуках и скриптах JSON разбирается встроенным `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` — пропустить. Любой сбой разбора входа должен приводить к пропуску, не к блокировке. - Хук блокирует вызов только через `exit 2` с сообщением в stderr; `exit 0` — пропустить. Любой сбой разбора входа должен приводить к пропуску, не к блокировке.
- `install.sh` идемпотентен: повторный запуск не ломает `settings.json` и делает бэкапы `CLAUDE.md` и `settings.json` с меткой времени. - `install.sh` идемпотентен: повторный запуск не ломает `settings.json` и делает бэкапы `CLAUDE.md` и `settings.json` с меткой времени. В `settings.json` он сливает `hooks` и только дописывает `permissions.allow` из `global/permissions.json`; чужие правила не трогает. Агент запускает его только с `--no-permissions`: права ставит владелец.
- Хуки работают из `~/.claude/hooks/`, пути в `settings.hooks.json` через `$HOME`; `CLAUDE_CONFIG_DIR` переопределяет `~/.claude`. - Хуки работают из `~/.claude/hooks/`, пути в `settings.hooks.json` через `$HOME`; `CLAUDE_CONFIG_DIR` переопределяет `~/.claude`.
- macOS и Linux; `python3` должен быть в PATH сессии Claude Code. - macOS и Linux; `python3` должен быть в PATH сессии Claude Code.
## Архитектура ## Архитектура
- `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`. - `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. - `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`. - `global/CLAUDE.md` — глобальные правила процесса, ставятся в `~/.claude/CLAUDE.md`. `global/permissions.json` — allow-правила цикла фичи (без merge и push в main), `install.sh` дописывает их в `~/.claude/settings.json` и кладёт копию в `~/.claude/harness/` для `doctor.sh`. `project-template/` — `CLAUDE.md`, `CLAUDE.harness-section.md` (секция «OpenSpec и superpowers», по ней `harness-check` узнаёт подключённый проект), `ARCHITECTURE.md`, `gitignore`.
- `skills/harness-doctor/` — skill диагностики: `scripts/doctor.sh` снимает состояние машины и проекта и прогоняет самотест хуков; `SKILL.md` — чеклист прочтения отчёта. `fixtures/hook-cases.jsonl` + `scripts/hook_corpus.py` — регрессионный корпус хуков (реальные команды с ожидаемым вердиктом, прогон в песочнице); лежит в skill, чтобы `doctor.sh` гонял его по установленным копиям. - `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}`. - `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`. - `tests/` — тесты пакета на bash: `run.sh` + `test-*.sh`, работают с источниками, `install.sh` ставят во временный `CLAUDE_CONFIG_DIR`.
@ -23,8 +23,8 @@
## Команды ## Команды
- `bash tests/run.sh [имя]` — весь набор тестов. Зелёным считается только весь набор. - `bash tests/run.sh [имя]` — весь набор тестов. Зелёным считается только весь набор.
- `bash install.sh --check` — проверка окружения: бинарники, плагины, конфликты. - `bash install.sh --check` — проверка окружения: бинарники, плагины, конфликты, allow-правила обвязки.
- `bash install.sh` — установка глобальной части, идемпотентно. - `bash install.sh [--no-permissions]` — установка глобальной части, идемпотентно; `--no-permissions` — без allow-правил.
- `bash skills/harness-doctor/scripts/doctor.sh [проект]` — отчёт и самотест хуков. Только чтение. - `bash skills/harness-doctor/scripts/doctor.sh [проект]` — отчёт и самотест хуков. Только чтение.
- `bash adopt-project.sh <папка> [--no-merge] [--push]` — подключить существующий проект. Из терминала, не из Claude Code. - `bash adopt-project.sh <папка> [--no-merge] [--push]` — подключить существующий проект. Из терминала, не из Claude Code.
- `bash new-project.sh <папка> [ssh-url]` — каркас нового проекта. - `bash new-project.sh <папка> [ssh-url]` — каркас нового проекта.
@ -42,7 +42,8 @@
- Сделано: B5+B6 часть 1 — слой ролей (PR #4, 0.3.0). Часть 2 (awp-ca) — issue #2, отложенные находки — issue #5. - Сделано: B5+B6 часть 1 — слой ролей (PR #4, 0.3.0). Часть 2 (awp-ca) — issue #2, отложенные находки — issue #5.
- Сделано: B1+B2 и отмена исключения openspec-only для guard-main — хуки по цели записи и корпус хуков (issue #6, PR #12, 0.4.0), спека — `openspec/specs/hook-guards/`. Отложенные находки — issue #13. - Сделано: B1+B2 и отмена исключения openspec-only для guard-main — хуки по цели записи и корпус хуков (issue #6, PR #12, 0.4.0), спека — `openspec/specs/hook-guards/`. Отложенные находки — issue #13.
- Сделано: B3 — allow-правило `git push origin --delete` печатают adopt/new-project, проверяет doctor (issue #22, 0.4.2). B4 закрыт в #6 (решение владельца 1). B10 — workspace-хук awp-ca, в части 2 issue #2. - Сделано: B3 — allow-правило `git push origin --delete` печатают adopt/new-project, проверяет doctor (issue #22, 0.4.2). B4 закрыт в #6 (решение владельца 1). B10 — workspace-хук awp-ca, в части 2 issue #2.
- Дальше: B12. - Сделано: allow-правила цикла фичи ставит `install.sh` (issue #24, 0.5.0; пересмотр решения владельца 2).
- Дальше: worktree в `.claude/worktrees/` (issue #25), затем B12.
## OpenSpec и superpowers ## OpenSpec и superpowers
- Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать. - Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать.

View File

@ -27,7 +27,8 @@ hooks/settings.hooks.json блок PreToolUse для ~/.claude/settings.json
caveman/config.json defaultMode: off → ~/.config/caveman/config.json caveman/config.json defaultMode: off → ~/.config/caveman/config.json
project-template/ CLAUDE.md, ARCHITECTURE.md, gitignore для нового проекта project-template/ CLAUDE.md, ARCHITECTURE.md, gitignore для нового проекта
examples/ реальный CLAUDE.md проекта-пилота examples/ реальный CLAUDE.md проекта-пилота
install.sh установка глобальной части, идемпотентно; --check проверяет окружение global/permissions.json allow-правила цикла фичи (worktree, tea, push фичевых веток, удаление веток) → ~/.claude/settings.json
install.sh установка глобальной части, идемпотентно; --check проверяет окружение; --no-permissions — без allow-правил
new-project.sh каркас нового проекта: git, CLAUDE.md, openspec init, codegraph init, remote new-project.sh каркас нового проекта: git, CLAUDE.md, openspec init, codegraph init, remote
roles/<r>.md общий слой ролей dev/admin/analyst/qa → ~/.claude/roles/ roles/<r>.md общий слой ролей dev/admin/analyst/qa → ~/.claude/roles/
skills/role/SKILL.md /role <r>: загрузить роль, память, запустить briefing → ~/.claude/skills/role/ skills/role/SKILL.md /role <r>: загрузить роль, память, запустить briefing → ~/.claude/skills/role/
@ -82,7 +83,9 @@ claude mcp add-json -s user codegraph '{"type":"stdio","command":"codegraph","ar
# 3. Глобальная часть # 3. Глобальная часть
bash install.sh --check bash install.sh --check
bash install.sh bash install.sh # дописывает в ~/.claude/settings.json allow-правила из global/permissions.json;
# чужие правила не трогает. Без них: bash install.sh --no-permissions.
# Нет tea pulls merge и push в main: merge — по вашей команде.
# 4. Проверка хука: в любом репозитории на main запустить claude и попросить создать файл. # 4. Проверка хука: в любом репозитории на main запустить claude и попросить создать файл.
# Ожидается BLOCKED и предложение создать worktree. # Ожидается BLOCKED и предложение создать worktree.

View File

@ -17,7 +17,7 @@ if [ "${1:-}" = "--workspace" ]; then
Workspace $NAME подключён. Дальше: Workspace $NAME подключён. Дальше:
1. Заполни таблицу репозиториев в CLAUDE.md и перезапусти: bash $HERE/link-roles.sh $(pwd) 1. Заполни таблицу репозиториев в CLAUDE.md и перезапусти: bash $HERE/link-roles.sh $(pwd)
2. Разреши агенту удалять ветки на сервере (чеклист закрытия, п. 4) — строка в .claude/settings.json → permissions.allow: 2. Если обвязка ставилась с --no-permissions: разреши агенту удалять ветки на сервере (чеклист закрытия, п. 4) — строка в .claude/settings.json → permissions.allow:
"Bash(git push origin --delete:*)" "Bash(git push origin --delete:*)"
3. Сессии: claude -n "dev …" | "admin …" | "qa …" | "analyst …" из этого каталога. 3. Сессии: claude -n "dev …" | "admin …" | "qa …" | "analyst …" из этого каталога.
MSG MSG
@ -73,7 +73,7 @@ fi
cat <<MSG cat <<MSG
Проект $NAME подключён к обвязке. Дальше: Проект $NAME подключён к обвязке. Дальше:
1. Разреши агенту удалять ветки на сервере (чеклист закрытия, п. 4) — строка в .claude/settings.json → permissions.allow: 1. Если обвязка ставилась с --no-permissions: разреши агенту удалять ветки на сервере (чеклист закрытия, п. 4) — строка в .claude/settings.json → permissions.allow:
"Bash(git push origin --delete:*)" "Bash(git push origin --delete:*)"
2. cd $(pwd) && claude 2. cd $(pwd) && claude
«Опиши проект в CLAUDE.md по коду: разделы «Что это», «Стек», «Архитектура», «Команды». Bounded-задача.» «Опиши проект в CLAUDE.md по коду: разделы «Что это», «Стек», «Архитектура», «Команды». Bounded-задача.»

View File

@ -29,7 +29,9 @@
Шаги 6–8 идут без остановок. Агент останавливается только на необратимой операции, вопросе безопасности, побочном эффекте вне worktree или плане, который невозможно исполнить. Шаги 6–8 идут без остановок. Агент останавливается только на необратимой операции, вопросе безопасности, побочном эффекте вне worktree или плане, который невозможно исполнить.
**Удаление ветки фичи (п. 4 закрытия).** `git push origin --delete <ветка>` из worktree фичи, затем из основного checkout `git worktree remove <путь>` и `git branch -D <ветка>`. guard-main с 0.4.0 эту команду не блокирует, но её может отклонить классификатор auto mode. Поэтому в проекте нужно allow-правило: строка `"Bash(git push origin --delete:*)"` в `.claude/settings.json` → `permissions.allow`. Строку добавляете вы (решение владельца 2); `adopt-project.sh` и `new-project.sh` её печатают, `doctor.sh` показывает, найдена ли она. **Удаление ветки фичи (п. 4 закрытия).** `git push origin --delete <ветка>` из worktree фичи, затем из основного checkout `git worktree remove <путь>` и `git branch -D <ветка>`. guard-main с 0.4.0 эту команду не блокирует, но её может отклонить классификатор auto mode. Поэтому нужно allow-правило `"Bash(git push origin --delete:*)"`. Его вместе с остальными правилами цикла фичи (`global/permissions.json`) ставит `bash install.sh` в `~/.claude/settings.json`. Запускаете его вы: агент себе права не выдаёт. Если обвязка стоит с `--no-permissions`, строку добавляете в `.claude/settings.json` проекта сами: её печатают `adopt-project.sh` и `new-project.sh`, а `doctor.sh` показывает, найдена ли она.
**Подтверждения.** В обычном режиме Claude Code спрашивает подтверждение на всё, что не покрыто allow-правилами. В auto mode решает классификатор: он пропускает или запрещает, но вас не спрашивает. Allow-правила проверяются раньше него. Вход в worktree вне `<repo>/.claude/worktrees/` спрашивает подтверждение при любых правилах, перенос worktree — #25.
**Bounded-задача** (флаг, фикс) идёт короче: дизайн в чате, файл `.bounded` в корне worktree (пропускает хук require-plan), TDD, коммит, PR. Без OpenSpec change и без плана. **Bounded-задача** (флаг, фикс) идёт короче: дизайн в чате, файл `.bounded` в корне worktree (пропускает хук require-plan), TDD, коммит, PR. Без OpenSpec change и без плана.

View File

@ -173,6 +173,8 @@ requesting-code-review 0 раз. Закрытие и ревью идут по ч
«в main только через PR» становится без исключений. (B4 → переформулирован.) «в main только через PR» становится без исключений. (B4 → переформулирован.)
2. **Удаление серверных веток — allow-правило в настройках проекта.** Строку правила владелец добавляет сам; обвязка даёт 2. **Удаление серверных веток — allow-правило в настройках проекта.** Строку правила владелец добавляет сам; обвязка даёт
готовую строку в `adopt-project.sh` и в отчёте `doctor.sh`. Чеклист закрытия п. 4 остаётся за агентом. (B3.) готовую строку в `adopt-project.sh` и в отчёте `doctor.sh`. Чеклист закрытия п. 4 остаётся за агентом. (B3.)
*Пересмотрено 23.10.2026 (#24): правило вместе с остальными allow-правилами цикла фичи ставит `install.sh`
в `~/.claude/settings.json`; отказ — `--no-permissions`, тогда строку по-прежнему печатают adopt/new-project и doctor.*
3. **codegraph — оставить и замерить.** Один срез с обязательным codegraph и подсчётом вызовов `codegraph_explore` против 3. **codegraph — оставить и замерить.** Один срез с обязательным codegraph и подсчётом вызовов `codegraph_explore` против
Bash/Read в тех же задачах; решение после замера. Метрика входит в `/harness-retro`. (B8 → замер, не вывод из потока.) Bash/Read в тех же задачах; решение после замера. Метрика входит в `/harness-retro`. (B8 → замер, не вывод из потока.)
4. **Память — политика «состояние задачи в Gitea, в память только уроки, фидбек и знания роли» и одноразовая чистка 4. **Память — политика «состояние задачи в Gitea, в память только уроки, фидбек и знания роли» и одноразовая чистка

17
global/permissions.json Normal file
View File

@ -0,0 +1,17 @@
{
"permissions": {
"allow": [
"Bash(git worktree:*)",
"Bash(git branch -m:*)",
"Bash(git branch -D:*)",
"Bash(git push -u origin feat/:*)",
"Bash(git push -u origin chore/:*)",
"Bash(git push origin --delete:*)",
"Bash(tea issues:*)",
"Bash(tea pulls create:*)",
"Bash(tea pulls list:*)",
"Bash(tea pulls edit:*)",
"Bash(tea comments:*)"
]
}
}

View File

@ -1,6 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# claude-harness: установка глобальной части (CLAUDE.md, хуки, конфиг caveman, слияние settings.json). # claude-harness: установка глобальной части (CLAUDE.md, хуки, конфиг caveman, слияние settings.json).
# Идемпотентно. Запуск: bash install.sh | проверка окружения: bash install.sh --check # Идемпотентно. Запуск: bash install.sh [--no-permissions] | проверка окружения: bash install.sh --check
# --no-permissions — не дописывать allow-правила цикла фичи (global/permissions.json) в settings.json.
set -euo pipefail set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)" HERE="$(cd "$(dirname "$0")" && pwd)"
CC="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" CC="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
@ -24,6 +25,23 @@ check() {
if [ -f "$CC/settings.json" ] && grep -q 'codegraph prompt-hook' "$CC/settings.json"; then if [ -f "$CC/settings.json" ] && grep -q 'codegraph prompt-hook' "$CC/settings.json"; then
echo " WARN codegraph prompt-hook в settings.json — рекомендуется отключить на время пилота" echo " WARN codegraph prompt-hook в settings.json — рекомендуется отключить на время пилота"
fi 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)" 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 \ 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 \ roles/dev.md roles/admin.md roles/analyst.md roles/qa.md skills/role/SKILL.md agents/briefing.md \
@ -33,7 +51,14 @@ check() {
return $ok return $ok
} }
if [ "${1:-}" = "--check" ]; then check; exit $?; fi 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" mkdir -p "$CC/hooks" "$HOME/.config/caveman"
stamp=$(date +%Y%m%d-%H%M%S) stamp=$(date +%Y%m%d-%H%M%S)
@ -50,7 +75,7 @@ cp "$HERE/hooks/guard-main.sh" "$HERE/hooks/require-plan.sh" "$HERE/hooks/harnes
# 2a. Скрипты бутстрапа проекта — агент запускает их из сессии # 2a. Скрипты бутстрапа проекта — агент запускает их из сессии
mkdir -p "$CC/harness" mkdir -p "$CC/harness"
rm -rf "$CC/harness/project-template" 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/" 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 chmod +x "$CC/harness/"*.sh
# 2b. Skill диагностики # 2b. Skill диагностики
mkdir -p "$CC/skills" mkdir -p "$CC/skills"
@ -94,6 +119,22 @@ json.dump(s, open(sp,"w"), ensure_ascii=False, indent=2)
print("settings.json: hooks merged") print("settings.json: hooks merged")
PY 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 "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" echo "Плагины Claude Code ставятся вручную: /plugin install superpowers@claude-plugins-official"
check || true check || true

View File

@ -32,7 +32,7 @@ if [ "${1:-}" = "--workspace" ]; then
Workspace $NAME готов. Дальше: Workspace $NAME готов. Дальше:
1. Заполни {{...}} в CLAUDE.md и в .claude/roles/dev.md; в репозиториях направлений — .claude/roles/<r>.md. 1. Заполни {{...}} в CLAUDE.md и в .claude/roles/dev.md; в репозиториях направлений — .claude/roles/<r>.md.
2. Заведи репозитории направлений в Gitea и добавь remote (tea repos create …; git remote add origin …). 2. Заведи репозитории направлений в Gitea и добавь remote (tea repos create …; git remote add origin …).
3. Разреши агенту удалять ветки на сервере (чеклист закрытия, п. 4) — строка в .claude/settings.json → permissions.allow: 3. Если обвязка ставилась с --no-permissions: разреши агенту удалять ветки на сервере (чеклист закрытия, п. 4) — строка в .claude/settings.json → permissions.allow:
"Bash(git push origin --delete:*)" "Bash(git push origin --delete:*)"
4. cd $(pwd) && claude -n "dev …" 4. cd $(pwd) && claude -n "dev …"
MSG MSG
@ -70,7 +70,7 @@ cat <<MSG
Проект $NAME готов. Дальше: Проект $NAME готов. Дальше:
1. Заполните {{...}} в CLAUDE.md и ARCHITECTURE.md. 1. Заполните {{...}} в CLAUDE.md и ARCHITECTURE.md.
2. Положите первые фикстуры в tests/fixtures/. 2. Положите первые фикстуры в tests/fixtures/.
3. Разрешите агенту удалять ветки на сервере (чеклист закрытия, п. 4) — строка в .claude/settings.json → permissions.allow: 3. Если обвязка ставилась с --no-permissions: разрешите агенту удалять ветки на сервере (чеклист закрытия, п. 4) — строка в .claude/settings.json → permissions.allow:
"Bash(git push origin --delete:*)" "Bash(git push origin --delete:*)"
4. cd $DIR && claude 4. cd $DIR && claude
«Хочу сделать ... Описание и ограничения в CLAUDE.md. Давай начнём.» «Хочу сделать ... Описание и ограничения в CLAUDE.md. Давай начнём.»

View File

@ -29,7 +29,7 @@
1. `openspec archive <id>` — всегда отдельным PR (ветка `chore/archive-<id>` из main, `tea pulls create`, merge по команде человека). Прямых коммитов в main нет, исключений нет (решение владельца 11.10.2026). 1. `openspec archive <id>` — всегда отдельным PR (ветка `chore/archive-<id>` из main, `tea pulls create`, merge по команде человека). Прямых коммитов в main нет, исключений нет (решение владельца 11.10.2026).
2. Issue закрывается (`tea issues close <id>`, комментарий со ссылкой на PR) только если все её пункты выполнены. Если PR закрывает часть пунктов — issue остаётся открытой, в неё пишется комментарий: какие маркеры закрыты, каким PR и коммитом. 2. Issue закрывается (`tea issues close <id>`, комментарий со ссылкой на PR) только если все её пункты выполнены. Если PR закрывает часть пунктов — issue остаётся открытой, в неё пишется комментарий: какие маркеры закрыты, каким PR и коммитом.
3. Удалить SDD workspace (`.superpowers/sdd/<plan>/`). 3. Удалить SDD workspace (`.superpowers/sdd/<plan>/`).
4. Удалить ветку фичи на сервере и локально: `git push origin --delete <ветка>` выполняется из worktree на ветке фичи (не из main-checkout), разрешён allow-правилом проекта (`Bash(git push origin --delete:*)` в `.claude/settings.json`; строку добавляет владелец, её печатают `adopt-project.sh` и `new-project.sh`, есть ли она — показывает `doctor.sh`); затем из основного checkout `git worktree remove`, потом `git branch -D`. Остальные worktree не трогать. Обходы через API Gitea запрещены. 4. Удалить ветку фичи на сервере и локально: `git push origin --delete <ветка>` выполняется из worktree на ветке фичи (не из main-checkout), разрешён allow-правилом `Bash(git push origin --delete:*)` (его ставит `install.sh` владельца; при `--no-permissions` — строка в `.claude/settings.json` проекта, её печатают `adopt-project.sh` и `new-project.sh`, есть ли она — показывает `doctor.sh`; агент права себе не добавляет); затем из основного checkout `git worktree remove`, потом `git branch -D`. Остальные worktree не трогать. Обходы через API Gitea запрещены.
5. Обновить основной checkout: дерево чистое — `git pull --ff-only`; есть незакоммиченные изменения — не трогать, перечислить их в итоговом сообщении. 5. Обновить основной checkout: дерево чистое — `git pull --ff-only`; есть незакоммиченные изменения — не трогать, перечислить их в итоговом сообщении.
6. Все находки со статусом `deferred` собрать в одну issue `Follow-ups from #<issue-id>`: маркер, одна строка, откуда взялось. Нет отложенного — issue не создавать. 6. Все находки со статусом `deferred` собрать в одну issue `Follow-ups from #<issue-id>`: маркер, одна строка, откуда взялось. Нет отложенного — issue не создавать.
7. Файл auto-memory задачи, если был, свернуть в строку `registry-closed.md` (issue, PR, follow-up, дата); уроки из него перенести в `notes/` направления. 7. Файл auto-memory задачи, если был, свернуть в строку `registry-closed.md` (issue, PR, follow-up, дата); уроки из него перенести в `notes/` направления.

View File

@ -27,6 +27,7 @@ description: Диагностика обвязки Claude Code (claude-harness)
- `WARN в окружении есть *_API_KEY` → ключ виден всем сабагентам и делает интеграционные тесты платными при каждом `npm test`. Убрать из профиля shell, передавать только в строке запуска. Это находка для пользователя, агент сам профиль не правит. - `WARN в окружении есть *_API_KEY` → ключ виден всем сабагентам и делает интеграционные тесты платными при каждом `npm test`. Убрать из профиля shell, передавать только в строке запуска. Это находка для пользователя, агент сам профиль не правит.
- Глобальный CLAUDE.md без «маркировка: да» или «projectPath: да» → старая версия, `install.sh`. - Глобальный CLAUDE.md без «маркировка: да» или «projectPath: да» → старая версия, `install.sh`.
- `MISS roles/<r>.md`, `MISS skill role`, `MISS agent briefing`, `MISS link-roles.sh` → `bash <пакет>/install.sh` (версия ≥ 0.3.0). - `MISS roles/<r>.md`, `MISS skill role`, `MISS agent briefing`, `MISS link-roles.sh` → `bash <пакет>/install.sh` (версия ≥ 0.3.0).
- `WARN permissions обвязки: N/M, нет: …` или `MISS permissions обвязки` → allow-правила цикла фичи (`global/permissions.json`) не стоят в `~/.claude/settings.json`. Это находка владельцу: `bash <пакет>/install.sh` запускает он сам. Агент себе права не выдаёт: так он обошёл бы отказ классификатора auto mode. Если обвязка ставилась с `--no-permissions` осознанно, WARN ожидаем.
- `WARN глобальный CLAUDE.md > 3 КБ` → в `~/.claude/CLAUDE.md` дописано лишнее; процесс роли живёт в `~/.claude/roles/<r>.md`, перенести туда. - `WARN глобальный CLAUDE.md > 3 КБ` → в `~/.claude/CLAUDE.md` дописано лишнее; процесс роли живёт в `~/.claude/roles/<r>.md`, перенести туда.
**Самотест хуков** — любое `WARN … (ожидалось N)` означает, что хук установлен, но не делает того, что должен. Причины по убыванию частоты: правлен руками; `python3` не в PATH сессии; settings.json зовёт другой путь. Чинится `install.sh`; если и после него WARN — приложить строку к находке, это дефект пакета. **Самотест хуков** — любое `WARN … (ожидалось N)` означает, что хук установлен, но не делает того, что должен. Причины по убыванию частоты: правлен руками; `python3` не в PATH сессии; settings.json зовёт другой путь. Чинится `install.sh`; если и после него WARN — приложить строку к находке, это дефект пакета.
@ -42,13 +43,13 @@ description: Диагностика обвязки Claude Code (claude-harness)
- «code-paths: умолчание» при коде вне `src/` → создать `.claude/code-paths`. - «code-paths: умолчание» при коде вне `src/` → создать `.claude/code-paths`.
- «SDD workspace: …» при отсутствии активной фичи → хвост незакрытой фичи; проверить чеклист закрытия. - «SDD workspace: …» при отсутствии активной фичи → хвост незакрытой фичи; проверить чеклист закрытия.
- `WARN .bounded не в .gitignore` → добавить строку, иначе уедет в main. - `WARN .bounded не в .gitignore` → добавить строку, иначе уедет в main.
- `WARN нет allow-правила для git push origin --delete` → находка владельцу: готовую строку из WARN он сам добавляет в `.claude/settings.json` проекта (решение владельца 2, чеклист закрытия п. 4); агент `settings.json` не правит. `ok allow …` называет файл, где правило найдено: проектный `settings.json`, `settings.local.json` или глобальный. - `WARN нет allow-правила для git push origin --delete` → находка владельцу (чеклист закрытия п. 4). Правило ставит `bash install.sh`. При `--no-permissions` готовую строку из WARN владелец сам добавляет в `.claude/settings.json` проекта. Агент `settings.json` не правит. `ok allow …` называет файл, где правило найдено: проектный `settings.json`, `settings.local.json` или глобальный.
- `WARN tea не отвечает` → `tea logins list`; токен нужен со scopes read:user, write:repository, write:issue. - `WARN tea не отвечает` → `tea logins list`; токен нужен со scopes read:user, write:repository, write:issue.
- «workspace (не git)» → режим нескольких репозиториев. `WARN … битая ссылка` → репозиторий направления переименован или файл удалён: `bash ~/.claude/harness/link-roles.sh <workspace>`. `WARN нет <dir>/.claude/roles/<r>.md` → создать проектный файл роли из `~/.claude/harness/project-template/role-repo/ROLE.md`. - «workspace (не git)» → режим нескольких репозиториев. `WARN … битая ссылка` → репозиторий направления переименован или файл удалён: `bash ~/.claude/harness/link-roles.sh <workspace>`. `WARN нет <dir>/.claude/roles/<r>.md` → создать проектный файл роли из `~/.claude/harness/project-template/role-repo/ROLE.md`.
- `WARN индекс auto-memory > 3 КБ` → индекс снова стал журналом состояния; свернуть закрытое в `registry-closed.md`, уроки — в `notes/` направления (политика памяти в глобальном CLAUDE.md). - `WARN индекс auto-memory > 3 КБ` → индекс снова стал журналом состояния; свернуть закрытое в `registry-closed.md`, уроки — в `notes/` направления (политика памяти в глобальном CLAUDE.md).
## Чинится само (без вопроса) ## Чинится само (без вопроса)
`install.sh`, `link-roles.sh` в workspace, `codegraph init` в worktree, `.claude/code-paths`, строка в `.gitignore` на ветке. Всё остальное — находка пользователю: профиль shell, плагины, токены, удаление чего-либо. `install.sh --no-permissions` (агент так обновляет хуки и файлы; права ставит владелец своим `bash install.sh`), `link-roles.sh` в workspace, `codegraph init` в worktree, `.claude/code-paths`, строка в `.gitignore` на ветке. Всё остальное — находка пользователю: профиль shell, плагины, токены, права, удаление чего-либо.
## Чего не делать ## Чего не делать
Не «улучшать» хуки по месту: их источник — пакет claude-harness, правка идёт туда и ставится install.sh. Не отключать хук ради того, чтобы «прошло». Не «улучшать» хуки по месту: их источник — пакет claude-harness, правка идёт туда и ставится install.sh. Не отключать хук ради того, чтобы «прошло».

View File

@ -34,7 +34,7 @@ PY
) )
while IFS= read -r line; do case "$line" in "bad "*) info "${line#bad } — не JSON, allow-правила не прочитаны" ;; esac; done <<< "$r" while IFS= read -r line; do case "$line" in "bad "*) info "${line#bad } — не JSON, allow-правила не прочитаны" ;; esac; done <<< "$r"
case "$r" in *"ok "*) ok "allow git push origin --delete (${r##*ok })" ;; case "$r" in *"ok "*) ok "allow git push origin --delete (${r##*ok })" ;;
*) warn "нет allow-правила для git push origin --delete (закрытие, п. 4) — строка в .claude/settings.json → permissions.allow: \"Bash(git push origin --delete:*)\"" ;; esac *) warn "нет allow-правила для git push origin --delete (закрытие, п. 4) — bash install.sh или строка в .claude/settings.json → permissions.allow: \"Bash(git push origin --delete:*)\"" ;; esac
} }
echo "## harness-doctor $(date +%F' '%T)" echo "## harness-doctor $(date +%F' '%T)"
@ -56,6 +56,25 @@ done
[ -f "$CC/hooks/harness_target.py" ] && ok "hooks/harness_target.py (ядро guard-main и require-plan)" || miss "hooks/harness_target.py (bash install.sh)" [ -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/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" [ -x "$CC/harness/link-roles.sh" ] && ok "~/.claude/harness/link-roles.sh" || miss "~/.claude/harness/link-roles.sh"
if [ -f "$CC/harness/permissions.json" ]; then # allow-правила цикла фичи, которые ставит install.sh
pr=$(python3 - "$CC/settings.json" "$CC/harness/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 [] if isinstance(s, dict) else []
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
)
case "$pr" in
"") warn "permissions обвязки: не прочитаны ($CC/harness/permissions.json)" ;;
*" ") ok "permissions обвязки: ${pr% }" ;;
*) warn "permissions обвязки: ${pr%% *}, нет: ${pr#* } — bash install.sh (или осознанно --no-permissions)" ;;
esac
else miss "permissions обвязки (harness/permissions.json — bash install.sh)"; fi
rmiss=(); for r in dev admin analyst qa; do [ -f "$CC/roles/$r.md" ] || rmiss+=("$r"); done 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 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/skills/role/SKILL.md" ] && ok "skill role" || miss "skill role (~/.claude/skills/role/SKILL.md)"

View File

@ -4,7 +4,7 @@ CC="$TMP/cc"; mkdir -p "$CC/hooks" "$CC/roles" "$CC/skills/role" "$CC/agents" "$
cp "$ROOT"/hooks/*.sh "$ROOT/hooks/harness_target.py" "$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/" 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"/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" cp "$ROOT/link-roles.sh" "$ROOT/adopt-project.sh" "$ROOT/global/permissions.json" "$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" printf '{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"\\"$HOME/.claude/hooks/role-hint.sh\\""}]}]}}' > "$CC/settings.json"
# workspace с одной целой и одной битой ссылкой # workspace с одной целой и одной битой ссылкой
W="$TMP/ws"; mkdir -p "$W/admin/.claude/roles" "$W/.claude/roles" "$W/.claude/agents" W="$TMP/ws"; mkdir -p "$W/admin/.claude/roles" "$W/.claude/roles" "$W/.claude/agents"
@ -26,6 +26,7 @@ assert_grep "самотест: Write на main" 'ok +guard-main: Write на main
assert_grep "самотест: Write на ветке" 'ok +guard-main: Write на ветке → exit 0' <(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 "корпус хуков сходится" 'ok +корпус хуков: [0-9]+/[0-9]+' <(printf '%s' "$out")
assert_grep "заголовок раздела проекта (M-8)" '^## Проект: /' <(printf '%s' "$out") assert_grep "заголовок раздела проекта (M-8)" '^## Проект: /' <(printf '%s' "$out")
assert_grep "машина: правил обвязки нет — WARN с подсказкой install.sh (#24)" 'WARN +permissions обвязки: 0/[0-9]+.*bash install\.sh' <(printf '%s' "$out")
assert_grep "проект: режим workspace" 'workspace' <(printf '%s' "$out") assert_grep "проект: режим workspace" 'workspace' <(printf '%s' "$out")
assert_grep "проект: целая ссылка" 'ok +roles/admin.md' <(printf '%s' "$out") assert_grep "проект: целая ссылка" 'ok +roles/admin.md' <(printf '%s' "$out")
assert_grep "проект: битая ссылка" 'WARN +agents/none.md' <(printf '%s' "$out") assert_grep "проект: битая ссылка" 'WARN +agents/none.md' <(printf '%s' "$out")
@ -53,6 +54,14 @@ printf '{"permissions":{"allow":["Bash(git push origin --delete:*)"]},"hooks":{}
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1) out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1)
assert_grep "workspace: правило в глобальном settings.json — ok" 'ok +allow git push origin --delete \(.*/cc/settings\.json\)' <(printf '%s' "$out") assert_grep "workspace: правило в глобальном settings.json — ok" 'ok +allow git push origin --delete \(.*/cc/settings\.json\)' <(printf '%s' "$out")
cp "$TMP/settings.bak" "$CC/settings.json" cp "$TMP/settings.bak" "$CC/settings.json"
# правила обвязки (#24): все на месте — ok; нет harness/permissions.json — MISS
python3 -c 'import json,sys; s=json.load(open(sys.argv[1])); s["permissions"]=json.load(open(sys.argv[2]))["permissions"]; json.dump(s,open(sys.argv[1],"w"))' "$CC/settings.json" "$ROOT/global/permissions.json"
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1)
assert_grep "машина: правила обвязки на месте — ok" 'ok +permissions обвязки: [0-9]+/[0-9]+' <(printf '%s' "$out")
rm "$CC/harness/permissions.json"
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1)
assert_grep "машина: нет harness/permissions.json — MISS" 'MISS +permissions обвязки' <(printf '%s' "$out")
cp "$TMP/settings.bak" "$CC/settings.json"
rm -f "$CC/roles/qa.md" rm -f "$CC/roles/qa.md"
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1) 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") assert_grep "машина: MISS roles/qa.md" 'MISS +roles/qa.md' <(printf '%s' "$out")

View File

@ -1,6 +1,6 @@
# install.sh: новые файлы ставятся, повторный запуск не дублирует хуки, --check их видит. # install.sh: новые файлы ставятся, повторный запуск не дублирует хуки, --check их видит.
TH="$TMP/home"; CLAUDE_CONFIG_DIR="$TH/.claude"; mkdir -p "$CLAUDE_CONFIG_DIR" TH="$TMP/home"; CLAUDE_CONFIG_DIR="$TH/.claude"; mkdir -p "$CLAUDE_CONFIG_DIR"
echo '{"hooks":{"SessionStart":[{"matcher":"startup","hooks":[{"type":"command","command":"\"$HOME/.claude/hooks/harness-check.sh\""}]}]}}' > "$CLAUDE_CONFIG_DIR/settings.json" echo '{"permissions":{"allow":["Bash(echo:*)"]},"hooks":{"SessionStart":[{"matcher":"startup","hooks":[{"type":"command","command":"\"$HOME/.claude/hooks/harness-check.sh\""}]}]}}' > "$CLAUDE_CONFIG_DIR/settings.json"
HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" >/dev/null 2>&1; HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" >/dev/null 2>&1 HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" >/dev/null 2>&1; HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" >/dev/null 2>&1
CC="$CLAUDE_CONFIG_DIR" CC="$CLAUDE_CONFIG_DIR"
for r in dev admin analyst qa; do assert_file "roles/$r.md установлен" "$CC/roles/$r.md"; done for r in dev admin analyst qa; do assert_file "roles/$r.md установлен" "$CC/roles/$r.md"; done
@ -26,3 +26,25 @@ assert_grep "--check: harness_target.py" 'ok +hooks/harness_target\.py' <(printf
rm "$CC/agents/briefing.md" rm "$CC/agents/briefing.md"
out=$(HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" --check 2>&1) 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") assert_grep "--check: MISS briefing" 'MISS +.*briefing' <(printf '%s' "$out")
# allow-правила цикла фичи (#24): ставятся из global/permissions.json, чужие не трогаются, --no-permissions — отказ
P="$ROOT/global/permissions.json"
assert_file "global/permissions.json есть" "$P"
assert_file "harness/permissions.json установлен" "$CC/harness/permissions.json"
perm(){ python3 -c 'import json,sys; s=json.load(open(sys.argv[1])); a=s.get("permissions",{}).get("allow",[]); r=json.load(open(sys.argv[2]))["permissions"]["allow"]; print(sum(a.count(x)==1 for x in r), len(r), a.count("Bash(echo:*)"), len(a)-len(set(a)))' "$1" "$P"; }
read -r have total own dup <<< "$(perm "$CC/settings.json")"
assert_eq "список правил обвязки прочитан и не пуст" "$([ "${total:-0}" -gt 0 ] 2>/dev/null && echo да)" "да"
assert_eq "правила обвязки в settings.json — все, по одному разу после двух установок" "$have" "$total"
assert_eq "чужое правило сохранено" "$own" "1"
assert_eq "дублей в permissions.allow нет" "$dup" "0"
assert_eq "в списке нет merge и push в main" "$(grep -cE 'pulls merge|push origin main|"Bash\(git push:\*\)"|"Bash\(git:\*\)"|"Bash"' "$P")" "0"
assert_eq "EnterWorktree в списке нет: allow-правило не снимает запрос (#25)" "$(grep -c 'EnterWorktree' "$P")" "0"
out=$(HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" --check 2>&1)
assert_grep "--check: правила обвязки на месте" 'ok +permissions обвязки: [0-9]+/[0-9]+' <(printf '%s' "$out")
T2="$TMP/home2"; C2="$T2/.claude"; mkdir -p "$C2"; echo '{}' > "$C2/settings.json"
out=$(HOME="$T2" CLAUDE_CONFIG_DIR="$C2" bash "$ROOT/install.sh" --no-permissions 2>&1); rc=$?
assert_eq "--no-permissions: exit 0" "$rc" "0"
assert_grep "--no-permissions: говорит, что правила пропущены" 'permissions.*--no-permissions' <(printf '%s' "$out")
read -r have total own dup <<< "$(perm "$C2/settings.json")"
assert_eq "--no-permissions: правил обвязки не добавлено" "$have" "0"
assert_file "--no-permissions: хуки всё равно ставятся" "$C2/hooks/guard-main.sh"
assert_grep "--no-permissions: --check предупреждает" 'WARN +permissions обвязки: 0/' <(printf '%s' "$out")

View File

@ -49,4 +49,5 @@ A="$TMP/adopt"; git init -q -b main "$A" && git -C "$A" commit -q --allow-empty
out=$(bash "$ROOT/adopt-project.sh" "$A" 2>&1); rc=$? out=$(bash "$ROOT/adopt-project.sh" "$A" 2>&1); rc=$?
assert_eq "adopt (старый режим): git-репозиторий → exit 0" "$rc" "0" assert_eq "adopt (старый режим): git-репозиторий → exit 0" "$rc" "0"
assert_grep "adopt (старый режим): печатает allow-правило (B3)" '"Bash\(git push origin --delete:\*\)"' <(printf '%s' "$out") assert_grep "adopt (старый режим): печатает allow-правило (B3)" '"Bash\(git push origin --delete:\*\)"' <(printf '%s' "$out")
assert_grep "adopt: строка — для установки с --no-permissions (#24)" 'no-permissions' <(printf '%s' "$out")
assert_eq "adopt (старый режим): settings.json не создаёт" "$([ -e "$A/.claude/settings.json" ] && echo есть || echo нет)" "нет" assert_eq "adopt (старый режим): settings.json не создаёт" "$([ -e "$A/.claude/settings.json" ] && echo есть || echo нет)" "нет"