claude-harness/docs/superpowers/plans/2026-10-24-dsh-superpowers-git.md

224 lines
13 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.

# 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: <dir>, git <short-sha>)`.
- `--check` MUST NOT клонировать/обновлять; показывает `ok: superpowers source (<dir>, git <sha>)`.
- Неудачный первый клон MUST NOT оставлять каталог: `rm -rf $DSH_HOME/superpowers`.
- Ручная копия `$DSH_HOME/superpowers` без `.git` — `pinned`, без обновления, с `WARN`.
- Тесты не выходят в сеть: `installerEnv` задаёт недоступный `DSH_SUPERPOWERS_REPO`, git-тесты — `file://`-фикстуру.
- Маркировка: `T<n>.<k>` (ревью задачи), `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 `<skills-dir>\tgit:<sha>`; `rc 1` без источника (печатает `WARN`/`FAIL` причину);
- `superpowers_source()` — прежний контракт `<dir>\t<explicit|pinned|legacy|git:<sha>>`;
- `install_skills` печатает `copied N superpowers skills into <dest> (source: <dir>, git <sha>)` для git и `(source: <dir>)` иначе.
- 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; <dshHome>/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;
// <dshHome>/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;
// <dshHome>/superpowers НЕ существует; FAIL упоминает no-superpowers-repo и --superpowers-from
test('an explicit source never touches git', ...)
// --skills --superpowers-from <fixture>; status 0; <dshHome>/superpowers НЕ существует
test('a manual non-git checkout is used but not updated', ...)
// write <dshHome>/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:<sha>` → в итоговой строке `(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:<sha>`) → ручная копия (`pinned`) → legacy; `--check` печатает `ok: superpowers source (<dir>, git <sha>)`, `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;
// <dshHome>/superpowers НЕ существует; stderr /WARN: no superpowers source/
test('--check warns about a manual non-git checkout', ...)
// write <dshHome>/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:<sha>` → `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 <sha>`, повторный `--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`.