242 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Spec Delta: dsh-install
Дельта change `dsh-port-v2` (#69). Доставка superpowers переходит с «сырой
копии клона» на sync по пину `v7.0.1`, бандл получает самопроверку контракта.
**BREAKING:** `--skills` без явного источника теперь требует сеть (клон пина);
офлайн-прогон без клона — `FAIL`, а не тихий legacy и не «WARN + код 0».
Отмена #67 (авто-pull unpinned HEAD) — решение design.md D-2.
## ADDED Requirements
### Requirement: Sync по пину
`dsh/install.sh --skills` без явного источника MUST получить superpowers из
клона `$DSH_HOME/superpowers` на пине `$DSH_SUPERPOWERS_REF` (default
`v7.0.1`) из `$DSH_SUPERPOWERS_REPO` (default
`https://github.com/obra/superpowers`) и MUST прогнать sync
(`dsh/sync/sync-from-upstream.mjs`) и verify (`dsh/sync/verify-skills.mjs`) до
копирования в `$DSH_HOME/skills`.
- Копирование MUST брать только synced-дерево; сырой текст апстрима при пине
MUST NOT попадать в `$DSH_HOME/skills`. Адаптации: `superpowers:`-префиксы
сняты, ссылки ведут на `using-superpowers/references/dsh-tools.md` из
`dsh/bundle/`, чужие harness-референсы не копируются.
- Sync MUST завершаться `FAIL` (код 1) на несовпадении якоря transform-а
(ноль или больше одного совпадения), на изменении набора скиллов и на
отсутствии `skills/` в checkout. При любом падении назначение (`--out`)
MUST остаться прежним, а staging-каталог MUST быть удалён.
- Verify MUST проверять YAML frontmatter (`name` и `description` непусты, без
дублей ключей), отсутствие остаточных `superpowers:`-префиксов в телах
`SKILL.md` и резолв всех относительных ссылок внутри synced-дерева. Дерево,
прошедшее verify с нулём скиллов (`verified 0 skills`), MUST трактоваться
как `FAIL`: пустое дерево не устанавливается.
- В `<out>/.sync-meta.json` MUST записываться `ref` (значение `--ref`) и
`version` (версия из `.claude-plugin/plugin.json` апстрима); `--check` MUST
печатать ref и version из этого файла, не обращаясь в сеть.
- Повторный `--skills` MUST быть идемпотентным: каталоги `*.tmp-*` не
остаются, установленные копии заменяются целиком (`rm -rf` + `cp -R`), а не
дополняются.
#### Scenario: Свежий HOME, установка по пину
- **WHEN** `HOME` пуст, флаг и `DSH_SUPERPOWERS_SOURCE` не заданы, а
`DSH_SUPERPOWERS_REPO` указывает на репозиторий с тегом `v7.0.1`
- **THEN** `--skills` клонирует пин, печатает `synced N files` и
`verified 15 skills`, копирует адаптированные скиллы; в
`using-superpowers/SKILL.md` есть `references/dsh-tools.md` и нет
`superpowers:`, а `references/dsh-tools.md` совпадает с
`dsh/bundle/dsh-tools.md`
#### Scenario: Сломанный якорь апстрима
- **WHEN** checkout на пине не содержит якоря одного из transform-ов
- **THEN** sync печатает `FAIL` с именем transform-а и завершается кодом 1,
прежнее `--out` и `$DSH_HOME/skills` не изменены
#### Scenario: Изменился набор скиллов
- **WHEN** в checkout на пине появился или исчез каталог скилла относительно
ожидаемого списка
- **THEN** sync печатает `added upstream:` / `removed upstream:` и завершается
кодом 1; установка ничего не копирует
#### Scenario: Verify нашёл висячую ссылку
- **WHEN** synced-дерево содержит ссылку на несуществующий файл (markdown или
inline-код)
- **THEN** verify печатает `<skill>/SKILL.md: dangling relative reference: …` и
завершается кодом 1; в `$DSH_HOME/skills` ничего не копируется
#### Scenario: Нулевое дерево
- **WHEN** verify завершился кодом 0, но сообщил `verified 0 skills`
- **THEN** установка завершается `FAIL`, ни один скилл не скопирован
#### Scenario: Повторный прогон
- **WHEN** `--skills` запущен второй раз на том же пине
- **THEN** установленные скиллы заменены, в `$DSH_HOME/superpowers` нет
каталогов `*.tmp-*`, а `.sync-meta.json.ref` равен пину
### Requirement: Самопроверка контракта бандла
Бандл `dsh-harness` MUST при активации проверять контракт DSH, на который
опирается, и MUST предоставлять команду `dsh-harness-check` для повторного
прогона той же проверки.
- Проверка MUST покрывать `ctx.tools.guard`, `ctx.on` для `agent/pre-step`,
`ctx.inject` (регистрация команд) и `workingDirectory.get`. Каждая
отсутствующая служба MUST логироваться строкой
`[dsh-harness] self-check: <feature> not-ok: <detail>`.
- Отсутствие службы MUST отключать только свою фичу: без `ctx.tools.guard`
guard-обвязка пропускается, bootstrap и `/role` продолжают работать при
своих службах. Активация бандла MUST NOT падать целиком из-за несовпадения
контракта.
- Команда `dsh-harness-check` MUST возвращать текстовый отчёт с одной строкой
`ok`/`not-ok` на каждую проверяемую фичу.
- Ключи конфига `selfCheck` и `checkCommand` (default `true`) MUST позволять
выключить активационную проверку и команду соответственно.
- Терминальный `--check` MUST оставаться офлайн-проверкой файлов и окружения:
SDK внутри `app.asar` ему недоступен, контракт проверяется только изнутри
DSH.
#### Scenario: Сломанный контракт при активации
- **WHEN** бандл активируется в `ctx` без `tools.guard`
- **THEN** в лог попадает `self-check: guards not-ok: tools.guard`, guard-wiring
пропускается, bootstrap и команды продолжают работать, бандл не падает
#### Scenario: Проверка по требованию
- **WHEN** пользователь вызывает `/dsh-harness-check`
- **THEN** отчёт перечисляет каждую фичу со статусом `ok` или `not-ok`
## MODIFIED Requirements
### Requirement: Источник superpowers задаётся при установке
`dsh/install.sh --skills` SHALL разрешать источник superpowers-скиллов в
порядке приоритета. Сеть нужна только ветке пина; явный источник
синхронизируется локально.
- Порядок: `--superpowers-from <dir>` → `$DSH_SUPERPOWERS_SOURCE` →
клон `$DSH_HOME/superpowers` на `$DSH_SUPERPOWERS_REF` →
legacy-кэш Claude Code → legacy `~/.claude/skills`.
- Явный источник (`--superpowers-from`, `$DSH_SUPERPOWERS_SOURCE`) MUST NOT
требовать сети и MUST NOT проверять пин. Допускаются две формы: корень
checkout (есть `skills/using-superpowers/SKILL.md`) или каталог `skills`
такого корня. Синк явного источника MUST идти в staging вне каталога клона
(`$DSH_HOME/.skills.synced`), потому что `$DSH_HOME/superpowers` зарезервирован
под клон пина.
- Каталог скиллов, который не является ни checkout-корнем, ни его `skills/`,
MUST быть `FAIL` (код 1) и в `--skills`, и в `--check`: вердикты двух команд
MUST совпадать. Тихий откат на legacy запрещён.
- Клон-пин: отсутствующий или пустой каталог MUST клонироваться
`git clone --depth 1 --branch <ref> <repo> <dir>`; неудачный клон MUST
удалять недоклонированный каталог (полусостояние не остаётся).
- Существующий клон MUST обновляться fetch'ем от `$DSH_SUPERPOWERS_REPO` (не от
origin клона) с force-рефспеком тега
(`+refs/tags/<ref>:refs/tags/<ref>`) и checkout `FETCH_HEAD`: смена
репозитория и переехавший тег MUST подхватываться следующим прогоном.
Офлайн клон MAY переиспользоваться, только если HEAD уже ровно на пине, и
тогда MUST печататься `WARN`; иначе — `FAIL` с командой клона.
- Непустой каталог на месте клона без рабочего `.git` MUST быть `FAIL` (код 1)
без изменения каталога.
- Копирование MUST быть заменой каталога назначения (`rm -rf` + `cp -R`), а не
симлинком: `~/.dsh/skills` не должен зависеть от каталога-источника.
#### Scenario: Явный источник без ~/.claude
- **WHEN** `HOME` — пустой temp-каталог без `~/.claude`, а `--superpowers-from`
указывает на каталог superpowers
- **THEN** `--skills` синхронизирует и копирует скиллы в `$DSH_HOME/skills`,
печатает источник и завершается кодом 0, не обращаясь в сеть
#### Scenario: Переменная окружения вместо флага
- **WHEN** задан `DSH_SUPERPOWERS_SOURCE=<dir>` и флаг `--superpowers-from` не передан
- **THEN** источник берётся из переменной; поведение совпадает с флагом
#### Scenario: Флаг важнее переменной
- **WHEN** заданы и `--superpowers-from`, и `DSH_SUPERPOWERS_SOURCE`
- **THEN** используется каталог из флага
#### Scenario: Невалидный явный источник
- **WHEN** `--superpowers-from` указывает на каталог без
`using-superpowers/SKILL.md`
- **THEN** `--skills` печатает `FAIL` с ожидаемой структурой и завершается
кодом 1, ничего не копируя из legacy-источников
#### Scenario: Голый каталог скиллов
- **WHEN** явный источник — каталог `my-skills` с
`using-superpowers/SKILL.md`, но без checkout-корня
- **THEN** и `--skills`, и `--check` печатают `FAIL: explicit superpowers source
must be a checkout root or its skills/ directory` и завершаются кодом 1
#### Scenario: Пиннинг в ~/.dsh
- **WHEN** существует клон `$DSH_HOME/superpowers` с
`skills/using-superpowers/SKILL.md` и флаг/переменная не заданы
- **THEN** источником служит этот клон, а `--check` офлайн печатает
`ok: superpowers source (<path>)` и ref из `.sync-meta.json`
#### Scenario: Обновление клона от DSH_SUPERPOWERS_REPO
- **WHEN** клон уже есть, а `DSH_SUPERPOWERS_REPO` сменился или тег переехал
- **THEN** прогон фетчит от `DSH_SUPERPOWERS_REPO` с force-рефспеком, делает
checkout `FETCH_HEAD` и устанавливает текст новой версии тега
#### Scenario: Офлайн-переиспользование клона
- **WHEN** клон существует, HEAD ровно на пине, сеть недоступна
- **THEN** `--skills` печатает `WARN: offline; reusing the existing clone` и
завершается кодом 0
#### Scenario: Каталог без .git на месте клона
- **WHEN** `$DSH_HOME/superpowers` непустой и не является git-клоном
- **THEN** `--skills` завершается `FAIL` (код 1), каталог не изменяется
### Requirement: Отсутствие источника не бывает тихим
Если источник superpowers недоступен, `--skills` MUST завершаться `FAIL`, кроме
случая, когда существует legacy-источник.
- Офлайн, без явного источника, без клона на пине и без legacy MUST печататься
`FAIL: no superpowers source` с готовой командой клона
(`git clone --depth 1 --branch <ref> <repo> <DSH_HOME>/superpowers`) и код
возврата 1. Тихий успех запрещён даже при уже установленных копиях.
- Уже установленные копии в `$DSH_HOME/skills` MUST NOT удаляться или
портиться при таком `FAIL`.
- Если legacy-источник существует — скиллы копируются сырым текстом с
`WARN: legacy superpowers source` и кодом 0 (путь живёт до #66).
- Локальные скиллы обвязки (`dsh/skills/*`: `role`, `sdd`) MUST ставиться в
любом исходе: они не зависят от superpowers.
#### Scenario: Первая установка без источника
- **WHEN** `HOME` пуст, `$DSH_HOME/skills` не существует, клон получить не
удалось, legacy нет
- **THEN** `--skills` печатает `FAIL: no superpowers source` и команду клона,
код 1, но `$DSH_HOME/skills/role/SKILL.md` и `$DSH_HOME/skills/sdd/SKILL.md`
на месте
#### Scenario: Повторная установка без источника
- **WHEN** `$DSH_HOME/skills/using-superpowers/SKILL.md` уже есть, а клон
получить не удалось
- **THEN** `--skills` печатает `FAIL: no superpowers source`, код 1,
установленный файл не изменён (раньше здесь был `WARN` и код 0)
### Requirement: ~/.claude — только legacy-fallback с предупреждением
superpowers-скиллы и роли MUST иметь рабочий путь `~/.dsh`; пути `~/.claude`
SHALL оставаться fallback'ом для машин, где `--skills --roles` ещё не
перезапускали, и MUST сопровождаться предупреждением.
- Установка: legacy-кэш Claude Code и `~/.claude/skills` MAY быть источником
только после того, как клон пина получить не удалось; копирование в этом
случае — сырое (без sync и verify), с
`WARN: legacy superpowers source` и подсказкой перейти на пин/явный источник.
- `--check`: legacy-источник — `WARN`, явный/пин — `ok`, отсутствие — `WARN` с
подсказкой клона, невалидный явный источник — `FAIL` и код 1
(пользователь задал его сам, подсказка «нет источника» здесь неверна).
- Runtime: bootstrap и резолвер ролей сохраняют порядок `~/.dsh` → `~/.claude`,
но при выборе legacy-пути один раз за процесс пишут в лог предупреждение.
- Удаление fallback'ов — отдельное изменение (#66); в этом change они не
удаляются.
#### Scenario: Legacy-кэш как источник установки
- **WHEN** в `HOME` есть только кэш Claude Code с superpowers, `$DSH_HOME` пуст
- **THEN** `--skills` копирует скиллы и печатает `WARN: legacy superpowers
source`
#### Scenario: Runtime-предупреждение
- **WHEN** bootstrap резолвит `using-superpowers` из `~/.claude/...`
- **THEN** в лог один раз за процесс попадает предупреждение с предложением
выполнить `dsh/install.sh --skills`
#### Scenario: ~/.dsh приоритетнее
- **WHEN** существуют и `~/.dsh/skills/using-superpowers/SKILL.md`, и legacy-копия
- **THEN** используется `~/.dsh`, предупреждения нет