feat: allow-правило git push origin --delete — строка в adopt/new-project, проверка в doctor (#22, B3)

Решение владельца 2: удаление серверных веток (чеклист закрытия, п. 4)
разрешается allow-правилом в настройках проекта; строку добавляет
владелец, обвязка её даёт.

- doctor.sh, раздел «Проект» (git и workspace): ищет правило, покрывающее
  `git push origin --delete <любая ветка>`, в .claude/settings.json и
  settings.local.json проекта и в глобальном settings.json; ok с файлом,
  WARN с готовой строкой, битый JSON — info.
- adopt-project.sh (обычный режим) и new-project.sh (оба режима) печатают
  строку в «Дальше»; файл настроек не пишут.
- SKILL.md: пункт чеклиста для нового WARN.
- HARNESS.md: шаг 10 — 8 пунктов в roles/dev.md, рецепт п. 4.
- roles/dev.md, п. 4: кто печатает строку; worktree remove раньше
  branch -D (ветку, выведенную в worktree, git не удаляет).
- CHANGELOG 0.4.2, CLAUDE.md «Текущая работа».

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
andrey.epifantsev 2026-10-23 23:39:47 +04:00
parent dce95e7e86
commit 1d1c70f66b
11 changed files with 86 additions and 7 deletions

View File

@ -1,5 +1,9 @@
# Changelog # Changelog
## 0.4.2 — 2026-10-23
- Удаление ветки фичи на сервере (чеклист закрытия, п. 4; B3, RT-3, решение владельца 2): `doctor.sh` в разделе «Проект» ищет allow-правило для `git push origin --delete` в `.claude/settings.json`, `settings.local.json` проекта и в глобальном `settings.json` — `ok` с файлом или `WARN` с готовой строкой `"Bash(git push origin --delete:*)"`. Строку печатают `adopt-project.sh` и `new-project.sh` во всех режимах (раньше — только `adopt --workspace`); файл настроек они не пишут, правило добавляет владелец (#22).
- `HARNESS.md`: шаг 10 — чеклист из 8 пунктов в `roles/dev.md` и рецепт п. 4. `roles/dev.md`, п. 4: `git worktree remove` раньше `git branch -D` (ветку, выведенную в worktree, git не удаляет).
## 0.4.1 — 2026-10-23 ## 0.4.1 — 2026-10-23
- harness-doctor и README: codegraph ставится `npm i -g @colbymchenry/codegraph` (формулы в brew нет) и подключается к Claude Code через `claude mcp add-json -s user`, не через `codegraph install` — тот дописывает секцию в `~/.claude/CLAUDE.md`, которую сотрёт `install.sh` (#19, O-8). - harness-doctor и README: codegraph ставится `npm i -g @colbymchenry/codegraph` (формулы в brew нет) и подключается к Claude Code через `claude mcp add-json -s user`, не через `codegraph install` — тот дописывает секцию в `~/.claude/CLAUDE.md`, которую сотрёт `install.sh` (#19, O-8).

View File

@ -41,7 +41,8 @@
Бэклог обвязки — `docs/retro/2026-10-11.md`, раздел 4 (B1–B12), решения владельца — раздел 5. Находки ретро маркируются `RT-n`. Бэклог обвязки — `docs/retro/2026-10-11.md`, раздел 4 (B1–B12), решения владельца — раздел 5. Находки ретро маркируются `RT-n`.
- Сделано: B5+B6 часть 1 — слой ролей (PR #4, 0.3.0). Часть 2 (awp-ca) — issue #2, отложенные находки — issue #5. - Сделано: B5+B6 часть 1 — слой ролей (PR #4, 0.3.0). Часть 2 (awp-ca) — issue #2, отложенные находки — issue #5.
- Сделано: B1+B2 и отмена исключения openspec-only для guard-main — хуки по цели записи и корпус хуков (issue #6, PR #12, 0.4.0), спека — `openspec/specs/hook-guards/`. Отложенные находки — issue #13. - Сделано: B1+B2 и отмена исключения openspec-only для guard-main — хуки по цели записи и корпус хуков (issue #6, PR #12, 0.4.0), спека — `openspec/specs/hook-guards/`. Отложенные находки — issue #13.
- Дальше: B3/B4/B10, B12. - Сделано: B3 — allow-правило `git push origin --delete` печатают adopt/new-project, проверяет doctor (issue #22, 0.4.2). B4 закрыт в #6 (решение владельца 1). B10 — workspace-хук awp-ca, в части 2 issue #2.
- Дальше: B12.
## OpenSpec и superpowers ## OpenSpec и superpowers
- Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать. - Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать.

View File

@ -73,6 +73,8 @@ fi
cat <<MSG cat <<MSG
Проект $NAME подключён к обвязке. Дальше: Проект $NAME подключён к обвязке. Дальше:
cd $(pwd) && claude 1. Разреши агенту удалять ветки на сервере (чеклист закрытия, п. 4) — строка в .claude/settings.json → permissions.allow:
«Опиши проект в CLAUDE.md по коду: разделы «Что это», «Стек», «Архитектура», «Команды». Bounded-задача.» "Bash(git push origin --delete:*)"
2. cd $(pwd) && claude
«Опиши проект в CLAUDE.md по коду: разделы «Что это», «Стек», «Архитектура», «Команды». Bounded-задача.»
MSG MSG

View File

@ -25,10 +25,12 @@
| 7 | Финальное ревью всей ветки (opus), одна волна фиксов | агент | — | | 7 | Финальное ревью всей ветки (opus), одна волна фиксов | агент | — |
| 8 | `finishing-a-development-branch`: PR в Gitea с «Rulings I made» | агент | PR | | 8 | `finishing-a-development-branch`: PR в Gitea с «Rulings I made» | агент | PR |
| 9 | Ревью PR в Gitea; комментарии → `receiving-code-review` → правки в ветке; **вы делаете merge** | вы ↔ агент | merge | | 9 | Ревью PR в Gitea; комментарии → `receiving-code-review` → правки в ветке; **вы делаете merge** | вы ↔ агент | merge |
| 10 | «Закрывай»: чеклист из 7 пунктов (см. CLAUDE.md) | агент | archive, closed issue, follow-up issue, чистый main | | 10 | «Закрывай»: чеклист из 8 пунктов (`~/.claude/roles/dev.md`, «Завершение») | агент | archive отдельным PR, closed issue, удалённая ветка, follow-up issue, чистый main |
Шаги 6–8 идут без остановок. Агент останавливается только на необратимой операции, вопросе безопасности, побочном эффекте вне worktree или плане, который невозможно исполнить. Шаги 6–8 идут без остановок. Агент останавливается только на необратимой операции, вопросе безопасности, побочном эффекте вне worktree или плане, который невозможно исполнить.
**Удаление ветки фичи (п. 4 закрытия).** `git push origin --delete <ветка>` из worktree фичи, затем из основного checkout `git worktree remove <путь>` и `git branch -D <ветка>`. guard-main с 0.4.0 эту команду не блокирует, но её может отклонить классификатор auto mode. Поэтому в проекте нужно allow-правило: строка `"Bash(git push origin --delete:*)"` в `.claude/settings.json` → `permissions.allow`. Строку добавляете вы (решение владельца 2); `adopt-project.sh` и `new-project.sh` её печатают, `doctor.sh` показывает, найдена ли она.
**Bounded-задача** (флаг, фикс) идёт короче: дизайн в чате, файл `.bounded` в корне worktree (пропускает хук require-plan), TDD, коммит, PR. Без OpenSpec change и без плана. **Bounded-задача** (флаг, фикс) идёт короче: дизайн в чате, файл `.bounded` в корне worktree (пропускает хук require-plan), TDD, коммит, PR. Без OpenSpec change и без плана.
## Subagent-driven development ## Subagent-driven development

View File

@ -32,7 +32,9 @@ if [ "${1:-}" = "--workspace" ]; then
Workspace $NAME готов. Дальше: Workspace $NAME готов. Дальше:
1. Заполни {{...}} в CLAUDE.md и в .claude/roles/dev.md; в репозиториях направлений — .claude/roles/<r>.md. 1. Заполни {{...}} в CLAUDE.md и в .claude/roles/dev.md; в репозиториях направлений — .claude/roles/<r>.md.
2. Заведи репозитории направлений в Gitea и добавь remote (tea repos create …; git remote add origin …). 2. Заведи репозитории направлений в Gitea и добавь remote (tea repos create …; git remote add origin …).
3. cd $(pwd) && claude -n "dev …" 3. Разреши агенту удалять ветки на сервере (чеклист закрытия, п. 4) — строка в .claude/settings.json → permissions.allow:
"Bash(git push origin --delete:*)"
4. cd $(pwd) && claude -n "dev …"
MSG MSG
exit 0 exit 0
fi fi
@ -68,6 +70,8 @@ cat <<MSG
Проект $NAME готов. Дальше: Проект $NAME готов. Дальше:
1. Заполните {{...}} в CLAUDE.md и ARCHITECTURE.md. 1. Заполните {{...}} в CLAUDE.md и ARCHITECTURE.md.
2. Положите первые фикстуры в tests/fixtures/. 2. Положите первые фикстуры в tests/fixtures/.
3. cd $DIR && claude 3. Разрешите агенту удалять ветки на сервере (чеклист закрытия, п. 4) — строка в .claude/settings.json → permissions.allow:
"Bash(git push origin --delete:*)"
4. cd $DIR && claude
«Хочу сделать ... Описание и ограничения в CLAUDE.md. Давай начнём.» «Хочу сделать ... Описание и ограничения в CLAUDE.md. Давай начнём.»
MSG MSG

View File

@ -29,7 +29,7 @@
1. `openspec archive <id>` — всегда отдельным PR (ветка `chore/archive-<id>` из main, `tea pulls create`, merge по команде человека). Прямых коммитов в main нет, исключений нет (решение владельца 11.10.2026). 1. `openspec archive <id>` — всегда отдельным PR (ветка `chore/archive-<id>` из main, `tea pulls create`, merge по команде человека). Прямых коммитов в main нет, исключений нет (решение владельца 11.10.2026).
2. Issue закрывается (`tea issues close <id>`, комментарий со ссылкой на PR) только если все её пункты выполнены. Если PR закрывает часть пунктов — issue остаётся открытой, в неё пишется комментарий: какие маркеры закрыты, каким PR и коммитом. 2. Issue закрывается (`tea issues close <id>`, комментарий со ссылкой на PR) только если все её пункты выполнены. Если PR закрывает часть пунктов — issue остаётся открытой, в неё пишется комментарий: какие маркеры закрыты, каким PR и коммитом.
3. Удалить SDD workspace (`.superpowers/sdd/<plan>/`). 3. Удалить SDD workspace (`.superpowers/sdd/<plan>/`).
4. Удалить ветку фичи на сервере и локально: `git push origin --delete <ветка>` выполняется из worktree на ветке фичи (не из main-checkout), разрешён allow-правилом проекта (`Bash(git push origin --delete:*)` в `.claude/settings.json`; строку печатает `adopt-project.sh --workspace`, для обычного репозитория добавить вручную); затем `git branch -D`, `git worktree remove`. Остальные worktree не трогать. Обходы через API Gitea запрещены. 4. Удалить ветку фичи на сервере и локально: `git push origin --delete <ветка>` выполняется из worktree на ветке фичи (не из main-checkout), разрешён allow-правилом проекта (`Bash(git push origin --delete:*)` в `.claude/settings.json`; строку добавляет владелец, её печатают `adopt-project.sh` и `new-project.sh`, есть ли она — показывает `doctor.sh`); затем из основного checkout `git worktree remove`, потом `git branch -D`. Остальные worktree не трогать. Обходы через API Gitea запрещены.
5. Обновить основной checkout: дерево чистое — `git pull --ff-only`; есть незакоммиченные изменения — не трогать, перечислить их в итоговом сообщении. 5. Обновить основной checkout: дерево чистое — `git pull --ff-only`; есть незакоммиченные изменения — не трогать, перечислить их в итоговом сообщении.
6. Все находки со статусом `deferred` собрать в одну issue `Follow-ups from #<issue-id>`: маркер, одна строка, откуда взялось. Нет отложенного — issue не создавать. 6. Все находки со статусом `deferred` собрать в одну issue `Follow-ups from #<issue-id>`: маркер, одна строка, откуда взялось. Нет отложенного — issue не создавать.
7. Файл auto-memory задачи, если был, свернуть в строку `registry-closed.md` (issue, PR, follow-up, дата); уроки из него перенести в `notes/` направления. 7. Файл auto-memory задачи, если был, свернуть в строку `registry-closed.md` (issue, PR, follow-up, дата); уроки из него перенести в `notes/` направления.

View File

@ -42,6 +42,7 @@ description: Диагностика обвязки Claude Code (claude-harness)
- «code-paths: умолчание» при коде вне `src/` → создать `.claude/code-paths`. - «code-paths: умолчание» при коде вне `src/` → создать `.claude/code-paths`.
- «SDD workspace: …» при отсутствии активной фичи → хвост незакрытой фичи; проверить чеклист закрытия. - «SDD workspace: …» при отсутствии активной фичи → хвост незакрытой фичи; проверить чеклист закрытия.
- `WARN .bounded не в .gitignore` → добавить строку, иначе уедет в main. - `WARN .bounded не в .gitignore` → добавить строку, иначе уедет в main.
- `WARN нет allow-правила для git push origin --delete` → находка владельцу: готовую строку из WARN он сам добавляет в `.claude/settings.json` проекта (решение владельца 2, чеклист закрытия п. 4); агент `settings.json` не правит. `ok allow …` называет файл, где правило найдено: проектный `settings.json`, `settings.local.json` или глобальный.
- `WARN tea не отвечает` → `tea logins list`; токен нужен со scopes read:user, write:repository, write:issue. - `WARN tea не отвечает` → `tea logins list`; токен нужен со scopes read:user, write:repository, write:issue.
- «workspace (не git)» → режим нескольких репозиториев. `WARN … битая ссылка` → репозиторий направления переименован или файл удалён: `bash ~/.claude/harness/link-roles.sh <workspace>`. `WARN нет <dir>/.claude/roles/<r>.md` → создать проектный файл роли из `~/.claude/harness/project-template/role-repo/ROLE.md`. - «workspace (не git)» → режим нескольких репозиториев. `WARN … битая ссылка` → репозиторий направления переименован или файл удалён: `bash ~/.claude/harness/link-roles.sh <workspace>`. `WARN нет <dir>/.claude/roles/<r>.md` → создать проектный файл роли из `~/.claude/harness/project-template/role-repo/ROLE.md`.
- `WARN индекс auto-memory > 3 КБ` → индекс снова стал журналом состояния; свернуть закрытое в `registry-closed.md`, уроки — в `notes/` направления (политика памяти в глобальном CLAUDE.md). - `WARN индекс auto-memory > 3 КБ` → индекс снова стал журналом состояния; свернуть закрытое в `registry-closed.md`, уроки — в `notes/` направления (политика памяти в глобальном CLAUDE.md).

View File

@ -5,6 +5,37 @@ set -uo pipefail
CC="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" CC="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
DIR="${1:-.}" DIR="${1:-.}"
ok(){ printf ' ok %s\n' "$*"; }; miss(){ printf ' MISS %s\n' "$*"; }; warn(){ printf ' WARN %s\n' "$*"; }; info(){ printf ' - %s\n' "$*"; } ok(){ printf ' ok %s\n' "$*"; }; miss(){ printf ' MISS %s\n' "$*"; }; warn(){ printf ' WARN %s\n' "$*"; }; info(){ printf ' - %s\n' "$*"; }
# allow-правило для п. 4 закрытия (решение владельца 2): строку добавляет владелец, doctor показывает, где она есть
allow_delete(){ # $1 — корень проекта или workspace
local r line
r=$(python3 - "$1/.claude/settings.json" "$1/.claude/settings.local.json" "$CC/settings.json" <<'PY' 2>/dev/null
import json, sys
cmd = "git push origin --delete " # правило должно покрывать любую ветку: feat/…, chore/archive-…
for f in sys.argv[1:]:
try:
d = json.load(open(f, encoding="utf-8"))
except FileNotFoundError:
continue
except Exception:
print("bad " + f)
continue
allow = (d.get("permissions") or {}).get("allow") if isinstance(d, dict) else None
for a in allow if isinstance(allow, list) else []:
if not isinstance(a, str):
continue
p = a[5:-1] if a.startswith("Bash(") and a.endswith(")") else ("" if a == "Bash" else None)
if p is None:
continue
p = p[:-2] if p.endswith(":*") else p[:-1] if p.endswith("*") else p if a == "Bash" else None
if p is not None and cmd.startswith(p):
print("ok " + f)
sys.exit()
PY
)
while IFS= read -r line; do case "$line" in "bad "*) info "${line#bad } — не JSON, allow-правила не прочитаны" ;; esac; done <<< "$r"
case "$r" in *"ok "*) ok "allow git push origin --delete (${r##*ok })" ;;
*) warn "нет allow-правила для git push origin --delete (закрытие, п. 4) — строка в .claude/settings.json → permissions.allow: \"Bash(git push origin --delete:*)\"" ;; esac
}
echo "## harness-doctor $(date +%F' '%T)" echo "## harness-doctor $(date +%F' '%T)"
echo "## Машина" echo "## Машина"
@ -91,6 +122,7 @@ if root=$(git -C "$DIR" rev-parse --show-toplevel 2>/dev/null); then
[ -f "$root/.claude/code-paths" ] && info "code-paths: $(grep -v '^#' "$root/.claude/code-paths" | tr '\n' ' ')" || info "code-paths: умолчание (любой src/)" [ -f "$root/.claude/code-paths" ] && info "code-paths: $(grep -v '^#' "$root/.claude/code-paths" | tr '\n' ' ')" || info "code-paths: умолчание (любой src/)"
[ -f "$root/.claude/readonly-paths" ] && info "readonly-paths: $(grep -v '^#' "$root/.claude/readonly-paths" | tr '\n' ' ')" [ -f "$root/.claude/readonly-paths" ] && info "readonly-paths: $(grep -v '^#' "$root/.claude/readonly-paths" | tr '\n' ' ')"
git -C "$root" check-ignore -q .bounded 2>/dev/null || warn ".bounded не в .gitignore" git -C "$root" check-ignore -q .bounded 2>/dev/null || warn ".bounded не в .gitignore"
allow_delete "$root"
[ -d "$root/.git/hooks" ] && [ -x "$root/.git/hooks/pre-commit" ] && ok "git pre-commit есть" || info "git pre-commit нет" [ -d "$root/.git/hooks" ] && [ -x "$root/.git/hooks/pre-commit" ] && ok "git pre-commit есть" || info "git pre-commit нет"
if command -v tea >/dev/null 2>&1 && git -C "$root" remote get-url origin >/dev/null 2>&1; then if command -v tea >/dev/null 2>&1 && git -C "$root" remote get-url origin >/dev/null 2>&1; then
info "remote: $(git -C "$root" remote get-url origin)" info "remote: $(git -C "$root" remote get-url origin)"
@ -114,5 +146,6 @@ elif [ -f "$DIR/CLAUDE.md" ] && grep -Eq '^\|[[:space:]]*Каталог[[:space:
# slug каталога памяти Claude Code: все не-алфавитно-цифровые символы пути → «-» (например, /Users/a.b/x → -Users-a-b-x) # slug каталога памяти Claude Code: все не-алфавитно-цифровые символы пути → «-» (например, /Users/a.b/x → -Users-a-b-x)
slug=$(printf '%s' "$ws" | sed 's#[^A-Za-z0-9]#-#g'); mem="$CC/projects/$slug/memory/MEMORY.md" slug=$(printf '%s' "$ws" | sed 's#[^A-Za-z0-9]#-#g'); mem="$CC/projects/$slug/memory/MEMORY.md"
if [ -f "$mem" ]; then n=$(wc -c < "$mem" | tr -d ' '); [ "$n" -le 3072 ] && ok "индекс auto-memory $n Б (≤ 3 КБ)" || warn "индекс auto-memory $n Б > 3 КБ ($mem)"; else info "индекс auto-memory: нет ($mem)"; fi if [ -f "$mem" ]; then n=$(wc -c < "$mem" | tr -d ' '); [ "$n" -le 3072 ] && ok "индекс auto-memory $n Б (≤ 3 КБ)" || warn "индекс auto-memory $n Б > 3 КБ ($mem)"; else info "индекс auto-memory: нет ($mem)"; fi
allow_delete "$ws"
[ -x "$CC/harness/link-roles.sh" ] && info "обновить ссылки: bash $CC/harness/link-roles.sh $ws" [ -x "$CC/harness/link-roles.sh" ] && info "обновить ссылки: bash $CC/harness/link-roles.sh $ws"
else info "не git-репозиторий и не workspace (нет таблицы репозиториев в CLAUDE.md)"; fi else info "не git-репозиторий и не workspace (нет таблицы репозиториев в CLAUDE.md)"; fi

View File

@ -30,6 +30,29 @@ assert_grep "проект: режим workspace" 'workspace' <(printf '%s' "$out
assert_grep "проект: целая ссылка" 'ok +roles/admin.md' <(printf '%s' "$out") assert_grep "проект: целая ссылка" 'ok +roles/admin.md' <(printf '%s' "$out")
assert_grep "проект: битая ссылка" 'WARN +agents/none.md' <(printf '%s' "$out") assert_grep "проект: битая ссылка" 'WARN +agents/none.md' <(printf '%s' "$out")
assert_grep "проект: индекс памяти > 3 КБ" 'WARN +индекс auto-memory' <(printf '%s' "$out") assert_grep "проект: индекс памяти > 3 КБ" 'WARN +индекс auto-memory' <(printf '%s' "$out")
assert_grep "workspace: нет allow push --delete — WARN с готовой строкой (B3)" 'WARN +нет allow-правила для git push origin --delete .*"Bash\(git push origin --delete:\*\)"' <(printf '%s' "$out")
# allow-правило push --delete (B3, решение владельца 2): git-проект, проектные и глобальные настройки
P="$TMP/p"; git init -q -b main "$P" && git -C "$P" commit -q --allow-empty -m init
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$P" 2>&1)
assert_grep "git-проект: нет allow push --delete — WARN" 'WARN +нет allow-правила для git push origin --delete' <(printf '%s' "$out")
mkdir -p "$P/.claude"; printf '{"permissions":{"allow":["Bash(git push origin --delete:*)"]}}' > "$P/.claude/settings.json"
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$P" 2>&1)
assert_grep "git-проект: правило в .claude/settings.json — ok" 'ok +allow git push origin --delete \(.*/p/\.claude/settings\.json\)' <(printf '%s' "$out")
printf '{"permissions":{"allow":["Bash(git push origin --delete feat/:*)"]}}' > "$P/.claude/settings.json"
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$P" 2>&1)
assert_grep "git-проект: правило только для feat/ не покрывает — WARN" 'WARN +нет allow-правила для git push origin --delete' <(printf '%s' "$out")
printf '{"permissions":{"allow":["Bash(git push:*)"]}}' > "$P/.claude/settings.local.json"
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$P" 2>&1)
assert_grep "git-проект: широкое правило в settings.local.json — ok" 'ok +allow git push origin --delete \(.*settings\.local\.json\)' <(printf '%s' "$out")
rm -f "$P/.claude/settings.local.json"; printf '{"permissions":' > "$P/.claude/settings.json"
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$P" 2>&1); rc=$?
assert_eq "git-проект: битый settings.json — doctor не падает" "$rc" "0"
assert_grep "git-проект: битый settings.json — info" '^ - +.*settings\.json — не JSON' <(printf '%s' "$out")
rm -f "$P/.claude/settings.json"; cp "$CC/settings.json" "$TMP/settings.bak"
printf '{"permissions":{"allow":["Bash(git push origin --delete:*)"]},"hooks":{}}' > "$CC/settings.json"
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1)
assert_grep "workspace: правило в глобальном settings.json — ok" 'ok +allow git push origin --delete \(.*/cc/settings\.json\)' <(printf '%s' "$out")
cp "$TMP/settings.bak" "$CC/settings.json"
rm -f "$CC/roles/qa.md" rm -f "$CC/roles/qa.md"
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1) out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1)
assert_grep "машина: MISS roles/qa.md" 'MISS +roles/qa.md' <(printf '%s' "$out") assert_grep "машина: MISS roles/qa.md" 'MISS +roles/qa.md' <(printf '%s' "$out")

View File

@ -10,6 +10,8 @@ check_role_file(){ # $1 = путь, $2 = ожидаемое имя роли
for r in dev admin analyst qa; do check_role_file "$ROOT/roles/$r.md" "$r"; done for r in dev admin analyst qa; do check_role_file "$ROOT/roles/$r.md" "$r"; done
assert_grep "dev: чеклист закрытия перенесён" 'openspec archive' "$ROOT/roles/dev.md" assert_grep "dev: чеклист закрытия перенесён" 'openspec archive' "$ROOT/roles/dev.md"
assert_grep "dev: архив всегда отдельным PR" 'всегда отдельным PR' "$ROOT/roles/dev.md" assert_grep "dev: архив всегда отдельным PR" 'всегда отдельным PR' "$ROOT/roles/dev.md"
assert_grep "dev: п. 4 — строку allow печатают adopt/new-project, наличие показывает doctor (B3)" 'new-project\.sh.*doctor\.sh' "$ROOT/roles/dev.md"
assert_grep "dev: п. 4 — worktree remove раньше branch -D" 'git worktree remove`.*`git branch -D' "$ROOT/roles/dev.md"
assert_grep "dev: не cd при фоновом контроллере" 'cd' "$ROOT/roles/dev.md" assert_grep "dev: не cd при фоновом контроллере" 'cd' "$ROOT/roles/dev.md"
assert_grep "dev: bounded обновляет спеку при смене API" 'спека .*обновляется в том же PR' "$ROOT/roles/dev.md" assert_grep "dev: bounded обновляет спеку при смене API" 'спека .*обновляется в том же PR' "$ROOT/roles/dev.md"
assert_grep "dev: маркировка R-n" 'R-n' "$ROOT/roles/dev.md" assert_grep "dev: маркировка R-n" 'R-n' "$ROOT/roles/dev.md"

View File

@ -25,6 +25,7 @@ assert_eq "admin: коммит есть" "$(git -C "$W/admin" log --oneline | wc
assert_symlink "ws .claude/roles/admin.md слинкован" "$W/.claude/roles/admin.md" assert_symlink "ws .claude/roles/admin.md слинкован" "$W/.claude/roles/admin.md"
assert_symlink "ws .claude/roles/qa.md слинкован" "$W/.claude/roles/qa.md" assert_symlink "ws .claude/roles/qa.md слинкован" "$W/.claude/roles/qa.md"
assert_eq "workspace сам не git" "$([ -d "$W/.git" ] && echo git || echo no)" "no" assert_eq "workspace сам не git" "$([ -d "$W/.git" ] && echo git || echo no)" "no"
assert_grep "new-project --workspace: печатает allow-правило (B3)" '"Bash\(git push origin --delete:\*\)"' <(printf '%s' "$out")
# adopt-project.sh --workspace на существующем каталоге без CLAUDE.md # adopt-project.sh --workspace на существующем каталоге без CLAUDE.md
W2="$TMP/ws2"; mkdir -p "$W2/qa/.claude/roles"; echo '# Роль: qa — x' > "$W2/qa/.claude/roles/qa.md" W2="$TMP/ws2"; mkdir -p "$W2/qa/.claude/roles"; echo '# Роль: qa — x' > "$W2/qa/.claude/roles/qa.md"
out=$(bash "$ROOT/adopt-project.sh" --workspace "$W2" 2>&1); rc=$? out=$(bash "$ROOT/adopt-project.sh" --workspace "$W2" 2>&1); rc=$?
@ -41,5 +42,11 @@ out=$(bash "$ROOT/new-project.sh" "$P" 2>&1); rc=$?
assert_eq "new-project (старый режим): exit 0" "$rc" "0" assert_eq "new-project (старый режим): exit 0" "$rc" "0"
assert_eq "new-project (старый режим): git" "$([ -d "$P/.git" ] && echo git || echo no)" "git" assert_eq "new-project (старый режим): git" "$([ -d "$P/.git" ] && echo git || echo no)" "git"
assert_file "new-project (старый режим): ARCHITECTURE.md" "$P/ARCHITECTURE.md" assert_file "new-project (старый режим): ARCHITECTURE.md" "$P/ARCHITECTURE.md"
assert_grep "new-project (старый режим): печатает allow-правило (B3)" '"Bash\(git push origin --delete:\*\)"' <(printf '%s' "$out")
out=$(bash "$ROOT/adopt-project.sh" "$TMP/notgit" 2>&1); rc=$? out=$(bash "$ROOT/adopt-project.sh" "$TMP/notgit" 2>&1); rc=$?
assert_eq "adopt (старый режим): не git → exit 1" "$rc" "1" assert_eq "adopt (старый режим): не git → exit 1" "$rc" "1"
A="$TMP/adopt"; git init -q -b main "$A" && git -C "$A" commit -q --allow-empty -m init
out=$(bash "$ROOT/adopt-project.sh" "$A" 2>&1); rc=$?
assert_eq "adopt (старый режим): git-репозиторий → exit 0" "$rc" "0"
assert_grep "adopt (старый режим): печатает allow-правило (B3)" '"Bash\(git push origin --delete:\*\)"' <(printf '%s' "$out")
assert_eq "adopt (старый режим): settings.json не создаёт" "$([ -e "$A/.claude/settings.json" ] && echo есть || echo нет)" "нет"