Хуки решают по цели записи + регрессионный корпус хуков (#6, B1+B2, 0.4.0) #12

Merged
aep merged 11 commits from feat/6-guard-main-target into main 2026-10-16 09:11:07 +03:00
Owner

Refs #6 — B1 + B2 + решение владельца 1. Спека: openspec/changes/hooks-write-target/ (proposal, specs/hook-guards, design). План: docs/superpowers/plans/2026-10-16-hooks-write-target.md.

Что сделано

  • guard-main решает по цели записи, а не по cwd и тексту команды (B1; RT-1, RT-2, RT-7). Цели:

    • файл Edit/Write;
    • в Bash — редиректы (кроме /dev/* и N>&M) и команды записи с учётом cd, git -C, env -C, sh -c, префиксов sudo/timeout/nice…;
    • git-мутации.

    Цель вне репозитория или в worktree на ветке — пропуск. Неразрешимый путь — пропуск. Git-мутация с неопределённым репозиторием — блок. git push блокируется, только если пушит в main. git switch -c / git checkout main в той же команде меняют ветку для последующих целей.

  • Несовместимо: исключение openspec-only и openspec archive на main удалено — архив только отдельным PR (решение владельца 1).

  • require-plan берёт корень, ветку, .claude/code-paths, .bounded и планы из репозитория правимого файла.

  • Ядро — hooks/harness_target.py (python3 stdlib, 3.8+). .sh — обёртки, settings.hooks.json не менялся. python3 hooks/harness_target.py targets '<cmd>' <cwd> показывает, какие цели видит хук.

  • Сообщения: 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 (установленные копии).

  • hook_blocks_export.py и retro_blocks.py считают только ошибки хуков (O-1).

Метрика (корпус в песочнице)

блоков ложных пропущенных настоящих
старые хуки (main) 41 37 0
новые хуки 4 0 0

Четыре блока — архивные коммиты openspec прямо в main с push в main (c031, c069, c070, c103). O-1: из 126 выгруженных записей настоящих ошибок хука 76, остальные — вывод команд со словом BLOCKED, поэтому счёт RT-1 в ретро завышен. «Единственный реальный перехват sed -i» из ретро (c083) правил файл памяти, а не docs.

Задержка хука: 26 мс на вызов без целей, 39 мс с git.

Проверка

  • bash tests/run.sh — passed 472, failed 0 (было 157). Новые тесты: test-hook-parse (91), test-hook-guard-main (62), test-hook-require-plan (17), test-hook-corpus (132), test-hook-export (3).
  • openspec validate hooks-write-target --strict — valid.
  • Финальное ревью ветки — opus: Critical 0, Important 2 + 3 переоценены в Important, все исправлены в 2f96a13 по TDD (тест красный → зелёный, весь набор зелёный).

Находки

Маркер Что Статус
RT-1, RT-2, RT-6, RT-7 ретр��: ложные блоки по cwd/тексту, чужой cd, нет регрессионных тестов, каскад классификатора fixed 87f2c95, 3d54932
I-1 git checkout main && … && git push проходил fixed 2f96a13
I-2 timeout 60/nice -n 5/sudo -u u прятали git commit/push (регрессия) fixed 2f96a13
M-3 rm/ln -sfn ссылки workspace на main-checkout блокировались (снос worktree awp-ca); переоценена в Important fixed 2f96a13
M-4 require-plan пропускал src/[slug].ts (регрессия); переоценена в Important fixed 2f96a13
M-5 git push -u origin "$(git branch --show-current)" из worktree блокировался, push --tags с main — тоже; переоценена в Important fixed 2f96a13
M-7 решения прототипа не записаны в design.md fixed 2f96a13; часть про сообщения — deferred
M-8 пропал пробел в заголовке «## Проект:» doctor.sh fixed 2f96a13
O-1 экстрактор и retro_blocks.py считали блокировкой вывод команд со словом BLOCKED fixed 3d54932
M-1 git-мутация при пустом cwd → exit 2 (Claude Code cwd передаёт всегда) deferred
M-2 test-hook-corpus проходит вхолостую, если раннер упал (страхует test-doctor) deferred
M-6 лексер: function f { … }, $'…', (( a << 2 )) deferred
M-7 (сообщения) у push? нет корня репозитория; «запись в .» для npm install/tar -x без операции deferred
M-9 hook_corpus.py собирает песочницу с глобальным git-конфигом (на этой машине ключей нет) deferred
M-10 worktree внутри репозитория, созданный в той же команде, блокирует записи в нём deferred
M-11 npm --prefix/pnpm -C/yarn --cwd: значение опции как подкоманда; perl -Mstrict ложно как perl -i deferred
M-12 корпус: c002 испорчен редакцией секретов, why не проверяется, block одной формы — добавить живые block-случаи deferred
— rm -rf link/ с косой чертой и mv в ссылку на каталог оцениваются по месту ссылки (пробел M-3) deferred
O-2 код плана написан прототипом до плана (RED каждой задачи наблюдался на исходной ветке) — тема для ретро deferred
O-3 require-plan: правило «план один → пропуск» в этом репозитории пропускало всё deferred
O-4 require-plan не видит записи в код через Bash deferred
O-5 изоляция worktree Claude Code отказывает read-only командам со словом git в тексте — не наш код deferred

Rulings I made

  • Task 4: старые хуки для метрики — в .superpowers/sdd/<план>/old-hooks, а не в $TMPDIR: изоляция worktree запрещает git show с выводом за его пределы. Цена: нет.
  • Task 5: документация перенесена копией проверенных файлов прототипа; diff сверен построчно. Цена: нет.
  • Final: M-3, M-4, M-5 переоценены в Important по эффекту — регрессии и ложные блоки в основном потоке. Цена: нет.
  • Final: M-8 восстановлен по плану: это непреднамеренная правка шага 4 задачи 5, не доработка. Цена: нет.
  • Final: pull, fetch origin main:main, branch -f main, update-ref — не мутации (спека); удалённый main защищён правилами push. Цена: локальный main расходится с origin.
  • Final: git clean -fdx, checkout ., checkout -f на main не блокируются: ветка и история не меняются, старый хук их тоже пропускал. Цена: потеря неотслеживаемых файлов main-checkout.
  • Final: mkdir -p .worktrees и запись в игнорируемые пути main блокируются (design.md, без check-ignore). Цена: ложный блок.
  • Final: запись внутри скриптов, python3 -c, make, find -delete — Non-Goals.
  • Final: tea pulls merge / gh pr merge — правило процесса, не хук.
  • Final: NotebookEdit не покрыт (design.md).
  • Final: submodule и worktree add --detach блокируются как detached HEAD (спека).
  • Final: git < 2.22 — предусловие, поведение как у старого хука.
  • Final: git stash на main блокируется (спека).
  • Final: git tag/config/remote/notes/gc — не мутации (design.md).
  • Final: cd /x & считается обычным cd.
  • Final: записи корпуса с /private/tmp/... разрешаются по ФС хоста — стабильно вне репозиториев.
  • Final: время старта python замерено (26/39 мс) — в пределах оценки design.
  • Final: рабочий каталог плана не удаляется до «закрывай» (roles/dev.md, п. 3 чеклиста).

Решения, принятые без обсуждения на этапах спеки и прототипа, — в design.md, раздел «Decisions made without discussion».

После merge

bash install.sh на каждой машине: подменит тела хуков и положит harness_target.py, settings.json не меняется. Затем bash ~/.claude/skills/harness-doctor/scripts/doctor.sh — ожидается ok корпус хуков: 129/129.

🤖 Generated with Claude Code

Refs #6 — B1 + B2 + решение владельца 1. Спека: `openspec/changes/hooks-write-target/` (proposal, specs/hook-guards, design). План: `docs/superpowers/plans/2026-10-16-hooks-write-target.md`. ## Что сделано - **guard-main решает по цели записи, а не по cwd и тексту команды** (B1; RT-1, RT-2, RT-7). Цели: - файл Edit/Write; - в Bash — редиректы (кроме `/dev/*` и `N>&M`) и команды записи с учётом `cd`, `git -C`, `env -C`, `sh -c`, префиксов `sudo`/`timeout`/`nice`…; - git-мутации. Цель вне репозитория или в worktree на ветке — пропуск. Неразрешимый путь — пропуск. Git-мутация с неопределённым репозиторием — блок. `git push` блокируется, только если пушит в main. `git switch -c` / `git checkout main` в той же команде меняют ветку для последующих целей. - **Несовместимо:** исключение openspec-only и `openspec archive` на main удалено — архив только отдельным PR (решение владельца 1). - **require-plan** берёт корень, ветку, `.claude/code-paths`, `.bounded` и планы из репозитория правимого файла. - Ядро — `hooks/harness_target.py` (python3 stdlib, 3.8+). `.sh` — обёртки, `settings.hooks.json` не менялся. `python3 hooks/harness_target.py targets '<cmd>' <cwd>` показывает, какие цели видит хук. - Сообщения: `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` (установленные копии). - `hook_blocks_export.py` и `retro_blocks.py` считают только ошибки хуков (O-1). ## Метрика (корпус в песочнице) | | блоков | ложных | пропущенных настоящих | |---|---|---|---| | старые хуки (`main`) | 41 | 37 | 0 | | новые хуки | 4 | 0 | 0 | Четыре блока — архивные коммиты openspec прямо в main с push в main (c031, c069, c070, c103). O-1: из 126 выгруженных записей настоящих ошибок хука 76, остальные — вывод команд со словом BLOCKED, поэтому счёт RT-1 в ретро завышен. «Единственный реальный перехват `sed -i`» из ретро (c083) правил файл памяти, а не docs. Задержка хука: 26 мс на вызов без целей, 39 мс с git. ## Проверка - `bash tests/run.sh` — **passed 472, failed 0** (было 157). Новые тесты: `test-hook-parse` (91), `test-hook-guard-main` (62), `test-hook-require-plan` (17), `test-hook-corpus` (132), `test-hook-export` (3). - `openspec validate hooks-write-target --strict` — valid. - Финальное ревью ветки — opus: Critical 0, Important 2 + 3 переоценены в Important, все исправлены в `2f96a13` по TDD (тест красный → зелёный, весь набор зелёный). ## Находки | Маркер | Что | Статус | |---|---|---| | RT-1, RT-2, RT-6, RT-7 | ретр��: ложные блоки по cwd/тексту, чужой `cd`, нет регрессионных тестов, каскад классификатора | fixed 87f2c95, 3d54932 | | I-1 | `git checkout main && … && git push` проходил | fixed 2f96a13 | | I-2 | `timeout 60`/`nice -n 5`/`sudo -u u` прятали `git commit/push` (регрессия) | fixed 2f96a13 | | M-3 | `rm`/`ln -sfn` ссылки workspace на main-checkout блокировались (снос worktree awp-ca); переоценена в Important | fixed 2f96a13 | | M-4 | require-plan пропускал `src/[slug].ts` (регрессия); переоценена в Important | fixed 2f96a13 | | M-5 | `git push -u origin "$(git branch --show-current)"` из worktree блокировался, `push --tags` с main — тоже; переоценена в Important | fixed 2f96a13 | | M-7 | решения прототипа не записаны в design.md | fixed 2f96a13; часть про сообщения — deferred | | M-8 | пропал пробел в заголовке «## Проект:» doctor.sh | fixed 2f96a13 | | O-1 | экстрактор и `retro_blocks.py` считали блокировкой вывод команд со словом BLOCKED | fixed 3d54932 | | M-1 | git-мутация при пустом `cwd` → exit 2 (Claude Code `cwd` передаёт всегда) | deferred | | M-2 | `test-hook-corpus` проходит вхолостую, если раннер упал (страхует `test-doctor`) | deferred | | M-6 | лексер: `function f { … }`, `$'…'`, `(( a << 2 ))` | deferred | | M-7 (сообщения) | у `push?` нет корня репозитория; «запись в .» для `npm install`/`tar -x` без операции | deferred | | M-9 | `hook_corpus.py` собирает песочницу с глобальным git-конфигом (на этой машине ключей нет) | deferred | | M-10 | worktree внутри репозитория, созданный в той же команде, блокирует записи в нём | deferred | | M-11 | `npm --prefix`/`pnpm -C`/`yarn --cwd`: значение опции как подкоманда; `perl -Mstrict` ложно как `perl -i` | deferred | | M-12 | корпус: c002 испорчен редакцией секретов, `why` не проверяется, block одной формы — добавить живые block-случаи | deferred | | — | `rm -rf link/` с косой чертой и `mv` в ссылку на каталог оцениваются по месту ссылки (пробел M-3) | deferred | | O-2 | код плана написан прототипом до плана (RED каждой задачи наблюдался на исходной ветке) — тема для ретро | deferred | | O-3 | require-plan: правило «план один → пропуск» в этом репозитории пропускало всё | deferred | | O-4 | require-plan не видит записи в код через Bash | deferred | | O-5 | изоляция worktree Claude Code отказывает read-only командам со словом `git` в тексте — не наш код | deferred | ## Rulings I made - Task 4: старые хуки для метрики — в `.superpowers/sdd/<план>/old-hooks`, а не в `$TMPDIR`: изоляция worktree запрещает `git show` с выводом за его пределы. Цена: нет. - Task 5: документация перенесена копией проверенных файлов прототипа; diff сверен построчно. Цена: нет. - Final: M-3, M-4, M-5 переоценены в Important по эффекту — регрессии и ложные блоки в основном потоке. Цена: нет. - Final: M-8 восстановлен по плану: это непреднамеренная правка шага 4 задачи 5, не доработка. Цена: нет. - Final: pull, `fetch origin main:main`, `branch -f main`, `update-ref` — не мутации (спека); удалённый main защищён правилами push. Цена: локальный main расходится с origin. - Final: `git clean -fdx`, `checkout .`, `checkout -f` на main не блокируются: ветка и история не меняются, старый хук их тоже пропускал. Цена: потеря неотслеживаемых файлов main-checkout. - Final: `mkdir -p .worktrees` и запись в игнорируемые пути main блокируются (design.md, без `check-ignore`). Цена: ложный блок. - Final: запись внутри скриптов, `python3 -c`, `make`, `find -delete` — Non-Goals. - Final: `tea pulls merge` / `gh pr merge` — правило процесса, не хук. - Final: NotebookEdit не покрыт (design.md). - Final: submodule и `worktree add --detach` блокируются как detached HEAD (спека). - Final: git < 2.22 — предусловие, поведение как у старого хука. - Final: `git stash` на main блокируется (спека). - Final: `git tag/config/remote/notes/gc` — не мутации (design.md). - Final: `cd /x &` считается обычным `cd`. - Final: записи корпуса с `/private/tmp/...` разрешаются по ФС хоста — стабильно вне репозиториев. - Final: время старта python замерено (26/39 мс) — в пределах оценки design. - Final: рабочий каталог плана не удаляется до «закрывай» (`roles/dev.md`, п. 3 чеклиста). Решения, принятые без обсуждения на этапах спеки и прототипа, — в `design.md`, раздел «Decisions made without discussion». ## После merge `bash install.sh` на каждой машине: подменит тела хуков и положит `harness_target.py`, `settings.json` не меняется. Затем `bash ~/.claude/skills/harness-doctor/scripts/doctor.sh` — ожидается `ok корпус хуков: 129/129`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
aep added 11 commits 2026-10-16 01:26:58 +03:00
hook_blocks_export.py выгружает блокировки хуков в JSONL (cwd, ветка, команда,
ответ хука) — сырьё для регрессионных тестов B2. Похожее на секреты скрывается.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
proposal, specs/hook-guards (16 требований), design с Decisions made without discussion. B1, B2, решение владельца 1; RT-1, RT-2, RT-6, RT-7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Требование «Смена ветки внутри команды» (c072, поток архива отдельным PR), сценарий BSD sed -i ''. design: 76 настоящих блокировок из 126, c083 — файл памяти, собственный лексер, один hook_corpus.py, фильтр retro_blocks.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Сценарий спеки: запись через симлинк, указывающий в main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
B1; RT-1, RT-2, RT-7; решение владельца 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
B2, RT-6, O-1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
I-1, I-2, M-3, M-4, M-5 (переоценены в Important), M-8; спека и design.md синхронизированы (M-7).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aep merged commit d245e506df into main 2026-10-16 09:11:07 +03:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: aep/claude-harness#12
No description provided.