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

13 KiB
Raw Blame History

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; добавить:

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.

Тесты:

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
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: Падающие тесты

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