fix(hooks): worktree-цепочка резолвится по новой ветке (O-4, #63) #71

Merged
aep merged 2 commits from feat/63-guard-worktree-chain into main 2026-10-24 22:17:16 +03:00
Owner

Что и почему

Issue #63 (находка O-4): цепочка git worktree add <path> -b <branch> … && cd <path> && openspec archive … в одной команде блокировалась guard-main по ветке основного checkout (main), хотя worktree создаётся на новой ветке и последующие команды выполнились бы уже в нём.

Причина в парсере. hooks/harness_target.py не разбирал git worktree add: цель openspec archive в каталоге ещё не существующего worktree поднималась resolve() до объемлющего репозитория и оценивалась по его ветке — main. Спец-случай «worktree — не мутация» только пропускал сам git worktree add, пары «путь → ветка» нигде не сохранялись.

Изменения

  • hooks/harness_target.py:
    • _worktree() — git worktree add <путь> -b|-B <ветка> запоминает пару «путь → ветка» (с учётом git -C, относительный путь считается от каталога -C);
    • parse()/_State получают список этих пар через _wts (доп. аргумент, рекурсия bash -c/eval его наследует); режим targets и схема целей не меняются — parse остаётся без обращений к ФС;
    • decide_guard() переводит пути worktree в реальные (realpath) и для цели, лежащей под таким путём, берёт ветку из пары вместо ветки объемлющего репозитория. Цели вне пути worktree оцениваются как раньше — по объемлющему репозиторию.
  • openspec/specs/hook-guards/spec.md — новое требование «Новый worktree внутри той же команды» и два сценария (цепочка с cd — пропуск; openspec archive без cd в main — блок).
  • tests/test-hook-guard-main.sh — три регрессии в песочнице: цепочка через git worktree add -b, то же через git -C, и негативный случай без cd (остаётся блок).

Проверка

  • RED: новые две цепочки падали (exit 2 вместо 0), негативный случай проходил.
  • GREEN: bash tests/run.sh → passed 633, failed 0 (до правок — 630/0).
  • Ручное воспроизведение из issue (git worktree add … -b … | tail -2 && cd … && openspec archive …, cwd — репозиторий на main): python3 hooks/harness_target.py targets … даёт цель file <repo>/.claude/worktrees/archive-dsh-openspec openspec archive, guard-main — exit 0. Без cd та же цели — exit 2.

Решения реализации

  1. Пары «путь → ветка» не добавляются в список целей (targets не меняется): это вход решения, а не цель записи; схема file|git|switch и тест test-hook-parse остаются прежними.
  2. Переопределение действует на все цели под путём worktree (файлы, openspec, git -C), а не только на openspec archive, — так требование issue выполняется без частного случая.
  3. Разбираются только формы -b/-B. Форма git worktree add <��уть> <существующая ветка> и ветка в переменной не мапятся: вердикт остаётся прежним (по объемлющему репозиторию), ложного пропуска нет.

Ограничения / не сделано

  • Кейс в регрессионный корпус хуков не добавлен: вердикты реальных команд корпуса не изменились, а разбор закреплён песочницей test-hook-guard-main; добавление live-кейса меняет счётчик в синтетическом сегменте test-hook-corpus и выходит за bounded-объём. Если владелец хочет кейс — отдельным follow-up.
## Что и почему Issue #63 (находка **O-4**): цепочка `git worktree add <path> -b <branch> … && cd <path> && openspec archive …` в одной команде блокировалась guard-main по ветке основного checkout (main), хотя worktree создаётся на новой ветке и последующие команды выполнились бы уже в нём. **Причина в парсере.** `hooks/harness_target.py` не разбирал `git worktree add`: цель `openspec archive` в каталоге ещё не существующего worktree поднималась `resolve()` до объемлющего репозитория и оценивалась по его ветке — main. Спец-случай «`worktree` — не мутация» только пропускал сам `git worktree add`, пары «путь → ветка» нигде не сохранялись. ## Изменения - `hooks/harness_target.py`: - `_worktree()` — `git worktree add <путь> -b|-B <ветка>` запоминает пару «путь → ветка» (с учётом `git -C`, относительный путь считается от каталога `-C`); - `parse()`/`_State` получают список этих пар через `_wts` (доп. аргумент, рекурсия `bash -c`/`eval` его наследует); режим `targets` и схема целей не меняются — `parse` остаётся без обращений к ФС; - `decide_guard()` переводит пути worktree в реальные (`realpath`) и для цели, лежащей под таким путём, берёт ветку из пары вместо ветки объемлющего репозитория. Цели вне пути worktree оцениваются как раньше — по объемлющему репозиторию. - `openspec/specs/hook-guards/spec.md` — новое требование «Новый worktree внутри той же команды» и два сценария (цепочка с `cd` — пропуск; `openspec archive` без `cd` в main — блок). - `tests/test-hook-guard-main.sh` — три регрессии в песочнице: цепочка через `git worktree add -b`, то же через `git -C`, и негативный случай без `cd` (остаётся блок). ## Проверка - RED: новые две цепочки падали (exit 2 вместо 0), негативный случай проходил. - GREEN: `bash tests/run.sh` → `passed 633, failed 0` (до правок — 630/0). - Ручное воспроизведение из issue (`git worktree add … -b … | tail -2 && cd … && openspec archive …`, cwd — репозиторий на main): `python3 hooks/harness_target.py targets …` даёт цель `file <repo>/.claude/worktrees/archive-dsh-openspec openspec archive`, guard-main — `exit 0`. Без `cd` та же цели — `exit 2`. ## Решения реализации 1. Пары «путь → ветка» не добавляются в список целей (`targets` не меняется): это вход решения, а не цель записи; схема `file|git|switch` и тест `test-hook-parse` остаются прежними. 2. Переопределение действует на все цели под путём worktree (файлы, `openspec`, `git -C`), а не только на `openspec archive`, — так требование issue выполняется без частного случая. 3. Разбираются только формы `-b`/`-B`. Форма `git worktree add <��уть> <существующая ветка>` и ветка в переменной не мапятся: вердикт остаётся прежним (по объемлющему репозиторию), ложного пропуска нет. ## Ограничения / не сделано - Кейс в регрессионный корпус хуков не добавлен: вердикты реальных команд корпуса не изменились, а разбор закреплён песочницей `test-hook-guard-main`; добавление live-кейса меняет счётчик в синтетическом сегменте `test-hook-corpus` и выходит за bounded-объём. Если владелец хочет кейс — отдельным follow-up.
aep added 1 commit 2026-10-24 21:55:54 +03:00
Author
Owner

Рулинг контроллера R-1 (по ревью T1.2): берём точный вариант — явный switch (over[root]) выигрывает у wt-пары. Порядок в decide_guard: over[root] → wt-пара (только для целей с индексом >= позиции add) → resolved. Ложных блоков не добавляет: over заполняется только явным switch/checkout в этой команде. Объём правок: T1.1–T1.4 + негативы T1.6. T1.5 и T1.8 — deferred, занесены в #70.

Рулинг контроллера R-1 (по ревью T1.2): берём точный вариант — явный switch (over[root]) выигрывает у wt-пары. Порядок в decide_guard: over[root] → wt-пара (только для целей с индексом >= позиции add) → resolved. Ложных блоков не добавляет: over заполняется только явным switch/checkout в этой команде. Объём правок: T1.1–T1.4 + негативы T1.6. T1.5 и T1.8 — deferred, занесены в #70.
aep added 1 commit 2026-10-24 22:09:40 +03:00
Author
Owner

Итог ревью: merge ok. Свежий ревьюер (v4-pro): перепроверка дельты 8d8dd89..e064de7 — test-hook-guard-main 78/0, test-hook-parse 91/0, test-hook-corpus 151/0 (вердикты корпуса не изменились); матрица 16 граничных цепочек: ложные пропуски 0→2, пере-блокировок нет. Находки: T1.1-T1.4, T1.6, T1.7 — fixed e064de7; T1.5, T1.8 — deferred в #70; T1.9 — rejected (обоснованно); T2.1 — rejected (осознанный компромисс R-1), опциональный follow-up в #70. Контроллер O-1 (инцидент с tea comments в сессии) — fixed, к коду не относится. Merge — по команде владельца.

Итог ревью: merge ok. Свежий ревьюер (v4-pro): перепроверка дельты 8d8dd89..e064de7 — test-hook-guard-main 78/0, test-hook-parse 91/0, test-hook-corpus 151/0 (вердикты корпуса не изменились); матрица 16 граничных цепочек: ложные пропуски 0→2, пере-блокировок нет. Находки: T1.1-T1.4, T1.6, T1.7 — fixed e064de7; T1.5, T1.8 — deferred в #70; T1.9 — rejected (обоснованно); T2.1 — rejected (осознанный компромисс R-1), опциональный follow-up в #70. Контроллер O-1 (инцидент с tea comments в сессии) — fixed, к коду не относится. Merge — по команде владельца.
aep merged commit c86125bc1e into main 2026-10-24 22:17:16 +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#71
No description provided.