Архив OpenSpec hooks-write-target → openspec/specs/hook-guards (#6) #14
@ -1,7 +1,7 @@
|
||||
# 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/`.
|
||||
- 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/specs/hook-guards/spec.md`, change в архиве — `openspec/changes/archive/2026-10-16-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` показывает, какие цели видит хук.
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
## Текущая работа
|
||||
Бэклог обвязки — `docs/retro/2026-10-11.md`, раздел 4 (B1–B12), решения владельца — раздел 5. Находки ретро маркируются `RT-n`.
|
||||
- Сделано: B5+B6 часть 1 — слой ролей (PR #4, 0.3.0). Часть 2 (awp-ca) — issue #2, отложенные находки — issue #5.
|
||||
- В работе: B1+B2 и отмена исключения openspec-only для guard-main — issue #6, ветка `feat/6-guard-main-target`.
|
||||
- Сделано: B1+B2 и отмена исключения openspec-only для guard-main — хуки по цели записи и корпус хуков (issue #6, PR #12, 0.4.0), спека — `openspec/specs/hook-guards/`. Отложенные находки — issue #13.
|
||||
- Дальше: B3/B4/B10, B12.
|
||||
|
||||
## OpenSpec и superpowers
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
Вход: docs/retro/fixtures/hook-blocks.jsonl (hook_blocks_export.py, 12.10.2026, 126 записей).
|
||||
Выход: первая строка — {"home": …, "layout": […]} (раскладка песочницы), дальше по записи на случай:
|
||||
поля выгрузки + id, expected (allow|block), why (класс). Вердикт размечен вручную по спеке
|
||||
openspec/changes/hooks-write-target/specs/hook-guards/spec.md, а не прогоном хука.
|
||||
openspec/specs/hook-guards/spec.md, а не прогоном хука.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
Отладка разбора без файловой системы:
|
||||
python3 harness_target.py targets '<команда>' [cwd] → по строке на цель: «kind path op»
|
||||
Любой сбой разбора или вызова git — пропуск (exit 0). Только stdlib, Python 3.8+.
|
||||
Спека: openspec/changes/hooks-write-target/specs/hook-guards/spec.md.
|
||||
Спека: openspec/specs/hook-guards/spec.md.
|
||||
"""
|
||||
import glob
|
||||
import json
|
||||
|
||||
325
openspec/specs/hook-guards/spec.md
Normal file
325
openspec/specs/hook-guards/spec.md
Normal file
@ -0,0 +1,325 @@
|
||||
# hook-guards Specification
|
||||
|
||||
## Purpose
|
||||
Блокирующие хуки обвязки Claude Code: guard-main не даёт изменить main, master и detached HEAD в обход PR, require-plan
|
||||
не даёт править код без плана или маркера `.bounded`. Хуки решают по цели записи и проверяются регрессионным корпусом
|
||||
реальных команд.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Решение по цели записи, а не по cwd
|
||||
guard-main SHALL блокировать вызов только тогда, когда хотя бы одна цель записи лежит в git-репозитории, текущая ветка
|
||||
которого защищена. cwd сессии MUST использоваться только как база для относительных путей и как начальный каталог
|
||||
разбора команды. Вызов без целей записи MUST пропускаться при любом cwd.
|
||||
|
||||
#### Scenario: Команда только чтения при cwd на main
|
||||
- **WHEN** cwd — репозиторий на main, команда `grep -rn "npm i\|npm install" docs 2>/dev/null | head`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Редирект в /dev/null и дублирование дескриптора
|
||||
- **WHEN** cwd — репозиторий на main, команда `git fetch origin --prune 2>&1 | tail -3` или `ls x 2>/dev/null`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Edit файла вне любого репозитория
|
||||
- **WHEN** cwd — репозиторий на main, Write в файл `~/.claude/projects/<slug>/memory/x.md`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Edit файла в worktree на ветке при cwd на main
|
||||
- **WHEN** cwd — основной checkout на main, Edit файла внутри worktree на ветке `feat/6-x`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Edit файла в репозитории на main
|
||||
- **WHEN** Edit файла внутри репозитория, текущая ветка которого main, при любом cwd
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
#### Scenario: Запись через симлинк, указывающий в main
|
||||
- **WHEN** Write в симлинк вне репозитория, цель которого — ещё не созданный файл в репозитории на main
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
### Requirement: Защищённые состояния ветки
|
||||
Защищёнными SHALL считаться ветки `main` и `master` и состояние detached HEAD. Любая другая ветка MUST считаться
|
||||
рабочей.
|
||||
|
||||
#### Scenario: Запись в репозитории на master
|
||||
- **WHEN** Write в файл репозитория на `master`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
#### Scenario: Запись при detached HEAD
|
||||
- **WHEN** Write в файл репозитория в состоянии detached HEAD
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
### Requirement: Редирект как файловая цель
|
||||
guard-main SHALL считать файловой целью файл после операторов перенаправления вывода (`>`, `>>`, `>|`, `&>`, `N>`,
|
||||
`N>>`). `/dev/null`, `/dev/stdout`, `/dev/stderr`, `/dev/tty` и дублирование дескриптора `N>&M` MUST NOT считаться
|
||||
целями.
|
||||
|
||||
#### Scenario: Запись редиректом в main
|
||||
- **WHEN** cwd — репозиторий на main, команда `echo x > notes.md`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
#### Scenario: stderr в файл в main
|
||||
- **WHEN** cwd — репозиторий на main, команда `make 2>build.log`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
### Requirement: Команды записи как файловые цели
|
||||
guard-main SHALL считать файловыми целями пути-аргументы известных команд записи: `tee`, `touch`, `mkdir`, `rm`,
|
||||
`rmdir`, `truncate`, `chmod`, `unlink`, все пути `mv`, назначение `cp`/`install`/`ln`/`rsync`, файлы `sed -i` и
|
||||
`perl -i`, `dd of=`, каталог `tar -x`/`unzip`/`patch`, текущий каталог для `npm`/`yarn`/`pnpm`
|
||||
`install|i|ci|add|init`. Аргументы прочих команд MUST NOT считаться целями.
|
||||
|
||||
#### Scenario: sed -i по файлу в main
|
||||
- **WHEN** команда `sed -i 's/a/b/' <репозиторий на main>/docs/HARNESS.md`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
#### Scenario: BSD sed -i '' по файлу памяти
|
||||
- **WHEN** cwd — репозиторий на main, команда `sed -i '' 's/a/b/' ~/.claude/projects/<slug>/memory/x.md`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Запись в контейнере не считается локальной
|
||||
- **WHEN** cwd — репозиторий на main, команда `docker compose exec -T db sh -c 'psql -c "select 1" > /tmp/x'`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Установка пакетов Python
|
||||
- **WHEN** cwd — репозиторий на main, команда `pip install requests`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Установка пакетов Node в main
|
||||
- **WHEN** cwd — репозиторий на main, команда `npm install`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
### Requirement: Каталог внутри команды
|
||||
guard-main SHALL учитывать `cd`, `pushd` и `popd` внутри команды: они меняют каталог для последующих команд списка,
|
||||
соединённых `;`, `&&`, `||`. Изменение каталога MUST NOT распространяться через `|` и за пределы `( … )`. Команды
|
||||
`sh -c`, `bash -c` и `eval` SHALL разбираться рекурсивно, когда они стоят головой команды.
|
||||
|
||||
#### Scenario: cd в worktree и запись
|
||||
- **WHEN** cwd — основной checkout на main, команда `cd <worktree на ветке> && echo x > f`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: cd в main и запись
|
||||
- **WHEN** cwd — worktree на ветке, команда `cd <репозиторий на main> && touch f`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
#### Scenario: Каталог для worktree в не-git workspace
|
||||
- **WHEN** cwd — каталог workspace вне git, команда `mkdir -p .wt/6-x`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Запись внутри bash -c
|
||||
- **WHEN** cwd — репозиторий на main, команда `bash -c 'echo x > f'`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
### Requirement: Неразрешимая файловая цель пропускается
|
||||
Если путь файловой цели нельзя определить без выполнения команды (переменная кроме `HOME`, подстановка команды,
|
||||
`cd -`), guard-main SHALL пропустить такую цель. `~`, `$HOME` и `${HOME}` MUST раскрываться.
|
||||
|
||||
#### Scenario: Цель в переменной
|
||||
- **WHEN** cwd — репозиторий на main, команда `echo x > "$OUT"`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Цель через HOME
|
||||
- **WHEN** команда `echo x > $HOME/<путь в репозитории на main>/f`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
### Requirement: Git-мутации на защищённой ветке
|
||||
guard-main SHALL блокировать git-мутации — `commit`, `merge`, `rebase`, `cherry-pick`, `revert`, `reset`, `am`,
|
||||
`apply` (кроме `--check`/`--stat`), `add`, `rm`, `mv`, `restore`, `stash` (кроме `list`/`show`), `checkout -- <пути>` — в репозитории на
|
||||
защищённой ветке. Репозиторий MUST определяться по `git -C <dir>`, иначе по текущему каталогу разбора. Прочие
|
||||
подкоманды, включая `worktree`, `switch`, `checkout -b`, `branch`, `fetch`, `pull`, MUST NOT считаться мутациями.
|
||||
|
||||
#### Scenario: Коммит в main
|
||||
- **WHEN** cwd — репозиторий на main, команда `git add -A && git commit -m x`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
#### Scenario: Коммит в worktree через -C при cwd на main
|
||||
- **WHEN** cwd — основной checkout на main, команда `git -C <worktree на ветке> commit -m x`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Создание worktree через -C
|
||||
- **WHEN** cwd — workspace вне git, команда `git -C <репозиторий на main> worktree add ../.wt/x -b feat/x`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Обновление main после merge
|
||||
- **WHEN** cwd — репозиторий на main, команда `git pull --ff-only`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Список stash на main
|
||||
- **WHEN** cwd — репозиторий на main, команда `git stash list`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
### Requirement: Смена ветки внутри команды
|
||||
`git switch -c|-C <ветка>`, `git switch <ветка>`, `git checkout -b|-B <ветка>` и `git checkout main|master` SHALL
|
||||
менять ветку репозитория для последующих целей той же команды. Если новую ветку нельзя определить (`-`, переменная),
|
||||
последующие git-мутации в этом репозитории MUST блокироваться, а файловые цели — пропускаться.
|
||||
|
||||
#### Scenario: Архивная ветка и коммит в одной команде
|
||||
- **WHEN** cwd — репозиторий на main, команда `git switch -q -c chore/archive-x && git add openspec && git commit -m a`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Смена ветки и запись
|
||||
- **WHEN** cwd — репозиторий на main, команда `git switch -q -c feat/16 && touch .bounded`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Ветка в переменной и коммит
|
||||
- **WHEN** cwd — репозиторий на main, команда `git switch "$B" && git commit -m x`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
#### Scenario: checkout main и merge из worktree
|
||||
- **WHEN** cwd — worktree на ветке, команда `git checkout main && git pull && git merge --no-ff feat/x && git push`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
#### Scenario: Переключение на предыдущую ветку
|
||||
- **WHEN** cwd — worktree на ветке, команда `git switch - && git merge x`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
### Requirement: Префиксы команды
|
||||
Голова команды SHALL определяться после присваиваний `VAR=x` и префиксов `sudo`, `env`, `nice`, `timeout`, `ionice`,
|
||||
`stdbuf`, `xargs`, `nohup`, `command`, `exec`, `time`, включая значения их опций (`nice -n 5`, `sudo -u u`,
|
||||
`timeout -s KILL 5`). `env -C <каталог>` MUST менять каталог только для этой команды.
|
||||
|
||||
#### Scenario: timeout перед push в main
|
||||
- **WHEN** cwd — worktree на ветке, команда `timeout 60 git push origin main`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
#### Scenario: nice -n перед коммитом в main
|
||||
- **WHEN** cwd — репозиторий на main, команда `nice -n 5 git commit -m x`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
### Requirement: Команды над самой ссылкой
|
||||
Для `rm`, `rmdir`, `unlink`, `ln` и `mv` местом записи SHALL считаться сама ссылка (каталог, где она лежит), а не её
|
||||
цель; для остальных целей ссылка MUST раскрываться.
|
||||
|
||||
#### Scenario: Удаление ссылки в workspace на main-репозиторий
|
||||
- **WHEN** cwd — каталог workspace вне git, команда `rm <workspace>/link`, где `link` указывает на репозиторий на main
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: Удаление ссылки внутри main
|
||||
- **WHEN** cwd — репозиторий на main, команда `rm LINK.md`, где `LINK.md` — ссылка наружу
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
### Requirement: Git-мутация с неопределённым репозиторием блокируется
|
||||
Если для git-мутации нельзя определить репозиторий (`-C` с переменной, `--git-dir`, `--work-tree`, неразрешимый
|
||||
текущий каталог), guard-main SHALL заблокировать вызов и в сообщении предложить `git -C <абсолютный путь>`.
|
||||
|
||||
#### Scenario: Репозиторий в переменной
|
||||
- **WHEN** команда `git -C "$REPO" commit -m x`
|
||||
- **THEN** guard-main завершается с кодом 2, сообщение содержит `git -C`
|
||||
|
||||
### Requirement: push проверяется по целевой ветке
|
||||
`git push` SHALL считаться мутацией защищённой ветки, только если он обновляет её на удалённом репозитории: push без
|
||||
refspec с текущей защищённой ветки, refspec `HEAD`, `main`, `master` или `<что угодно>:main|master`, флаги `--all` и
|
||||
`--mirror`. Push и удаление других веток MUST пропускаться при любом cwd.
|
||||
|
||||
#### Scenario: push с main
|
||||
- **WHEN** cwd — репозиторий на main, команда `git push`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
#### Scenario: Удаление ветки фичи из main-checkout
|
||||
- **WHEN** cwd — репозиторий на main, команда `git push origin --delete feat/6-x`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: push ветки фичи из main-checkout
|
||||
- **WHEN** cwd — репозиторий на main, команда `git push origin feat/6-x`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: push в main из worktree
|
||||
- **WHEN** cwd — worktree на ветке, команда `git push origin HEAD:main`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
### Requirement: Текущая ветка и теги в push
|
||||
Refspec-подстановка текущей ветки (`$(git branch --show-current)`, `$(git rev-parse --abbrev-ref HEAD)`,
|
||||
`$(git symbolic-ref --short HEAD)`) SHALL считаться push текущей ветки. `git push --tags` без refspec MUST NOT
|
||||
считаться push ветки.
|
||||
|
||||
#### Scenario: push текущей ветки через подстановку
|
||||
- **WHEN** cwd — worktree на ветке, команда `git push -u origin "$(git branch --show-current)"`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
#### Scenario: push тегов с main
|
||||
- **WHEN** cwd — репозиторий на main, команда `git push --tags`
|
||||
- **THEN** guard-main завершается с кодом 0
|
||||
|
||||
### Requirement: Без исключения для OpenSpec
|
||||
guard-main SHALL применять одни и те же правила к изменениям в `openspec/`, включая `openspec archive`: архив OpenSpec
|
||||
делается отдельным PR (решение владельца 11.10.2026).
|
||||
|
||||
#### Scenario: Коммит только openspec/ в main
|
||||
- **WHEN** в репозитории на main в индексе только файлы `openspec/`, команда `git commit -m archive`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
#### Scenario: openspec archive в main
|
||||
- **WHEN** cwd — репозиторий на main, команда `openspec archive x`
|
||||
- **THEN** guard-main завершается с кодом 2
|
||||
|
||||
### Requirement: Сбой разбора не блокирует
|
||||
Любой сбой — невалидный JSON на входе, отсутствие `python3`, внутренняя ошибка разбора — SHALL приводить к пропуску
|
||||
вызова (код 0) обоими хуками.
|
||||
|
||||
#### Scenario: Невалидный вход
|
||||
- **WHEN** на stdin хука подан текст `not json`
|
||||
- **THEN** хук завершается с кодом 0
|
||||
|
||||
#### Scenario: Нет python3
|
||||
- **WHEN** `python3` отсутствует в PATH
|
||||
- **THEN** хук завершается с кодом 0
|
||||
|
||||
### Requirement: Сообщение блокировки
|
||||
При блокировке хук SHALL писать в stderr одну строку, которая начинается с `BLOCKED [guard-main]:` или
|
||||
`BLOCKED [require-plan]:` и называет цель (путь или git-команду), корень репозитория и действие для выхода.
|
||||
|
||||
#### Scenario: Сообщение guard-main
|
||||
- **WHEN** guard-main блокирует Write в `docs/HARNESS.md` репозитория на main
|
||||
- **THEN** stderr начинается с `BLOCKED [guard-main]:` и содержит `docs/HARNESS.md`, путь корня и `worktree`
|
||||
|
||||
### Requirement: require-plan по репозиторию правимого файла
|
||||
require-plan SHALL определять корень, ветку, `.claude/code-paths`, `.bounded` и каталог планов
|
||||
`docs/superpowers/plans/` по git-репозиторию правимого файла, а не по cwd. Файл вне репозитория MUST пропускаться.
|
||||
|
||||
#### Scenario: Файл в другом репозитории без плана
|
||||
- **WHEN** cwd — репозиторий A с `.bounded`, Edit файла `src/a.py` в репозитории B без плана и без `.bounded`
|
||||
- **THEN** require-plan завершается с кодом 2
|
||||
|
||||
#### Scenario: Файл в worktree с .bounded при cwd на main
|
||||
- **WHEN** cwd — основной checkout, Edit файла `src/a.py` в worktree, в корне которого есть `.bounded`
|
||||
- **THEN** require-plan завершается с кодом 0
|
||||
|
||||
#### Scenario: Имя файла со скобками — литерал
|
||||
- **WHEN** Edit файла `src/[slug].ts` в репозитории без плана и без `.bounded`
|
||||
- **THEN** require-plan завершается с кодом 2
|
||||
|
||||
#### Scenario: code-paths из репозитория файла
|
||||
- **WHEN** в репозитории файла `.claude/code-paths` содержит `hooks/`, Edit файла `hooks/x.sh` без плана
|
||||
- **THEN** require-plan завершается с кодом 2
|
||||
|
||||
### Requirement: Правила require-plan, которые не меняются
|
||||
require-plan SHALL защищать по умолчанию любой каталог `src/` на любой глубине, а при наличии `.claude/code-paths` —
|
||||
только перечисленные префиксы. Правка разрешается при `.bounded` в корне, при плане, где упомянута ветка, при
|
||||
единственном плане в каталоге планов и при detached HEAD, если план есть.
|
||||
|
||||
#### Scenario: src на глубине без плана
|
||||
- **WHEN** Edit файла `api/src/a.py` в репозитории без планов и без `.bounded`
|
||||
- **THEN** require-plan завершается с кодом 2
|
||||
|
||||
#### Scenario: План упоминает ветку
|
||||
- **WHEN** в каталоге планов два плана, один из них содержит имя текущей ветки
|
||||
- **THEN** require-plan завершается с кодом 0
|
||||
|
||||
### Requirement: Регрессионный корпус хуков
|
||||
Пакет SHALL содержать корпус вызовов хуков из реальных транскриптов и живых случаев; каждая запись MUST иметь
|
||||
идентификатор, ожидаемый вердикт `allow|block` и код класса. Корпус MUST прогоняться через хуки в песочнице с раскладкой
|
||||
репозиториев из корпуса: тестами пакета по источникам и `doctor.sh` по установленным копиям.
|
||||
|
||||
#### Scenario: Расхождение в тестах пакета
|
||||
- **WHEN** хук из `hooks/` выносит по записи корпуса вердикт, отличный от ожидаемого
|
||||
- **THEN** `bash tests/run.sh` печатает FAIL с идентификатором и классом записи и завершается с ненулевым кодом
|
||||
|
||||
#### Scenario: Отчёт doctor
|
||||
- **WHEN** запущен `doctor.sh` при установленных хуках
|
||||
- **THEN** в разделе самотеста есть строка `корпус хуков: <совпало>/<всего>`, расхождения перечислены с идентификаторами
|
||||
|
||||
### Requirement: Выгрузка только настоящих блокировок
|
||||
Экстрактор блокировок из транскриптов SHALL выгружать только результаты инструмента, которые являются ошибкой хука, и
|
||||
MUST NOT выгружать обычный вывод команды, в котором встречается слово `BLOCKED`.
|
||||
|
||||
#### Scenario: cat исходника хука
|
||||
- **WHEN** в транскрипте результат `cat hooks/require-plan.sh` содержит строку `BLOCKED: editing`
|
||||
- **THEN** экстрактор эту запись не выгружает
|
||||
Loading…
x
Reference in New Issue
Block a user