2026-10-25 01:13:10 +04:00

288 lines
21 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.

# dsh-install Specification
## Purpose
Слой установки DSH-обвязки: откуда берутся superpowers-скиллы и роли, что
делает `dsh/install.sh --skills/--roles`, какие пути попадают в установленные
инструкции. В Claude Code источником служит кэш плагина в `~/.claude`; в DSH
источник задаётся установщиком явно (аргумент, переменная или пиннинг в
`~/.dsh`), а `~/.claude` остаётся только legacy-fallback'ом с предупреждением.
Контракт нужен, чтобы свежая установка на машине без Claude Code проходила
полный цикл #48 и чтобы путь `~/.claude` не возвращался в runtime незаметно.
## 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`, предупреждения нет
### Requirement: Установленные инструкции DSH не ссылаются на ~/.claude
`dsh/install.sh --global` MUST писать `$DSH_HOME/AGENTS.md` так, чтобы пути
ролей указывали на DSH-слой, а формат инструкций не выдавался за runtime
Claude Code.
- Строки `~/.claude/roles` в установленной копии MUST заменяться на
`~/.dsh/roles`, а adapter-шапка MUST NOT называть пути `~/.claude`: в
установленном файле не остаётся ни одного такого пути. `global/CLAUDE.md` в
репозитории MUST NOT меняться (его читает Claude Code).
- Шапка установленного файла MUST говорить, что проектный `CLAUDE.md` /
`AGENTS.md` — это формат файлов инструкций DSH, а не запущенный Claude Code,
и что роли лежат в `~/.dsh/roles` (`--roles`), а superpowers-скиллы — в
`~/.dsh/skills` (`--skills`).
- Повторный `--global` обязан делать бэкап прежнего `AGENTS.md` (как сейчас).
#### Scenario: Пути ролей после установки
- **WHEN** выполнен `--global` во временный `DSH_HOME`
- **THEN** в `AGENTS.md` есть `~/.dsh/roles/dev.md` и нет ни одного пути
`~/.claude`
#### Scenario: global/CLAUDE.md не тронут
- **WHEN** выполнен `--global`
- **THEN** `global/CLAUDE.md` в репозитории побайтно не изменился
### Requirement: Чистый HOME без ~/.claude проходит установку
Установка, bootstrap и skill `role` MUST работать на машине, где Claude Code
никогда не устанавливался.
- `--global --skills --roles` с явным источником superpowers в пустом `HOME`
MUST создавать `$DSH_HOME/{AGENTS.md,skills/using-superpowers/SKILL.md,skills/role/SKILL.md,skills/sdd/SKILL.md,roles/dev.md}`.
- bootstrap MUST резолвить скилл из `$DSH_HOME/skills` при `home` этого HOME и
MUST NOT обращаться к `~/.claude`.
- Резолвер ролей MUST находить `$DSH_HOME/roles` при `home` этого HOME.
#### Scenario: Установка в чистый HOME
- **WHEN** `HOME` — пустой temp-каталог, `DSH_HOME=$HOME/.dsh`,
`--superpowers-from` указывает на каталог superpowers
- **THEN** `--global --skills --roles` завершается кодом 0, все перечисленные
файлы на месте, `--check` зелёный по skills и roles
#### Scenario: Bootstrap в чистом HOME
- **WHEN** `prepareBootstrap({ home: <temp HOME> })` вызывается после установки
- **THEN** выбранный путь — `$HOME/.dsh/skills/using-superpowers/SKILL.md`,
текст содержит маркер `<!-- DSH-HARNESS-BOOTSTRAP:... -->`, предупреждений о
legacy нет
### 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`