claude-harness/tests/test-retro-leftovers.sh
andrey.epifantsev 4bbf6ebb3a feat(retro): retro_leftovers.py — хвосты чеклиста закрытия по состоянию репозиториев (#29)
SDD workspace в worktree активной задачи хвостом не считается (O-19, живой прогон).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 01:25:14 +04:00

61 lines
5.5 KiB
Bash
Raw Permalink 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.

# retro_leftovers.py: хвосты чеклиста закрытия по состоянию репозиториев (спека harness-retro, #29).
L="$ROOT/docs/retro/scripts/retro_leftovers.py"
G="$TMP/lo"; mkdir -p "$G"
git init -q --bare -b main "$G/o.git"
git clone -q "$G/o.git" "$G/c" 2>/dev/null; git -C "$G/c" symbolic-ref HEAD refs/heads/main
printf '.superpowers/\n.claude/worktrees/\n' > "$G/c/.gitignore"
git -C "$G/c" add .gitignore && git -C "$G/c" commit -q -m init && git -C "$G/c" push -q origin main 2>/dev/null
git -C "$G/c" switch -q -c feat/1 && git -C "$G/c" commit -q --allow-empty -m f1 && git -C "$G/c" push -q -u origin feat/1 2>/dev/null
git -C "$G/c" switch -q main && git -C "$G/c" merge -q --no-ff feat/1 -m "merge feat/1"
mkdir -p "$G/c/openspec/changes/foo" "$G/c/openspec/changes/archive/2026-01-01-bar"
echo x > "$G/c/openspec/changes/foo/proposal.md"; echo y > "$G/c/openspec/changes/archive/2026-01-01-bar/p.md"
git -C "$G/c" add openspec && git -C "$G/c" commit -q -m spec && git -C "$G/c" push -q origin main 2>/dev/null
git -C "$G/c" worktree add -q "$G/c/.claude/worktrees/w1" feat/1
mkdir -p "$G/c/.superpowers/sdd/plan-x" && touch "$G/c/.superpowers/sdd/plan-x/progress.md"
# активная задача: worktree на неслитой ветке со своим SDD workspace — не хвост
git -C "$G/c" worktree add -q "$G/c/.claude/worktrees/w2" -b feat/2 && git -C "$G/c/.claude/worktrees/w2" commit -q --allow-empty -m wip
mkdir -p "$G/c/.claude/worktrees/w2/.superpowers/sdd/plan-y" && touch "$G/c/.claude/worktrees/w2/.superpowers/sdd/plan-y/progress.md"
git clone -q "$G/o.git" "$G/c2" 2>/dev/null && git -C "$G/c2" commit -q --allow-empty -m ahead && git -C "$G/c2" push -q origin main 2>/dev/null
git -C "$G/c" fetch -q origin
echo x > "$G/c/untracked.txt"
J="$TMP/lo.json"
python3 "$L" "$G/c" > "$J"; rc=$?
assert_eq "хвосты: exit 0" "$rc" "0"
k(){ python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); print(eval(sys.argv[2]))' "$J" "$1"; }
assert_eq "виды хвостов" "$(k 'sorted(d["totals"])')" "['local-branch', 'main-behind', 'main-dirty', 'openspec-change', 'remote-branch', 'sdd-workspace', 'worktree']"
assert_eq "ветка на сервере и подсказка" "$(k '[(i["item"], i["hint"]) for i in d["items"] if i["kind"] == "remote-branch"]')" "[('origin/feat/1', 'git push origin --delete feat/1')]"
assert_eq "локальная ветка" "$(k '[i["item"] for i in d["items"] if i["kind"] == "local-branch"]')" "['feat/1']"
assert_eq "worktree на слитой ветке" "$(k '[i["item"].endswith("/.claude/worktrees/w1") for i in d["items"] if i["kind"] == "worktree"]')" "[True]"
assert_eq "SDD workspace" "$(k '[i["item"].endswith("/sdd/plan-x") for i in d["items"] if i["kind"] == "sdd-workspace"]')" "[True]"
assert_eq "неархивный change — без archive" "$(k '[i["item"] for i in d["items"] if i["kind"] == "openspec-change"]')" "['openspec/changes/foo']"
assert_grep "отставание от origin/main" 'позади origin/main на 1' "$J"
assert_grep "грязное дерево с путём" 'untracked.txt' "$J"
# только чтение: без --fetch ссылки и дерево не меняются
git -C "$G/c2" commit -q --allow-empty -m ahead2 && git -C "$G/c2" push -q origin main 2>/dev/null
before="$(git -C "$G/c" for-each-ref)$(git -C "$G/c" status --porcelain)"
python3 "$L" "$G/c" > /dev/null
assert_eq "без --fetch ничего не меняется" "$(git -C "$G/c" for-each-ref)$(git -C "$G/c" status --porcelain)" "$before"
python3 "$L" "$G/c" --fetch > "$J"
assert_grep "--fetch видит новые коммиты" 'позади origin/main на 2' "$J"
md=$(python3 "$L" "$G/c" --markdown)
assert_grep "таблица для черновика" '^\| remote-branch \| c \| `origin/feat/1` \| `git push origin --delete feat/1` \|$' <(printf '%s' "$md")
# чистый репозиторий: свежая ветка на вершине origin/main — не хвост
git init -q --bare -b main "$G/k.git"; git clone -q "$G/k.git" "$G/k" 2>/dev/null; git -C "$G/k" symbolic-ref HEAD refs/heads/main
git -C "$G/k" commit -q --allow-empty -m init && git -C "$G/k" push -q origin main 2>/dev/null && git -C "$G/k" branch feat/new
python3 "$L" "$G/k" > "$J"
assert_eq "чистый репозиторий — хвостов нет" "$(k 'd["items"]')" "[]"
assert_eq "чистый репозиторий — markdown" "$(python3 "$L" "$G/k" --markdown)" "Хвостов закрытия нет."
# без origin
git init -q -b main "$G/n" && git -C "$G/n" commit -q --allow-empty -m init
python3 "$L" "$G/n" > "$J"; rc=$?
assert_eq "без origin: exit 0 и no-origin-main" "$rc $(k '[i["kind"] for i in d["items"]]')" "0 ['no-origin-main']"
# workspace: репозитории из таблицы CLAUDE.md
W="$TMP/ws"; mkdir -p "$W"; git clone -q "$G/o.git" "$W/r" 2>/dev/null
printf '# w\n\n| Каталог | Репозиторий | Роль |\n|---|---|---|\n| r/ | o/r | dev |\n| - | x | qa |\n' > "$W/CLAUDE.md"
python3 "$L" "$W" > "$J"
assert_eq "workspace: репозитории из таблицы" "$(k '[r.endswith("/ws/r") for r in d["repos"]]')" "[True]"
assert_eq "workspace: ветка на сервере видна" "$(k '[i["item"] for i in d["items"] if i["kind"] == "remote-branch"]')" "['origin/feat/1']"
# не проект и не workspace
mkdir -p "$TMP/empty"; python3 "$L" "$TMP/empty" > /dev/null 2>&1; rc=$?
assert_eq "не репозиторий и не workspace: exit 1" "$rc" "1"