152 lines
12 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.

# Design: установка DSH без зависимости от ~/.claude
Issue: #49. Спека: `openspec/changes/dsh-install-source/specs/dsh-install/spec.md`.
Исходные факты: `dsh/PORT.md` §3/§5/§7, issue #37/#38 (runtime-отвязка уже
сделана), ответ владельца 24.10.2026 («нормально так» — без вендоринга).
## 1. Что уже сделано и чего не хватает
| Слой | Источник в runtime | Зависимость от `~/.claude` |
|---|---|---|
| bootstrap `using-superpowers` | `~/.dsh/skills` → `~/.agents/skills` → кэш CC → `~/.claude/skills` | только fallback |
| роли `/role`, skill `role` | `~/.dsh/roles` → `~/.claude/roles` → repo `roles/` | только fallback |
| установка `--skills` | кэш плагина CC | **жёсткая** |
| установка `--roles` | repo `roles/` | нет |
| установленные инструкции | `global/CLAUDE.md` + adapter-шапка | **текст путей ролей** |
То есть #37 снял зависимость в том, что исполняется, и оставил её в том, что
устанавливается. Первая же команда `--skills` на чистой машине не находит
источник, печатает `WARN` и оставляет bootstrap без скилла — при этом установка
формально успешна.
## 2. Решения
### D-1 (решение владельца). Без вендоринга superpowers
Копию апстрима в репозиторий не кладём. Обоснование: superpowers живёт своей
версией (в кэше уже 6.4.1 и 7.0.0), репозиторий обвязки не должен превращаться
в зеркало; обновление вендорной копии — ручная синхронизация, которая стареет
молча. Цена: install-time источник нужно получить один раз (клон), это лишний
шаг в quick start.
### D-2. Три уровня источника, один формат
Порядок: `--superpowers-from` → `DSH_SUPERPOWERS_SOURCE` →
`$DSH_HOME/superpowers/skills` → legacy-кэш CC → legacy `~/.claude/skills`.
- Флаг и переменная принимают **оба** каталога: корень superpowers (с `skills/`)
и сам `skills/`. Клон `git clone … ~/.dsh/superpowers` даёт первый вид, кэш
CC — второй; пользователь не должен помнить, какой именно.
- `$DSH_HOME/superpowers` — рекомендуемое место пиннинга: один клон на машину,
дальше `--skills` воспроизводим без флага и без `~/.claude`.
- Явно заданный невалидный источник — `FAIL`, а не fallback: иначе опечатка в
пути тихо даёт установку из legacy-кэша, и на чистой машине это снова
«установка прошла, bootstrap пуст».
### D-3. `FAIL` только когда ставить нечего
`--skills` без источника:
- `$DSH_HOME/skills/using-superpowers/SKILL.md` есть → `WARN` + код 0
(идемпотентный повтор, старые копии не трогаем);
- нет → `FAIL` + код 1 + готовая команда клона.
Локальные скиллы (`role`, `sdd`) ставятся в обоих случаях: они из репозитория и
от superpowers не зависят. Из-за этого `--skills` может вернуть 1, уже записав
часть файлов — это сказано в `FAIL`-сообщении.
### D-4. Legacy-пути остаются, но говорят о себе
`~/.claude` поддерживается как fallback: машины, где `--skills --roles` ещё не
перезапускали, не должны сломаться на этом change. Предупреждения:
- `--skills` из legacy-источника → `WARN: legacy superpowers source`;
- `--check` → `WARN` для legacy и для «источника нет»;
- bootstrap/`resolveRolesDir` при выборе `~/.claude` → одно предупреждение за
процесс (module-level `Set`), не на каждый `agent/pre-step`.
Удаление fallback'ов — отдельная задача: сначала все машины должны переехать на
`~/.dsh` (и это видно по `WARN`), потом код можно чистить. Цена: путь
`~/.claude` остаётся в bundle, и code review видит его ещё один цикл.
### D-5. Инструкции переписываются при установке, не в источнике
`global/CLAUDE.md` — общий с Claude Code, его правит только CC-контур. Поэтому
`install_global` пишет `AGENTS.md` в два шага: adapter-шапка (как сейчас, плюс
явная строка про формат инструкций) и тело `global/CLAUDE.md`, прогнанное через
замену `~/.claude/roles` → `~/.dsh/roles`.
Замена точечная и проверяемая тестом: после `--global` в `AGENTS.md` есть
`~/.dsh/roles/dev.md` и нет `~/.claude/roles`; сам `global/CLAUDE.md` не меняется
(тест сравнивает содержимое до/после). Цена: расхождение между источником и
установленной копией теперь intentional, и его надо помнить при правках CC-текста.
### D-6. `--check` не падает без источника
`--check` — диагностика, а не установка: отсутствие superpowers-источника
остаётся `WARN`, код возврата меняют только жёсткие `FAIL` (python3, git, node,
хуки, бандл). Так же уже сделано для openspec (#42, R-4). Исключение —
невалидный **явный** источник (`--superpowers-from`/`DSH_SUPERPOWERS_SOURCE`):
это ошибка конфигурации пользователя, `--check` даёт `FAIL`, иначе опечатка
выглядит как «источника нет».
## 3. Формулировки, которые попадут в установленный AGENTS.md
- `DSH adapter: role files are ~/.dsh/roles (installed by --roles); legacy ~/.claude/roles is a migration fallback.`
- `DSH adapter: superpowers skills are ~/.dsh/skills (installed by --skills from --superpowers-from, DSH_SUPERPOWERS_SOURCE, or ~/.dsh/superpowers); the ~/.claude cache is a legacy fallback.`
- `DSH adapter: a project CLAUDE.md/AGENTS.md is the instruction-file format of this harness; it does not imply a Claude Code runtime.`
- Тело `global/CLAUDE.md` — с заменой `~/.claude/roles` → `~/.dsh/roles`; строка
про `role-hint` остаётся, adapter-шапка говорит, что хука в DSH нет и роль
выводится из первого сообщения.
## 4. Решения, принятые без обсуждения
- **2026-10-24. Клон для пиннинга — `git clone --depth 1 https://github.com/obra/superpowers ~/.dsh/superpowers`.**
Это апстрим superpowers (public repo). `install.sh` его не запускает: сеть в
установке запрещена (D-1/спека). Цена ошибки: неверный URL — баг
документации, ловится первой же установкой на чистой машине; лечится одной
строкой в README/PORT.
- **2026-10-24. `--superpowers-from` принимает и корень, и `skills/`.**
Обоснование: два реальных источника дают разные формы (клон и кэш CC).
Цена: две ветки разбора и два теста; ошибка разбора видна сразу по `FAIL`.
- **2026-10-24. Предупреждение о legacy в runtime — один раз за процесс.**
`agent/pre-step` вызывается на каждом шаге; предупреждение на каждый шаг
утопило бы лог. Цена: после перезапуска процесса предупреждение повторится —
это ожидаемо; диагностика — `--check`.
- **2026-10-24. `--check` проверяет у пиннинга только наличие `using-superpowers`.**
Полная инвентаризация набора — работа `--skills` (он копирует всё, что есть).
Цена: неполный клон пройдёт `--check` и даст частичный набор скиллов; признак
— отсутствие ожидаемого имени в каталоге скиллов сессии.
- **2026-10-24. `tasks.md` OpenSpec не заполняется.**
Задачи живут в плане superpowers, как требует `CLAUDE.md` репозитория. Цена:
`openspec status` показывает незаполненный `tasks.md`; `archive` это не
блокирует (уже так в `dsh-openspec`).
## 5. Риски
- **`set -euo pipefail` против «частичного успеха».** `install_skills` при
`FAIL` возвращает 1; в `--skills` он вызывается через `|| status=1`, иначе
`install_local_skills` не выполнится. Тест на «первую установку без
источника» проверяет оба файла и код возврата.
- **Legacy-fallback маскирует неправильную настройку.** Лечится `WARN` в
установке, `--check` и runtime-предупреждением; полное удаление — D-4.
- **Замена `~/.claude/roles` → `~/.dsh/roles` в тексте.** Заменяем только этот
префикс, а не `~/.claude` целиком: в тексте есть упоминания хуков/CC, которые
DSH-версии не касаются. Тест проверяет и наличие нового пути, и отсутствие
старого.
- **Проверка «чистый HOME» — юнит-уровень.** Реальный fresh-session smoke в DSH
делает владелец: сессия должна подняться без `~/.claude` и показать
superpowers-скиллы. В PR это отмечено как единственный ручной шаг приёмки.
- **Апстрим superpowers может переехать.** Явный источник проверяется по
`using-superpowers/SKILL.md`; если апстрим переименует каталог, `FAIL` скажет,
что искать.
## 6. Приёмка
- `openspec validate dsh-install-source --strict` — change валиден.
- `bash dsh/tests/run.sh` — зелёный, включая новые тесты источника, `AGENTS.md`
и legacy-предупреждений.
- `bash tests/run.sh` — регрессий в CC-контуре нет.
- Temp `HOME` без `~/.claude`: `--global --skills --roles` с
`--superpowers-from`; `--check`; `prepareBootstrap({home})` — маркер на месте.
- Живой fresh-session smoke без `~/.claude` — за владельцем (в PR отмечено).