288 lines
21 KiB
Markdown
288 lines
21 KiB
Markdown
# 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`
|