Gate OpenSpec changes onto a worktree #42

Open
opened 2026-08-10 13:10:10 +00:00 by claude · 0 comments
Collaborator

Summary

PreToolUse-хук hooks/spec-guard.sh: не даёт запустить openspec new change на
main, заводит worktree ../tea-worktrees/<slug> с веткой feat/<slug> и
возвращает модели инструкцию войти в него.

Spec

AGENTS.md

Depends on

  • commit-the-openspec-scaffold — без openspec/ в git свежий worktree бесполезен

Motivation

Change — это спека, тест-кейсы, задачи и код. Всё это одна единица работы и
должно ревьюиться одним PR, а значит жить на одной ветке с первого артефакта, а
не с первой строки кода.

Принудить это можно только хуком. Схема openspec — строгие данные, shell в
ней нет. Сгенерированные .claude/commands/opsx/*.md перезаписываются
openspec update (они рендерятся из dist/core/templates/workflows/propose.js),
так что правка там держится до первого апгрейда.

Хук не может вызвать EnterWorktree. Хук — внешний процесс, его контракт:
passthrough, updatedInput, additionalContext или exit 2 + stderr. Тулы он не
вызывает. Поэтому форма ровно как у hooks/tea-guard.sh:33 — блок с точной
инструкцией, а действие делает модель, прочитав stderr как фидбек.

Грязное дерево блокируем. Технически git worktree add на грязном дереве
работает. Ломается не git, а человек: незакоммиченные правки остаются в
main-чекауте, сессия уезжает в worktree, работа осиротела — и это ровно тот
случай, когда правки как раз про этот change. Блокируем tracked-изменения;
untracked не блокируем, иначе шум и обход правила.

Строка в AGENTS.md входит в объём. EnterWorktree по своему контракту
запускается только когда worktree разрешён пользователем напрямую или
инструкциями проекта. Без разрешения в AGENTS.md хук блокирует, модель
отказывается разблокировать, и propose встаёт намертво. Хук и строка — один
результат, порознь они ломают процесс.

Матрица поведения:

Условие Действие
openspec не в PATH или в корне нет openspec/ passthrough
команда не openspec new change и не openspec archive passthrough
текущая ветка не main passthrough
main, есть tracked-изменения exit 2 — commit или stash→pop
main, чисто, new change <slug> git worktree add ../tea-worktrees/<slug> -b feat/<slug> HEAD, затем exit 2 с EnterWorktree(path: …) и «re-run this exact command»
git worktree add вернул ненулевой код exit 2 со stderr git'а дословно
main, чисто, archive passthrough
не main, archive exit 2 — архив только на main после мержа

Acceptance criteria

  • hooks/spec-guard.sh существует и зарегистрирован в hooks/hooks.json
    как PreToolUse с matcher Bash
  • passthrough, если openspec не в PATH или в корне репозитория нет
    каталога openspec/ — плагин молчит у тех, кто openspec не ставил
  • passthrough на любой команде, кроме openspec new change и
    openspec archive
  • passthrough, если текущая ветка не main
  • на main при непустом git status --porcelain --untracked-files=no
    exit 2, и текст называет оба выхода: закоммитить или
    git stash → повтор → git stash pop внутри worktree
  • untracked-файлы сами по себе не блокируют
  • на чистом main openspec new change <slug> создаёт
    ../tea-worktrees/<slug> с веткой feat/<slug> от HEAD, затем exit 2 с
    инструкцией EnterWorktree(path: …) и требованием повторить ту же команду
  • при падении git worktree add (ветка занята, путь занят) — exit 2 со
    stderr git'а, и ничего не создано наполовину
  • openspec archive не на main — exit 2
  • AGENTS.md разрешает worktree явно и описывает hooks/spec-guard.sh в
    разделе про hooks/
  • хук не выполняет git stash сам

Constraints

  • Не входит в объём: тесты — test-the-spec-guard-and-the-linkage-linter.
  • Не входит в объём: выбор типа ветки. Всегда feat/; fix/ переименовывается
    руками, автоматически тип из слага не выводится — существующие ветки репы
    (feat/close-script, fix/no-store-for-label-payloads) слаг с типом не
    связывают.
  • Не входит в объём: удаление worktree после мержа и ExitWorktree.
  • Не входит в объём: worktree.baseRef из настроек Claude Code — worktree
    создаёт хук, стартовая точка задаётся явно.
<!-- tea:id gate-openspec-changes-onto-a-worktree --> ## Summary PreToolUse-хук `hooks/spec-guard.sh`: не даёт запустить `openspec new change` на main, заводит worktree `../tea-worktrees/<slug>` с веткой `feat/<slug>` и возвращает модели инструкцию войти в него. ## Spec `AGENTS.md` ## Depends on - commit-the-openspec-scaffold — без `openspec/` в git свежий worktree бесполезен ## Motivation Change — это спека, тест-кейсы, задачи и код. Всё это одна единица работы и должно ревьюиться одним PR, а значит жить на одной ветке с первого артефакта, а не с первой строки кода. **Принудить это можно только хуком.** Схема openspec — строгие данные, shell в ней нет. Сгенерированные `.claude/commands/opsx/*.md` перезаписываются `openspec update` (они рендерятся из `dist/core/templates/workflows/propose.js`), так что правка там держится до первого апгрейда. **Хук не может вызвать `EnterWorktree`.** Хук — внешний процесс, его контракт: passthrough, `updatedInput`, `additionalContext` или exit 2 + stderr. Тулы он не вызывает. Поэтому форма ровно как у `hooks/tea-guard.sh:33` — блок с точной инструкцией, а действие делает модель, прочитав stderr как фидбек. **Грязное дерево блокируем.** Технически `git worktree add` на грязном дереве работает. Ломается не git, а человек: незакоммиченные правки остаются в main-чекауте, сессия уезжает в worktree, работа осиротела — и это ровно тот случай, когда правки как раз про этот change. Блокируем tracked-изменения; untracked не блокируем, иначе шум и обход правила. **Строка в AGENTS.md входит в объём.** `EnterWorktree` по своему контракту запускается только когда worktree разрешён пользователем напрямую или инструкциями проекта. Без разрешения в `AGENTS.md` хук блокирует, модель отказывается разблокировать, и propose встаёт намертво. Хук и строка — один результат, порознь они ломают процесс. Матрица поведения: | Условие | Действие | |---|---| | `openspec` не в PATH или в корне нет `openspec/` | passthrough | | команда не `openspec new change` и не `openspec archive` | passthrough | | текущая ветка не main | passthrough | | main, есть tracked-изменения | exit 2 — commit или stash→pop | | main, чисто, `new change <slug>` | `git worktree add ../tea-worktrees/<slug> -b feat/<slug> HEAD`, затем exit 2 с `EnterWorktree(path: …)` и «re-run this exact command» | | `git worktree add` вернул ненулевой код | exit 2 со stderr git'а дословно | | main, чисто, `archive` | passthrough | | не main, `archive` | exit 2 — архив только на main после мержа | ## Acceptance criteria - [ ] `hooks/spec-guard.sh` существует и зарегистрирован в `hooks/hooks.json` как PreToolUse с matcher `Bash` - [ ] passthrough, если `openspec` не в PATH или в корне репозитория нет каталога `openspec/` — плагин молчит у тех, кто openspec не ставил - [ ] passthrough на любой команде, кроме `openspec new change` и `openspec archive` - [ ] passthrough, если текущая ветка не main - [ ] на main при непустом `git status --porcelain --untracked-files=no` — exit 2, и текст называет оба выхода: закоммитить или `git stash` → повтор → `git stash pop` внутри worktree - [ ] untracked-файлы сами по себе не блокируют - [ ] на чистом main `openspec new change <slug>` создаёт `../tea-worktrees/<slug>` с веткой `feat/<slug>` от `HEAD`, затем exit 2 с инструкцией `EnterWorktree(path: …)` и требованием повторить ту же команду - [ ] при падении `git worktree add` (ветка занята, путь занят) — exit 2 со stderr git'а, и ничего не создано наполовину - [ ] `openspec archive` не на main — exit 2 - [ ] `AGENTS.md` разрешает worktree явно и описывает `hooks/spec-guard.sh` в разделе про `hooks/` - [ ] хук не выполняет `git stash` сам ## Constraints - Не входит в объём: тесты — `test-the-spec-guard-and-the-linkage-linter`. - Не входит в объём: выбор типа ветки. Всегда `feat/`; `fix/` переименовывается руками, автоматически тип из слага не выводится — существующие ветки репы (`feat/close-script`, `fix/no-store-for-label-payloads`) слаг с типом не связывают. - Не входит в объём: удаление worktree после мержа и `ExitWorktree`. - Не входит в объём: `worktree.baseRef` из настроек Claude Code — worktree создаёт хук, стартовая точка задаётся явно.
claude added this to the openspec integration milestone 2026-08-10 13:10:10 +00:00
claude added the
type
task
comp/hookscomp/openspec
labels 2026-08-10 13:10:10 +00:00
claude added a new dependency 2026-08-10 13:10:11 +00:00
claude added a new dependency 2026-08-10 13:10:11 +00:00
claude added a new dependency 2026-08-10 13:10:12 +00:00
Sign in to join this conversation.