# 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 ` → `$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 `; неудачный клон MUST удалять недоклонированный каталог (полусостояние не остаётся). - Существующий клон MUST обновляться fetch'ем от `$DSH_SUPERPOWERS_REPO` (не от origin клона) с force-рефспеком тега (`+refs/tags/:refs/tags/`) и 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=` и флаг `--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 ()` и 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 /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: })` вызывается после установки - **THEN** выбранный путь — `$HOME/.dsh/skills/using-superpowers/SKILL.md`, текст содержит маркер ``, предупреждений о 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`: пустое дерево не устанавливается. - В `/.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.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: not-ok: `. - Отсутствие службы 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`