# 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`.