From c591083b37dee0f94d19cd9a8374e28ae899dd4e Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 24 Oct 2026 21:34:15 +0400 Subject: [PATCH] =?UTF-8?q?docs(openspec):=20change=20dsh-superpowers-git?= =?UTF-8?q?=20=D0=B8=20=D0=BF=D0=BB=D0=B0=D0=BD=20(#67)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plans/2026-10-24-dsh-superpowers-git.md | 223 ++++++++++++++++++ .../dsh-superpowers-git/.openspec.yaml | 2 + .../changes/dsh-superpowers-git/design.md | 111 +++++++++ .../changes/dsh-superpowers-git/proposal.md | 52 ++++ .../specs/dsh-install/spec.md | 156 ++++++++++++ 5 files changed, 544 insertions(+) create mode 100644 docs/superpowers/plans/2026-10-24-dsh-superpowers-git.md create mode 100644 openspec/changes/dsh-superpowers-git/.openspec.yaml create mode 100644 openspec/changes/dsh-superpowers-git/design.md create mode 100644 openspec/changes/dsh-superpowers-git/proposal.md create mode 100644 openspec/changes/dsh-superpowers-git/specs/dsh-install/spec.md diff --git a/docs/superpowers/plans/2026-10-24-dsh-superpowers-git.md b/docs/superpowers/plans/2026-10-24-dsh-superpowers-git.md new file mode 100644 index 0000000..d857a5c --- /dev/null +++ b/docs/superpowers/plans/2026-10-24-dsh-superpowers-git.md @@ -0,0 +1,223 @@ +# Install pulls superpowers from git — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** `dsh/install.sh --skills` сам клонирует и обновляет superpowers из git, оставляя явные источники и офлайн-кэш рабочими. + +**Architecture:** В `install.sh` появляется git-шаг: клон `$DSH_HOME/superpowers` из `$DSH_SUPERPOWERS_REPO` (по умолчанию GitHub) или `fetch --depth 1` + `reset --hard FETCH_HEAD` для существующего клона. Резолвер для `--check` отдельный и офлайновый. Тесты используют локальный `file://`-репозиторий. + +**Tech Stack:** bash (`set -euo pipefail`), git CLI, Node.js ESM (node:test), OpenSpec. + +**Spec:** `openspec/changes/dsh-superpowers-git/specs/dsh-install/spec.md` (дизайн — `design.md`). + +## Global Constraints + +- Порядок источников: `--superpowers-from` → `$DSH_SUPERPOWERS_SOURCE` → git (`$DSH_HOME/superpowers`) → legacy-кэш CC → `~/.claude/skills`. +- Явный валидный источник MUST NOT запускать git и не создавать `$DSH_HOME/superpowers`. +- Репозиторий: `$DSH_SUPERPOWERS_REPO`, default `https://github.com/obra/superpowers`; глубина 1; обновление `fetch --depth 1 origin` + `reset --hard FETCH_HEAD`. +- Фактический вывод `--skills` для git: `(source: , git )`. +- `--check` MUST NOT клонировать/обновлять; показывает `ok: superpowers source (, git )`. +- Неудачный первый клон MUST NOT оставлять каталог: `rm -rf $DSH_HOME/superpowers`. +- Ручная копия `$DSH_HOME/superpowers` без `.git` — `pinned`, без обновления, с `WARN`. +- Тесты не выходят в сеть: `installerEnv` задаёт недоступный `DSH_SUPERPOWERS_REPO`, git-тесты — `file://`-фикстуру. +- Маркировка: `T.` (ревью задачи), `C/I/M` (ревью ветки), `R-n`/`O-n` (рулинги/наблюдения). + +## Review Focus + +- Клон падает на середине: каталог не остаётся, следующий запуск клонирует заново. +- Офлайн при существующем клоне: `WARN`, копии из кэша, код 0 (не FAIL). +- Ручная копия без `.git`: используется, `WARN` об отсутствии обновления, копии на месте. +- Явный источник при пустом `DSH_HOME`: git не вызывается, `$DSH_HOME/superpowers` не появляется. +- `--check` на пустом HOME с недоступным репозиторием: `WARN`, код 0, клон не создан. +- Путь `$DSH_HOME` с пробелами: клон в кавычках, sha/копирование работают. + +--- + +### Task 1: git-источник в `--skills` + +**Files:** +- Modify: `dsh/install.sh` (`CLONE_HINT`, `git_superpowers_source`, `superpowers_source`, `install_skills`, `usage`) +- Modify: `dsh/tests/install.test.mjs` + +**Interfaces:** +- Produces: + - `git_superpowers_source() -> rc 0` и stdout `\tgit:`; `rc 1` без источника (печатает `WARN`/`FAIL` причину); + - `superpowers_source()` — прежний контракт `\t>`; + - `install_skills` печатает `copied N superpowers skills into (source: , git )` для git и `(source: )` иначе. +- Consumes: `normalize_superpowers_dir`, `$DSH_HOME`, `$DSH_SUPERPOWERS_REPO`. + +- [ ] **Step 1: Тестовый хелпер и падающие тесты** + +В `dsh/tests/install.test.mjs` импортировать `mkdirSync` из `node:fs`, `git` из `./helpers.mjs`, `pathToFileURL` из `node:url`; добавить: + +```js +function makeSuperpowersRepo(home, name = 'superpowers-src') { + const repo = join(home, name) + mkdirSync(repo, { recursive: true }) + git(repo, ['init', '-b', 'main']) + git(repo, ['config', 'user.email', 'test@example.invalid']) + git(repo, ['config', 'user.name', 'Test']) + makeSuperpowers(repo) + git(repo, ['add', '.']) + git(repo, ['commit', '-m', 'init']) + return repo +} +``` + +`installerEnv` дополнительно: `delete env.DSH_SUPERPOWERS_REPO` и +`env.DSH_SUPERPOWERS_REPO = join(home, 'no-superpowers-repo')` (недоступный путь — сеть в тестах не нужна). Git-тесты переопределяют его через `file://`-URL. + +Тесты: + +```js +test('--skills clones the git source on first run', ...) +// home; repo = makeSuperpowersRepo(home); env repo = pathToFileURL(repo).href; +// run --skills; status 0; /superpowers/.git существует; +// stdout /copied 2 superpowers skills/ и new RegExp(`git ${git(repo,['rev-parse','--short','HEAD'])}`); +// skills/using-superpowers на месте + +test('--skills updates an existing clone to the new commit', ...) +// после первого прогона: write(join(repo,'skills','new-skill','SKILL.md'), ...); +// git add/commit; повторить --skills; status 0; +// /skills/new-skill/SKILL.md существует; stdout содержит новый short-sha + +test('an offline update keeps the cached clone', ...) +// после клона: env repo = join(home,'gone'); run --skills; status 0; +// stdout/stderr /WARN: cannot update superpowers/; /copied 2 superpowers skills/ + +test('a failed first clone leaves no checkout', ...) +// пустой home (repo недоступен); run --skills; status 1; +// /superpowers НЕ существует; FAIL упоминает no-superpowers-repo и --superpowers-from + +test('an explicit source never touches git', ...) +// --skills --superpowers-from ; status 0; /superpowers НЕ существует + +test('a manual non-git checkout is used but not updated', ...) +// write /superpowers/skills/using-superpowers/SKILL.md; repo недоступен; +// run --skills; status 0; stderr /not a git checkout/; /copied 1 superpowers skills/ +``` + +- [ ] **Step 2: Прогнать, увидеть падение** + +Run: `node --test dsh/tests/install.test.mjs` +Expected: новые тесты падают (нет клона/обновления/сообщений). + +- [ ] **Step 3: Реализовать в `dsh/install.sh`** + +- `SUPERPOWERS_REPO="${DSH_SUPERPOWERS_REPO:-https://github.com/obra/superpowers}"` рядом с `CLONE_HINT`; `CLONE_HINT` переписать на `git clone --depth 1 "$SUPERPOWERS_REPO" "$DSH_HOME/superpowers"`. +- `git_superpowers_source` по описанию из Interfaces: git-клон → fetch+reset → `normalize_superpowers_dir`; на неудачном клоне `rm -rf "$DSH_HOME/superpowers"` и `return 1`; на неудачном fetch — `WARN` и работа из кэша. +- `superpowers_source`: после явных источников вызвать `git_superpowers_source`; её успех вернуть как есть, `rc 1` — продолжить на legacy-кэш и `~/.claude/skills`. +- `install_skills`: разобрать kind `git:` → в итоговой строке `(source: $source, git $sha)`; `FAIL`-сообщение — репозиторий и `--superpowers-from` вместо ручного клона. +- `usage`: пункт 3 — git-клон `$SUPERPOWERS_REPO` → `$DSH_HOME/superpowers`, обновляется при каждом `--skills`. + +- [ ] **Step 4: Прогнать тесты** + +Run: `node --test dsh/tests/install.test.mjs` +Expected: PASS всех тестов файла. + +- [ ] **Step 5: Commit** + +```bash +git add dsh/install.sh dsh/tests/install.test.mjs +git commit -m "feat(dsh): install pulls superpowers from git (#67)" +``` + +--- + +### Task 2: офлайновый `--check` с git-версией + +**Files:** +- Modify: `dsh/install.sh` (`static_superpowers_source`, блок `check`) +- Modify: `dsh/tests/install.test.mjs` + +**Interfaces:** +- Produces: `static_superpowers_source()` — тот же формат, но без сети: явные источники → существующий git-клон (`git:`) → ручная копия (`pinned`) → legacy; `--check` печатает `ok: superpowers source (, git )`, `WARN` для `pinned`/legacy/отсутствия, `FAIL` для невалидного явного. + +- [ ] **Step 1: Падающие тесты** + +```js +test('--check reports the git sha and does not clone', ...) +// сначала --skills с file://-репозиторием (клон создан); +// затем env repo = недоступный путь; run --check; +// stdout /ok: superpowers source \(.*superpowers\/skills, git [0-9a-f]+\)/; status 0 + +test('--check never clones on an empty HOME', ...) +// empty home, repo недоступен; run --check; status 0; +// /superpowers НЕ существует; stderr /WARN: no superpowers source/ + +test('--check warns about a manual non-git checkout', ...) +// write /superpowers/skills/using-superpowers/SKILL.md; +// run --check; stdout/stderr /not a git checkout/ +``` + +Существующий тест `--check reports the pinned source` заменить этим кейсом (ручная копия больше не `ok`), сценарий спеки #49 обновлён в change. + +- [ ] **Step 2: Прогнать, увидеть падение** + +Run: `node --test dsh/tests/install.test.mjs` +Expected: FAIL на новых и переименованном тестах. + +- [ ] **Step 3: Реализовать** + +- Вынести `static_superpowers_source` (без вызова git-команд, меняющих состояние; `rev-parse --short HEAD` — чтение). +- `check()` переключить с `superpowers_source` на `static_superpowers_source`; разобрать kind: `git:` → `ok: superpowers source ($src, git $sha)`, `pinned` → `WARN: ... is not a git checkout; --skills will not update it`, `legacy` → `WARN`, нет — `WARN` с `$SUPERPOWERS_REPO` и `--superpowers-from`. +- `--check` не должен создавать `$DSH_HOME/superpowers` и не вызывать `git clone`/`fetch`. + +- [ ] **Step 4: Прогнать тесты** + +Run: `bash dsh/tests/run.sh` +Expected: PASS всего набора. + +- [ ] **Step 5: Commit** + +```bash +git add dsh/install.sh dsh/tests/install.test.mjs +git commit -m "feat(dsh): offline --check reports the git source (#67)" +``` + +--- + +### Task 3: документация, приёмка, PR + +**Files:** +- Modify: `dsh/README.md` (Quick start, Known limits, config) +- Modify: `dsh/PORT.md` (§5 install, §7 risks, §8 phases) +- Modify: `openspec/changes/dsh-superpowers-git/design.md` (раздел приёмки) + +- [ ] **Step 1: README/PORT** + +Quick start: убрать ручной `git clone`, оставить `bash dsh/install.sh --global --skills --roles`; описать `DSH_SUPERPOWERS_REPO`, офлайн-кэш и `--superpowers-from`. Known limits: свежий апстрим тянется при каждом `--skills`; ручная копия без `.git` не обновляется. PORT §5: git-источник и sha; §7: сеть в установке и откат на `--superpowers-from`; §8: #67. + +- [ ] **Step 2: Полный прогон и приёмка** + +```bash +bash dsh/tests/run.sh +bash tests/run.sh +openspec validate dsh-superpowers-git --strict +``` + +Живой прогон на машине владельца (после merge): `bash dsh/install.sh --skills` +→ клон GitHub, `--check` → `git `, повторный `--skills` → обновление. +В design.md записать `### Приёмка: прогон <дата>` с результатами. + +- [ ] **Step 3: Commit, push, PR** + +```bash +git add -A +git commit -m "docs(dsh): git source для superpowers (#67)" +git push -u origin feat/67-dsh-superpowers-git +tea pulls create --head feat/67-dsh-superpowers-git --base main \ + --title "DSH: install сам тянет свежий superpowers с git (#67)" \ + --description-file <подготовленный файл> +``` + +- [ ] **Step 4: Ревью ветки и итог** + +Свежий ревьюер (`deepseek-v4-pro`) по `review-package`; Critical/Important → один fix pass по TDD; Minor → ledger и PR; итоговая таблица маркеров. + +## Acceptance + +- Спека `dsh-install` (delta) покрыта тестами: клон, обновление, офлайн-кэш, неудачный клон, явный источник без сети, ручная копия, `--check` без сети и с sha. +- `bash dsh/tests/run.sh`, `bash tests/run.sh`, `openspec validate --strict` — зелёные. +- Тесты не обращаются к GitHub: единственный источник — `file://`-фикстура или недоступный путь. +- После merge: `--skills` на машине владельца тянет GitHub и убирает `WARN: legacy superpowers source`. diff --git a/openspec/changes/dsh-superpowers-git/.openspec.yaml b/openspec/changes/dsh-superpowers-git/.openspec.yaml new file mode 100644 index 0000000..1f5d280 --- /dev/null +++ b/openspec/changes/dsh-superpowers-git/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-24 diff --git a/openspec/changes/dsh-superpowers-git/design.md b/openspec/changes/dsh-superpowers-git/design.md new file mode 100644 index 0000000..51d445b --- /dev/null +++ b/openspec/changes/dsh-superpowers-git/design.md @@ -0,0 +1,111 @@ +# Design: install тянет superpowers с git + +Issue: #67. Спека: `openspec/changes/dsh-superpowers-git/specs/dsh-install/spec.md`. +Контекст: #49 (`dsh-install-source`) — источник был явным, сеть в установке +запрещена; это решение пересматривается по команде владельца 24.10.2026. + +## 1. Что меняется в контракте #49 + +| | #49 | #67 | +|---|---|---| +| Источник по умолчанию | ручной клон/флаг | git: клон в `$DSH_HOME/superpowers` | +| Обновление | нет | `fetch --depth 1` + `reset --hard FETCH_HEAD` при каждом `--skills` | +| Сеть в установке | запрещена | разрешена только для git-источника | +| Явный источник | главнее | главнее, сеть не трогает | +| `--check` | офлайн | офлайн + git sha | +| Офлайн-машина | явный источник | кэш клона (WARN) → legacy (WARN) → FAIL | + +## 2. Решения + +### D-7 (решение владельца). Git — источник по умолчанию + +`--skills` без флага/переменной клонирует репозиторий и обновляет его при +следующих запусках. Обоснование владельца: ручной клон не обновляется и +оставляет `WARN: legacy superpowers source` навсегда. Цена: установка требует +сеть (кроме явного источника и кэша), а свежий апстрим может принести +несовместимость — она лечится `--superpowers-from` на любой коммит. + +### D-8. Явный источник полностью отключает сеть + +`--superpowers-from` и `$DSH_SUPERPOWERS_SOURCE` проверяются первыми и, если +валидны, git не вызывается вообще. Это и офлайн-путь, и отладка, и пиннинг на +конкретную версию. Цена: две ветки кода, покрыты тестами, включая «явный +источник не создаёт `$DSH_HOME/superpowers`». + +### D-9. Офлайн не ломает установку, если есть чем ставить + +Порядок при недоступном git: существующий клон (в т.ч. с неудачным fetch) → +legacy-кэш CC → `~/.claude/skills` → FAIL. Первые два шага печатают `WARN` с +причиной (репозиторий), чтобы деградация была видна, но машина без сети +продолжала работать. Цена: опечатка в `DSH_SUPERPOWERS_REPO` может привести к +legacy-источнику — сообщение называет репозиторий и путь кэша. + +### D-10. Ручная копия без `.git` — пиннинг, а не ошибка + +Если `$DSH_HOME/superpowers` существует, валиден, но не является git-клоном, +он используется как есть с `WARN`, что обновляться не будет. Это совместимость +с #49: у кого уже лежит ручная копия, после обновления обвязки установка не +падает. Цена: такой каталог стареет молча — предупреждение на каждом `--skills`. + +### D-11. `--check` остаётся офлайновым + +`--check` — диагностика; клонировать или обновлять из него нельзя (в #42 уже +принято: `--check` не должен иметь побочных эффектов). Он показывает +`ok: superpowers source (, git )` для клона, `WARN` для ручной копии +и для legacy, и подсказку про `--skills`/`DSH_SUPERPOWERS_REPO`, когда клона нет. +Цена: `--check` не скажет, доступен ли репозиторий; это выясняется при установке. + +### D-12. Тесты идут через локальный `file://`-репозиторий + +`DSH_SUPERPOWERS_REPO` — не только фича для зеркал, но и тестовый шов: набор не +ходит в GitHub. Тестовый хелпер создаёт git-репозиторий с `skills/*/SKILL.md`, +запускает `--skills` с `DSH_SUPERPOWERS_REPO=file://<путь>`, затем добавляет +коммит и проверяет обновление копий. Офлайн-кейс — репозиторий, которого нет. + +## 3. Решения, принятые без обсуждения + +- **2026-10-24. `--depth 1` и `reset --hard FETCH_HEAD`, без тегов и подписей.** + «Свежая версия» = последний коммит default-ветки; глубина 1 экономит сеть, + `reset --hard` гарантирует чистое управляемое дерево. Цена: локальные правки в + `~/.dsh/superpowers` теряются, подлинность апстрима не проверяется (TLS и + доверие к GitHub); откат — `--superpowers-from` на известный коммит. +- **2026-10-24. Неудачный первый клон удаляет каталог.** + `rm -rf $DSH_HOME/superpowers` после неудачи, чтобы следующий запуск снова + клонировал, а не принимал половину за кэш. Цена: если пользователь положил + туда что-то своё и не было `.git` — этот путь не трогается (D-10), удаление + только для созданного нами каталога в текущем запуске. +- **2026-10-24. Отдельного флага `--offline` нет.** + Офлайн покрыт существующим клоном (D-9) и явным источником (D-8). Цена: нет + способа гарантированно запретить сеть, кроме явного источника; для установки + это приемлемо. +- **2026-10-24. `DSH_SUPERPOWERS_REPO` виден и в `--check`.** + В `WARN`/`FAIL` печатается фактический репозиторий, чтобы зеркало и опечатка + различались. Цена: строка вывода зависит от env — приемлемо. +- **2026-10-24. `tasks.md` OpenSpec не заполняется** (как в прошлых change): + задачи в плане superpowers. + +## 4. Риски + +- **Свежий апстрим ломает установку.** `--skills` тянет последний коммит; если + superpowers переименует `skills/using-superpowers`, `FAIL` скажет, что искать. + Обход — `--superpowers-from` с проверенной копией; sha в выводе помогает + зафиксировать версию. +- **Сеть в установке.** Машины без доступа к GitHub должны использовать + `DSH_SUPERPOWERS_REPO` (зеркало) или `--superpowers-from`; ошибка клона + называет репозиторий и оба пути. +- **Тесты и сеть.** Любой тест, забывший выставить `DSH_SUPERPOWERS_REPO`, уйдёт + в GitHub. Поэтому `installerEnv` в тестах задаёт недоступный repo по + умолчанию, а тесты git-источника — `file://`-фикстуру; проверяется, что + `--check` не создаёт клон. +- **Офлайн-деградация незаметна.** WARN есть на каждом шаге деградации; `--check` + показывает, какой источник будет использован при следующем `--skills`. +- **Совместимость с #49.** Ручная копия без `.git` и legacy-кэш остаются + рабочими; тесты #49 переиспользуются, меняются только ожидания сообщений. + +## 5. Приёмка + +- `openspec validate dsh-superpowers-git --strict` — change валиден. +- `bash dsh/tests/run.sh` — зелёный, включая клон/обновление/офлайн/`--check`. +- `bash tests/run.sh` — регрессий в CC-контуре нет. +- Живой прогон на машине владельца: `--skills` клонирует GitHub-репозиторий, + `--check` показывает `git `, повторный `--skills` обновляет. diff --git a/openspec/changes/dsh-superpowers-git/proposal.md b/openspec/changes/dsh-superpowers-git/proposal.md new file mode 100644 index 0000000..128ae5f --- /dev/null +++ b/openspec/changes/dsh-superpowers-git/proposal.md @@ -0,0 +1,52 @@ +# Proposal: install сам тянет свежий superpowers с git + +Issue: #67. Связано: #49 (закрыт), #48 (роадмап), change `dsh-install-source`. +Ответ владельца 24.10.2026: «а давай инстал будет тащить сам свежую версию с +гита». + +## Why + +После #49 install-time источник задаётся явно: `--superpowers-from`, +`$DSH_SUPERPOWERS_SOURCE` или ручной клон в `~/.dsh/superpowers`. Это снимает +зависимость от `~/.claude`, но перекладывает получение источника на человека и +не обновляет его: клон, сделанный полгода назад, остаётся «свежим» навсегда, а +`WARN: legacy superpowers source` на машине владельца так и висит, потому что +пиннинга нет. + +Владелец хочет, чтобы `dsh/install.sh --skills` сам получал свежий superpowers: +первый запуск — клон, следующие — обновление. Явные флаги остаются для офлайна, +зеркал и отладки и сеть не трогают. + +## What Changes + +- **git — источник по умолчанию.** `--skills` клонирует + `https://github.com/obra/superpowers` в `$DSH_HOME/superpowers` (depth 1), если + клона нет, и обновляет `fetch --depth 1` + `reset --hard FETCH_HEAD`, если + есть. URL переопределяется `$DSH_SUPERPOWERS_REPO` (зеркала, локальные + репозитории в тестах). +- **Порядок источников:** явный источник (`--superpowers-from`, + `$DSH_SUPERPOWERS_SOURCE`) → git-клон (обновить/создать) → legacy-кэш Claude + Code → legacy `~/.claude/skills`. Ручная копия `~/.dsh/superpowers` без `.git` + используется как есть с `WARN` (совместимость с #49). +- **Сеть и ошибки.** Нет сети при существующем клоне — `WARN`, работа из кэша. + Нет сети, нет клона, нет других источников — `FAIL` с кодом 1 и подсказкой + `--superpowers-from`. Оборванный клон не оставляется на диске. +- **`--check` остаётся офлайновым:** не клонирует и не обновляет, показывает + `ok: superpowers source (, git )` для git-клона, `WARN` для ручной + копии без `.git`, legacy и «нет источника» — как в #49. +- **Тесты без сети:** локальный git-репозиторий через `DSH_SUPERPOWERS_REPO` + (`file://`), проверка первого клона, обновления до нового коммита, офлайна и + `--check`. +- **Документация.** README/PORT: quick start без ручного клона, офлайн-путь, + `DSH_SUPERPOWERS_REPO`, `--check` с sha. + +## Non-goals + +- Вендоринг superpowers в репозиторий — отклонён в #49 (D-1) и остаётся + отклонённым. +- Пиннинг тега/коммита и проверка подписи: «свежая версия» = последний коммит + default-ветки; воспроизводимость установки обеспечивается глубиной 1 и + записанным sha в выводе, а не неизменностью источника. +- Субмодули и LFS в клоне superpowers. +- Обновление ролей/локальных скиллов из сети: они по-прежнему из репозитория + обвязки. diff --git a/openspec/changes/dsh-superpowers-git/specs/dsh-install/spec.md b/openspec/changes/dsh-superpowers-git/specs/dsh-install/spec.md new file mode 100644 index 0000000..9ab1fb8 --- /dev/null +++ b/openspec/changes/dsh-superpowers-git/specs/dsh-install/spec.md @@ -0,0 +1,156 @@ +# Spec Delta: dsh-superpowers-git + +## Purpose + +`dsh/install.sh --skills` получает superpowers из git сам: первый запуск — +клон, следующие — обновление до последнего коммита default-ветки. Явные +источники остаются приоритетнее и не ходят в сеть; `~/.claude` — по-прежнему +legacy-fallback с предупреждением. + +## ADDED Requirements + +### Requirement: Свежий superpowers из git +`dsh/install.sh --skills` MUST получать superpowers-скиллы из git, когда явный +источник не задан: клонировать при отсутствии клона и обновлять при каждом +следующем запуске. + +- Репозиторий по умолчанию — `https://github.com/obra/superpowers`; + `$DSH_SUPERPOWERS_REPO` MUST переопределять его (зеркала, локальные + репозитории в тестах). +- Клон — `git clone --depth 1 $DSH_HOME/superpowers`. +- Обновление — `git -C $DSH_HOME/superpowers fetch --depth 1 origin` и + `git reset --hard FETCH_HEAD`; рабочее дерево управляемое, локальные правки + там не сохраняются. +- После клона/обновления источником MUST считаться `$DSH_HOME/superpowers/skills` + (или сам каталог), только если в нём есть `using-superpowers/SKILL.md`. +- Вывод `--skills` MUST называть источник и git-версию: + `(source: , git )`. +- `--check` MUST NOT выполнять сетевые операции: он показывает состояние + существующего клона (`ok: superpowers source (, git )`) и не + создаёт `$DSH_HOME/superpowers`. + +#### Scenario: Первый запуск клонирует источник +- **WHEN** `$DSH_HOME/superpowers` отсутствует, `DSH_SUPERPOWERS_REPO` указывает + на репозиторий superpowers +- **THEN** `--skills` клонирует его в `$DSH_HOME/superpowers`, копирует скиллы в + `$DSH_HOME/skills` и печатает `git ` + +#### Scenario: Повторный запуск обновляет клон +- **WHEN** клон уже есть, а в источнике появился новый коммит со скиллом +- **THEN** повторный `--skills` обновляет клон до нового коммита, и новый скилл + оказывается в `$DSH_HOME/skills` + +#### Scenario: Нет сети, клон есть +- **WHEN** обновление клона не удалось (недоступный репозиторий), но + `$DSH_HOME/superpowers` содержит `using-superpowers/SKILL.md` +- **THEN** `--skills` печатает `WARN`, использует кэш, копирует скиллы и + завершается кодом 0 + +#### Scenario: Нет сети и нет клона +- **WHEN** клонирование не удалось, клона и других источников нет, + `$DSH_HOME/skills` пуст +- **THEN** `--skills` печатает `FAIL` с репозиторием и подсказкой + `--superpowers-from`, код 1; недоклонированный каталог не остаётся на диске + +#### Scenario: Ручная копия без git +- **WHEN** `$DSH_HOME/superpowers` существует, не является git-клоном и содержит + `using-superpowers/SKILL.md` +- **THEN** каталог используется как есть, `--skills` печатает `WARN` о том, что + он не будет обновляться + +#### Scenario: --check не ходит в сеть +- **WHEN** клон существует, а `DSH_SUPERPOWERS_REPO` указывает на недоступный + репозиторий +- **THEN** `--check` печатает `ok: superpowers source (, git )`, + завершается без сетевых ошибок и ничего не клонирует + +## MODIFIED Requirements + +### Requirement: Источник superpowers задаётся при установке +`dsh/install.sh --skills` SHALL разрешать источник superpowers-скиллов в +порядке приоритета. + +- Порядок: `--superpowers-from ` → `$DSH_SUPERPOWERS_SOURCE` → git-клон + `$DSH_HOME/superpowers` (обновить существующий или склонировать из + `$DSH_SUPERPOWERS_REPO`/`https://github.com/obra/superpowers`) → + legacy-кэш Claude Code → legacy `~/.claude/skills`. +- Для явного источника (`--superpowers-from`, `$DSH_SUPERPOWERS_SOURCE`) + допускаются обе формы: корень superpowers (есть `skills/using-superpowers/SKILL.md`) + или сам каталог `skills`; сеть при явном источнике MUST NOT использоваться. +- Явно заданный, но невалидный источник MUST приводить к `FAIL` и коду + возврата 1: тихий откат на git/legacy запрещён. +- Копирование 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, ничего не копируя из git/legacy-источников + +#### Scenario: Явный источник не трогает git +- **WHEN** `--superpowers-from` задан, а `$DSH_HOME/superpowers` отсутствует +- **THEN** `--skills` не создаёт `$DSH_HOME/superpowers` + +### Requirement: Отсутствие источника не бывает тихим +Если источник superpowers не найден, `--skills` MUST различать первичную и +повторную установку и MUST NOT завершаться успехом молча. + +- Если `$DSH_HOME/skills/using-superpowers/SKILL.md` уже существует — + `WARN` «источник не найден, копии сохранены» и код 0 (идемпотентный повтор). +- Если копий нет — `FAIL` с репозиторием (`$DSH_SUPERPOWERS_REPO`) и подсказкой + `--superpowers-from `; код возврата 1. +- Локальные скиллы обвязки (`dsh/skills/*`: `role`, `sdd`) MUST ставиться в + обоих случаях: они не зависят от superpowers. + +#### Scenario: Первая установка без источника +- **WHEN** `HOME` пуст, `$DSH_HOME/skills` не существует, git-источник недоступен + и других источников нет +- **THEN** `--skills` печатает `FAIL`, код 1, но `$DSH_HOME/skills/role/SKILL.md` + и `$DSH_HOME/skills/sdd/SKILL.md` на месте + +#### Scenario: Повторная установка без источника +- **WHEN** `$DSH_HOME/skills/using-superpowers/SKILL.md` уже есть, источника нет +- **THEN** `--skills` печатает `WARN`, код 0, установленные скиллы не удалены + +### Requirement: ~/.claude — только legacy-fallback с предупреждением +superpowers-скиллы и роли MUST иметь рабочий путь `~/.dsh`; пути `~/.claude` +SHALL оставаться fallback'ом для машин, где `--skills --roles` ещё не +перезапускали, и MUST сопровождаться предупреждением. + +- Установка: legacy-кэш Claude Code и `~/.claude/skills` допустимы как + источник, но `--skills` печатает `WARN: legacy superpowers source` с + подсказкой на git-источник или `--superpowers-from`. +- `--check`: legacy-источник — `WARN`, git-клон/явный источник — `ok`, + отсутствие — `WARN` с подсказкой, невалидный явный источник — `FAIL` и код 1. +- Runtime: bootstrap и резолвер ролей сохраняют порядок `~/.dsh` → `~/.claude`, + но при выборе legacy-пути один раз за процесс пишут в лог предупреждение. +- Удаление fallback'ов — отдельное изменение; в этом change они не удаляются. + +#### Scenario: Legacy-кэш как источник установки +- **WHEN** в `HOME` есть только кэш Claude Code с superpowers, `$DSH_HOME` пуст, + git-источник недоступен +- **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`, предупреждения нет