Роли и память, часть 1: слой ролей обвязки (role-hint, /role, briefing, link-roles) #4
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## 0.3.0 — 2026-10-11
|
||||
- Роли сессии `dev|admin|analyst|qa`: общий слой `roles/<r>.md` (`~/.claude/roles/`), проектный слой `<workspace>/.claude/roles/<r>.md`; формат из четырёх разделов (спека `docs/superpowers/specs/2026-10-11-roles-and-memory-design.md`).
|
||||
- Skill `/role <r>`: читает оба слоя, раздел памяти роли, «Читать на старте»; строки `brief:` — фоновому агенту `briefing` (sonnet, только чтение).
|
||||
- SessionStart-хук `role-hint.sh`: подсказка роли по заголовку сессии (`claude -n "admin …"`), регистр и префикс `cc-` не важны; ничего не блокирует.
|
||||
- `link-roles.sh`: симлинки ролей и агентов из workspace в репозитории направлений по таблице `| Каталог | Репозиторий | Роль |`. Память агентов через симлинк не работает (проверено, спека, раздел 11) — для неё запасной вариант 6.5 (`notes/agent-memory.md`).
|
||||
- Шаблоны `project-template/workspace/` и `project-template/role-repo/`; режим `--workspace` в `adopt-project.sh` и `new-project.sh`.
|
||||
- Глобальный CLAUDE.md сжат до ≤ 3 КБ (роль, хуки, память, маркировка, стиль); процесс разработки переехал в `roles/dev.md` — таблица адресов `docs/retro/2026-10-11-claude-md-addresses.md`. Изменения правил: архив OpenSpec всегда отдельным PR; п. 4 чеклиста закрытия — из worktree по allow-правилу; bounded обновляет спеку при смене API; новый п. 7 — память задачи при закрытии.
|
||||
- Политика памяти: состояние задачи в трекере, уроки в `notes/` направления и памяти агентов, в auto-memory — только ловушки машины и индекс.
|
||||
- `doctor.sh`: файлы ролей, самотест `role-hint`, режим workspace (симлинки, размеры CLAUDE.md и индекса памяти).
|
||||
- Тесты пакета `tests/run.sh` (bash, без зависимостей): хук, роли, размеры, skill/agent, link-roles, workspace, doctor, install.
|
||||
- Ретроспектива `docs/retro/2026-10-11.md` и скрипты `docs/retro/scripts/`.
|
||||
|
||||
## 0.2.0 — 2026-09-27
|
||||
- Skill `harness-doctor` (`doctor.sh` + чеклист прочтения): машина, самотест хуков, проект; ставится в `~/.claude/skills/`.
|
||||
- require-plan: защищает любой `src/` на любой глубине (было — только от корня), переопределение списком в `.claude/code-paths`.
|
||||
|
||||
11
CLAUDE.md
11
CLAUDE.md
@ -1,7 +1,7 @@
|
||||
# claude-harness
|
||||
|
||||
## Что это
|
||||
Пакет обвязки для разработки с Claude Code: глобальный `CLAUDE.md` с процессом, три блокирующих хука, скрипты подключения проекта и skill диагностики. Для одного разработчика, который ведёт фичи через superpowers, хранит задачи в Gitea и требования в OpenSpec. Главный сценарий: `bash install.sh` ставит глобальную часть в `~/.claude`, `adopt-project.sh` подключает существующий проект, `new-project.sh` создаёт новый; дальше проект живёт по `docs/HARNESS.md`. Репозиторий — источник; установленные копии в `~/.claude` по месту не правятся.
|
||||
Пакет обвязки для разработки с Claude Code: глобальный `CLAUDE.md` с процессом, три блокирующих хука и неблокирующий `role-hint`, скрипты подключения проекта и skill диагностики. Для одного разработчика, который ведёт фичи через superpowers, хранит задачи в Gitea и требования в OpenSpec. Главный сценарий: `bash install.sh` ставит глобальную часть в `~/.claude`, `adopt-project.sh` подключает существующий проект, `new-project.sh` создаёт новый; дальше проект живёт по `docs/HARNESS.md`. Репозиторий — источник; установленные копии в `~/.claude` по месту не правятся.
|
||||
|
||||
## Стек и ограничения
|
||||
- Bash (`set -euo pipefail`) и markdown. JSON из stdin хуков разбирается встроенным `python3`; других зависимостей времени выполнения нет. Внешние CLI (`tea`, `openspec`, `codegraph`) проверяются, но не требуются для установки.
|
||||
@ -11,15 +11,18 @@
|
||||
- macOS и Linux; `python3` должен быть в PATH сессии Claude Code.
|
||||
|
||||
## Архитектура
|
||||
- `hooks/` — поведение обвязки, единственный «код» с логикой. `guard-main.sh` (PreToolUse на Edit/Write/MultiEdit/Bash): запрет записи на main/master/detached HEAD, исключение для openspec-only коммитов. `require-plan.sh` (PreToolUse на Edit/Write/MultiEdit): правки в каталогах кода только при плане для ветки или файле `.bounded`. `harness-check.sh` (SessionStart): проверка, что проект подключён. `settings.hooks.json` — блок, который `install.sh` сливает в `~/.claude/settings.json`.
|
||||
- `hooks/` — поведение обвязки, единственный «код» с логикой. `guard-main.sh` (PreToolUse на Edit/Write/MultiEdit/Bash): запрет записи на main/master/detached HEAD, исключение для openspec-only коммитов. `require-plan.sh` (PreToolUse на Edit/Write/MultiEdit): правки в каталогах кода только при плане для ветки или файле `.bounded`. `harness-check.sh` (SessionStart): проверка, что проект подключён. `role-hint.sh` (SessionStart, все источники): подсказка роли по `session_title`. `settings.hooks.json` — блок, который `install.sh` сливает в `~/.claude/settings.json`.
|
||||
- `install.sh`, `adopt-project.sh`, `new-project.sh` — установка и бутстрап. `install.sh` копирует `global/`, `hooks/`, `project-template/`, `adopt-project.sh`, `new-project.sh` и `skills/harness-doctor` в `~/.claude/{CLAUDE.md,hooks,harness,skills}`. `adopt-project.sh` работает на ветке `chore/harness-init` и вливает её ff в текущую ветку; это единственная разрешённая запись на main вне PR.
|
||||
- `global/CLAUDE.md` — глобальные правила процесса, ставятся в `~/.claude/CLAUDE.md`. `project-template/` — `CLAUDE.md`, `CLAUDE.harness-section.md` (секция «OpenSpec и superpowers», по ней `harness-check` узнаёт подключённый проект), `ARCHITECTURE.md`, `gitignore`.
|
||||
- `skills/harness-doctor/` — skill диагностики: `scripts/doctor.sh` снимает состояние машины и проекта и прогоняет самотест хуков; `SKILL.md` — чеклист прочтения отчёта.
|
||||
- `roles/` — общий слой ролей (формат: четыре раздела, см. спеку ролей), `skills/role/` — skill `/role`, `agents/briefing.md` — агент выжимки, `link-roles.sh` — симлинки workspace → репозитории направлений. Ставятся `install.sh` в `~/.claude/{roles,skills/role,agents,harness}`.
|
||||
- `tests/` — тесты пакета на bash: `run.sh` + `test-*.sh`, работают с источниками, `install.sh` ставят во временный `CLAUDE_CONFIG_DIR`.
|
||||
- `caveman/config.json` — выключенный по умолчанию caveman. `examples/` — реальный `CLAUDE.md` проекта-пилота.
|
||||
- `docs/HARNESS.md` — как устроен цикл фичи, SDD, маркировка, закрытие. `docs/PILOT-2026-09-27.md` — пилот. `docs/retro/` — ретроспектива 11.10.2026 и скрипты подсчёта по транскриптам. `docs/superpowers/specs/` — спеки фич обвязки, `docs/superpowers/plans/` — планы.
|
||||
- Связь слоёв: хуки читают JSON вызова из stdin и состояние git проекта; скрипты бутстрапа читают `project-template/`; `doctor.sh` читает установленные копии в `~/.claude`, не репозиторий.
|
||||
|
||||
## Команды
|
||||
- `bash tests/run.sh [имя]` — весь набор тестов. Зелёным считается только весь набор.
|
||||
- `bash install.sh --check` — проверка окружения: бинарники, плагины, конфликты.
|
||||
- `bash install.sh` — установка глобальной части, идемпотентно.
|
||||
- `bash skills/harness-doctor/scripts/doctor.sh [проект]` — отчёт и самотест хуков. Только чтение.
|
||||
@ -28,9 +31,9 @@
|
||||
- `python3 docs/retro/scripts/retro_stats.py <каталог транскриптов>` и `retro_blocks.py` — статистика сессий и блокировок хуков для ретро.
|
||||
|
||||
## Тесты
|
||||
- Отдельного набора тестов нет. Единственная автоматическая проверка — самотест хуков в `doctor.sh`: фиктивные вызовы Write/Bash на main и на ветке, `src/` с планом и без, `.bounded`. Прогоняется по установленным копиям, поэтому перед проверкой изменений в `hooks/` сначала `bash install.sh`.
|
||||
- `tests/run.sh` — автоматические тесты источников (хуки через фиктивный JSON, скрипты во временных каталогах, размеры). Самотест хуков в `doctor.sh` проверяет установленные копии; перед ним `bash install.sh`.
|
||||
- Ручная проверка хука: на main в любом репозитории попросить агента создать файл, ожидается `BLOCKED`.
|
||||
- Регрессионные тесты хуков на реальных командах из транскриптов — бэклог ретро B2, пока не сделаны.
|
||||
- `tests/` покрывает `role-hint` и скрипты `install.sh`, `doctor.sh`, `link-roles.sh`; регрессионные тесты `guard-main` и `require-plan` на реальных командах из транскриптов (бэклог ретро B2) пока не сделаны.
|
||||
|
||||
## Текущая работа
|
||||
Бэклог обвязки — `docs/retro/2026-10-11.md`, раздел 4 (B1–B12), решения владельца — раздел 5. Порядок: B1+B2 (guard-main по цели записи, тесты хуков), затем B5+B6 (роли и память, спека `docs/superpowers/specs/2026-10-11-roles-and-memory-design.md`), затем B3/B4/B10. Находки ретро маркируются `RT-n`.
|
||||
|
||||
25
README.md
25
README.md
@ -11,6 +11,7 @@
|
||||
| Текущее поведение системы | OpenSpec | требования, дельты, история решений | `npm i -g @fission-ai/openspec` |
|
||||
| Карта кода | codegraph (MCP) | связи символов для сабагентов | `brew install`/`npm i -g` |
|
||||
| Контекст по модулям | вложенные CLAUDE.md, ARCHITECTURE.md | границы, конвенции | этот пакет |
|
||||
| Роль сессии | `roles/`, skill `/role`, агент `briefing`, хук `role-hint.sh` | правила, файлы и память своей роли | этот пакет |
|
||||
| Жёсткие правила | `guard-main.sh`, `require-plan.sh` (PreToolUse) | то, что нельзя доверять промпту | этот пакет |
|
||||
|
||||
Что **не** входит и почему: beads (дублирует Gitea), template-bridge (устарел, дублирует ledger), caveman по умолчанию (сжимает отчёты и рулинги; остаётся доступен по `/caveman`).
|
||||
@ -27,6 +28,14 @@ project-template/ CLAUDE.md, ARCHITECTURE.md, gitignore для ново
|
||||
examples/ реальный CLAUDE.md проекта-пилота
|
||||
install.sh установка глобальной части, идемпотентно; --check проверяет окружение
|
||||
new-project.sh каркас нового проекта: git, CLAUDE.md, openspec init, codegraph init, remote
|
||||
roles/<r>.md общий слой ролей dev/admin/analyst/qa → ~/.claude/roles/
|
||||
skills/role/SKILL.md /role <r>: загрузить роль, память, запустить briefing → ~/.claude/skills/role/
|
||||
agents/briefing.md выжимка состояния для роли (sonnet, только чтение) → ~/.claude/agents/
|
||||
hooks/role-hint.sh подсказка роли по заголовку сессии (SessionStart)
|
||||
link-roles.sh симлинки ролей/агентов из workspace в репозитории направлений → ~/.claude/harness/
|
||||
project-template/workspace/ CLAUDE.md workspace с таблицей репозиториев, roles/dev.md
|
||||
project-template/role-repo/ репозиторий направления: CLAUDE.md, ROLE.md, notes/README.md
|
||||
tests/run.sh тесты пакета (bash): bash tests/run.sh
|
||||
docs/HARNESS.md как это работает: слои, цикл фичи, SDD, дисциплина закрытия
|
||||
docs/PILOT-2026-09-27.md результаты пилота и находки
|
||||
```
|
||||
@ -39,6 +48,21 @@ bash ~/projects/cc-skills/claude-harness/adopt-project.sh ~/projects/foo # [--
|
||||
|
||||
Запускать из терминала, не из Claude Code: хук guard-main не даёт агенту писать на main, а бутстрап — это запись на main. Скрипт делает ветку `chore/harness-init`, дописывает секцию харнеса в существующий `CLAUDE.md` (или создаёт его из шаблона), добавляет `ARCHITECTURE.md`, строки в `.gitignore`, `openspec init`, `codegraph init`, коммитит и вливает в main fast-forward. Заполнить описание проекта в `CLAUDE.md` потом можно агентом bounded-задачей на ветке.
|
||||
|
||||
## Workspace из нескольких репозиториев и роли
|
||||
|
||||
```
|
||||
bash ~/.claude/harness/adopt-project.sh --workspace ~/projects/foo # есть каталог с репозиториями
|
||||
bash ~/.claude/harness/new-project.sh --workspace ~/projects/foo --role admin --role qa # с нуля
|
||||
```
|
||||
|
||||
Заполните таблицу репозиториев в `CLAUDE.md` workspace и перезапустите `link-roles.sh`. Сессии запускайте из корня workspace с ролью в заголовке: `claude -n "admin спринт 3"`. Удобно завести алиасы в профиле shell (обвязка их не ставит):
|
||||
|
||||
```bash
|
||||
alias cc-dev='claude -n "dev"'; alias cc-admin='claude -n "admin"'; alias cc-qa='claude -n "qa"'; alias cc-analyst='claude -n "analyst"'
|
||||
```
|
||||
|
||||
В сессии: `/role` — текущая роль и список; `/role admin` — переключить. Подробности в `docs/HARNESS.md`, раздел «Роли».
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
@ -58,6 +82,7 @@ bash install.sh
|
||||
|
||||
# 4. Проверка хука: в любом репозитории на main запустить claude и попросить создать файл.
|
||||
# Ожидается BLOCKED и предложение создать worktree.
|
||||
# bash tests/run.sh — тесты пакета должны быть зелёными.
|
||||
```
|
||||
|
||||
## Новый проект
|
||||
|
||||
@ -4,6 +4,25 @@
|
||||
# Делает всё на ветке chore/harness-init и по умолчанию вливает её в main fast-forward.
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
# Режим workspace (каталог не git, рядом репозитории направлений): bash adopt-project.sh --workspace [папка]
|
||||
if [ "${1:-}" = "--workspace" ]; then
|
||||
cd "${2:-.}"; NAME="$(basename "$(pwd)")"
|
||||
if [ -f CLAUDE.md ]; then echo "CLAUDE.md: есть, не трогаю"
|
||||
else sed "s/{{PROJECT_NAME}}/$NAME/g" "$HERE/project-template/workspace/CLAUDE.md" > CLAUDE.md; echo "CLAUDE.md: создан из шаблона workspace — заполни {{...}} и таблицу репозиториев"; fi
|
||||
mkdir -p .claude/roles
|
||||
if [ -e .claude/roles/dev.md ]; then echo ".claude/roles/dev.md: есть, не трогаю"
|
||||
else sed "s/{{PROJECT_NAME}}/$NAME/g" "$HERE/project-template/workspace/roles/dev.md" > .claude/roles/dev.md; echo ".claude/roles/dev.md: заготовка"; fi
|
||||
bash "$HERE/link-roles.sh" "$(pwd)"
|
||||
cat <<MSG
|
||||
|
||||
Workspace $NAME подключён. Дальше:
|
||||
1. Заполни таблицу репозиториев в CLAUDE.md и перезапусти: bash $HERE/link-roles.sh $(pwd)
|
||||
2. Разреши агенту удалять ветки на сервере (чеклист закрытия, п. 4) — строка в .claude/settings.json → permissions.allow:
|
||||
"Bash(git push origin --delete:*)"
|
||||
3. Сессии: claude -n "dev …" | "admin …" | "qa …" | "analyst …" из этого каталога.
|
||||
MSG
|
||||
exit 0
|
||||
fi
|
||||
DIR="."; MERGE=1; PUSH=0
|
||||
for a in "$@"; do case "$a" in --no-merge) MERGE=0;; --push) PUSH=1;; *) DIR="$a";; esac; done
|
||||
cd "$DIR"
|
||||
|
||||
24
agents/briefing.md
Normal file
24
agents/briefing.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
name: briefing
|
||||
description: Выжимка состояния для роли при /role — активные задачи, решения за последние дни, открытые вопросы, ловушки. Только чтение. Запускается skill-ом /role в фоне, не вызывать для другой работы.
|
||||
model: sonnet
|
||||
tools: Read, Glob, Grep, Bash
|
||||
---
|
||||
|
||||
Ты готовишь выжимку для сессии в роли, названной в промпте. Тебе дан workspace (cwd) и список источников: пути
|
||||
(файлы или каталоги, относительно workspace) и команды.
|
||||
|
||||
Правила:
|
||||
- Выполняй только читающие команды: `tea issues ls`, `tea pulls ls`, `openspec list`, `git log`, `git status`, `ls`, `cat`,
|
||||
`find`, `grep`. Любую другую команду из списка пропусти и назови в разделе «Ловушки» как «не выполнено: <команда>».
|
||||
- Каталог читай так: `MEMORY.md`/`README.md`/`decisions.md`, если есть, затем 5 самых свежих файлов по дате изменения.
|
||||
- Не редактируй файлы, не предлагай действий, не оценивай.
|
||||
|
||||
Ответ — до 60 строк, ровно в такой форме, без вступления:
|
||||
|
||||
**Активные задачи** — открытые issue/PR/срезы с номерами и состоянием, по строке на задачу.
|
||||
**Решения за последние дни** — решения владельца за 14 дней с датами (из notes/, decisions.md, памяти).
|
||||
**Открытые вопросы** — что ждёт решения владельца, с источником.
|
||||
**Ловушки** — предупреждения из уроков и памяти, актуальные для этой роли, по строке.
|
||||
|
||||
Если раздел пуст — строка «— нет». Если источник не найден — строка «не найдено: <путь>» в «Ловушках».
|
||||
@ -72,13 +72,26 @@ Fix loop: раунды 1–3 продолжают того же implementer, 4
|
||||
| Никаких изменений на main / detached HEAD | `guard-main.sh`, PreToolUse на Edit/Write/MultiEdit/Bash | проверяет ветку; для Bash блокирует команды, похожие на запись (`>`, `tee`, `sed -i`, `mv`, `rm`, `git commit/push`…), пропускает `git worktree add`; коммит/push в main разрешены только если изменены исключительно файлы в `openspec/` (архивация) |
|
||||
| Правки в коде только при плане или `.bounded` | `require-plan.sh`, PreToolUse на Edit/Write/MultiEdit | защищает любой `src/` на любой глубине (`api/src/`, `packages/*/src/`) или каталоги из `.claude/code-paths`; ищет план в `docs/superpowers/plans/` для текущей ветки или файл `.bounded` |
|
||||
| Обвязка подключена к проекту | `harness-check.sh`, SessionStart | при старте сессии проверяет `openspec/`, `.codegraph/`, секцию в CLAUDE.md; если чего-то нет — пишет агенту инструкцию запустить `~/.claude/harness/adopt-project.sh` (спросив пользователя; `HARNESS_AUTO_ADOPT=1` — без вопроса) |
|
||||
| Подсказка роли по заголовку сессии | `role-hint.sh`, SessionStart (все источники) | читает `session_title`; если начинается с «dev, admin, analyst или qa» — одна строка-подсказка вызвать `/role`; ничего не блокирует |
|
||||
| Тесты зелёные перед коммитом | git pre-commit в проекте (не входит в пакет) | обычный git-хук с командой тестов |
|
||||
|
||||
Exit 2 блокирует вызов и показывает агенту stderr, поэтому агент сам делает то, что просит сообщение. Оба обхода, замеченные на пилоте (запись через `echo >` вместо Write; push через detached HEAD), закрыты.
|
||||
|
||||
## Дисциплина между сессиями
|
||||
|
||||
Новая сессия начинается с `tea issues ls` и `openspec list`, а не с памяти о прошлой. Незавершённый план возобновляется из ledger в worktree. Связка разваливается не на исполнении, а на закрытии — поэтому «закрывай» выполняет весь чеклист без остановок.
|
||||
Новая сессия начинается с роли (`/role`), затем `tea issues ls` и `openspec list`, а не с памяти о прошлой. Незавершённый план возобновляется из ledger в worktree. Связка разваливается не на исполнении, а на закрытии — поэтому «закрывай» выполняет весь чеклист без остановок.
|
||||
|
||||
## Роли
|
||||
|
||||
Одна сессия — одна роль: `dev`, `admin`, `analyst`, `qa`. Роль даёт сессии только свои правила, свои файлы на старте и свой раздел памяти; процесс разработки (superpowers → Gitea → OpenSpec, SDD, чеклист закрытия) живёт в роли dev и не загружается в сессии admin.
|
||||
|
||||
Два слоя, оба в git: общий — `~/.claude/roles/<r>.md` (из пакета, `install.sh`), проектный — `<workspace>/.claude/roles/<r>.md` (для dev — файл, для остальных — симлинк в репозиторий направления `admin/`, `qa/`, `analysis/`). Формат одинаковый: четыре раздела «Процесс», «Читать на старте», «Не делать», «Делегировать»; при конфликте побеждает проектный слой.
|
||||
|
||||
Выбор роли, от автоматического к ручному: заголовок сессии (`claude -n "admin …"`; хук `role-hint.sh` печатает подсказку `[claude-harness] Роль сессии: admin`), вывод из первого сообщения (правило в глобальном CLAUDE.md: объявить «Роль: admin; если не так, скажи»), явно — `/role <r>`. Skill `/role` читает оба слоя, раздел памяти роли, строки «Читать на старте», а строки с префиксом `brief:` отдаёт фоновому агенту `briefing`, который возвращает выжимку: активные задачи, решения, открытые вопросы, ловушки.
|
||||
|
||||
Workspace из нескольких репозиториев (не git, cwd всех сессий) описывается таблицей `| Каталог | Репозиторий | Роль |` в его CLAUDE.md; `link-roles.sh` по ней создаёт симлинки на проектные файлы ролей и агентов направлений. Память агентов (`memory: project`) через симлинк каталога не работает (проверено 2026-10-11, спека, раздел 11), поэтому агентам направлений применяется запасной вариант спеки 6.5: поле `memory` не используется, промпт агента велит читать `<dir>/notes/agent-memory.md` при старте и дописывать в конце. `adopt-project.sh --workspace` и `new-project.sh --workspace --role …` собирают workspace из шаблонов; `doctor.sh` проверяет ссылки и размеры.
|
||||
|
||||
Политика памяти (глобальный CLAUDE.md): состояние задачи — в трекере; уроки, решения владельца и рецепты — в `notes/` направления и памяти агентов (в git); в локальной auto-memory — только ловушки машины и индекс со ссылками. При закрытии файл auto-memory задачи сворачивается в строку `registry-closed.md`.
|
||||
|
||||
## Стоимость
|
||||
|
||||
|
||||
35
docs/retro/2026-10-11-claude-md-addresses.md
Normal file
35
docs/retro/2026-10-11-claude-md-addresses.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Куда переехал глобальный CLAUDE.md (0.2.0 → 0.3.0)
|
||||
|
||||
Проверка критерия спеки «ни одно правило не потеряно». Старый файл — `global/CLAUDE.md` до коммита этой ветки (9155 Б).
|
||||
|
||||
| Старый абзац (раздел → пункт) | Новый адрес |
|
||||
|---|---|
|
||||
| Роли инструментов → superpowers ведёт процесс | `roles/dev.md` → Процесс → Инструменты и их роли |
|
||||
| Роли инструментов → Gitea, одна фича = одна issue, порядок старта | `roles/dev.md` → Инструменты и их роли |
|
||||
| Роли инструментов → OpenSpec | `roles/dev.md` → Инструменты и их роли |
|
||||
| Роли инструментов → Ledger superpowers | `roles/dev.md` → Инструменты и их роли |
|
||||
| Маршрутизация → сообщение `[claude-harness]`, adopt-project.sh | `global/CLAUDE.md` → Хуки и ветки (сообщение); `roles/dev.md` → Маршрутизация (скрипт) |
|
||||
| Маршрутизация → bounded-задача | `roles/dev.md` → Маршрутизация (дополнено: спека при смене API, verification-before-completion) |
|
||||
| Маршрутизация → architectural-задача, спека в openspec/changes | `roles/dev.md` → Маршрутизация |
|
||||
| Маршрутизация → читать openspec/specs и ARCHITECTURE.md | `roles/dev.md` → Маршрутизация |
|
||||
| Маршрутизация → codegraph, projectPath, пустой ответ | `global/CLAUDE.md` → Хуки и ветки (init, projectPath); `roles/dev.md` → Инструменты (implementer, пустой ответ, замер) |
|
||||
| Исполнение → только в worktree, имя ветки | `roles/dev.md` → Исполнение |
|
||||
| Исполнение → SDD по умолчанию, контроллер на sonnet, executing-plans до 5 задач | `roles/dev.md` → Исполнение (дополнено: режим не спрашивать) |
|
||||
| Исполнение → модели исполнителей | `roles/dev.md` → Исполнение |
|
||||
| Завершение → finishing-a-development-branch, receiving-code-review, merge человеком | `roles/dev.md` → Завершение |
|
||||
| Завершение → чеклист закрытия п. 1 (архив в main) | `roles/dev.md` → Завершение п. 1 — изменено: архив всегда отдельным PR (решение владельца 11.10.2026) |
|
||||
| Завершение → чеклист п. 2–3 | `roles/dev.md` → Завершение п. 2–3 |
|
||||
| Завершение → чеклист п. 4 (удаление веток) | `roles/dev.md` → Завершение п. 4 — дополнено: из worktree, allow-правило |
|
||||
| Завершение → чеклист п. 5–7 | `roles/dev.md` → Завершение п. 5, 6, 8 (новый п. 7 — память, спека 7.1) |
|
||||
| Завершение → неприменимое к bounded пропускается | `roles/dev.md` → Завершение |
|
||||
| Завершение → любой другой push в main только через PR | `roles/dev.md` → Завершение п. 1 (без исключений) |
|
||||
| Завершение → «Rulings I made» в PR | `roles/dev.md` → Завершение |
|
||||
| Завершение → Decisions made without discussion | `roles/dev.md` → Завершение |
|
||||
| Маркировка → список маркеров | `global/CLAUDE.md` → Маркировка (кратко); `roles/dev.md` → Маркировка (полностью, + `RT-n`) |
|
||||
| Маркировка → статусы находок | `global/CLAUDE.md` → Маркировка; `roles/dev.md` → Маркировка |
|
||||
| Маркировка → таблица в финальном отчёте, deferred в PR и follow-up | `roles/dev.md` → Маркировка |
|
||||
| Стиль → не сжимать, caveman по команде | `global/CLAUDE.md` → Стиль |
|
||||
|
||||
Правка финального ревью (I-1): в разделе «Роль» формулировка «Процесс и чеклисты роли — в её файле» сокращена до «Процесс роли — в `~/.claude/roles/<r>.md`» (ради лимита 3 КБ), добавлено правило для сабагентов; ссылка «Подробности — в роли dev» стала полным путём.
|
||||
|
||||
Новое, чего не было: раздел «Роль» (спека 4), раздел «Память» (спека 7.1), запрет обхода хуков (ретро, `feedback-no-harness-bypass`), `cd` при фоновом контроллере (RT-2), абсолютные пути worktree, trailer коммита (RT-9), предусловия окружения (RT-8).
|
||||
1573
docs/superpowers/plans/2026-10-11-roles-and-memory.md
Normal file
1573
docs/superpowers/plans/2026-10-11-roles-and-memory.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -28,7 +28,7 @@
|
||||
settings.json как сейчас (хуки workspace, разрешения)
|
||||
roles/<r>.md проектный слой ролей (dev — файл; admin/analyst/qa — симлинки в репозитории направлений)
|
||||
agents/<name>.md симлинки на агентов из репозиториев направлений
|
||||
agent-memory/<name>/ симлинки на память агентов в репозиториях направлений
|
||||
agent-memory/<name>/ симлинки на память агентов (не работает, см. раздел 11)
|
||||
<code repos>/ роль dev
|
||||
docs/ витрина: контекст, модель, контракты, гайды, инструкции. Читают все, пишут через PR
|
||||
admin/ → arm-ka-admin кухня admin: Jira, Гант, спринты, документы заказчику, notes/, .claude/
|
||||
@ -47,7 +47,7 @@
|
||||
.claude/role имя роли по умолчанию (одна строка) — для будущего режима запуска из репозитория
|
||||
.claude/roles/<r>.md проектный слой роли
|
||||
.claude/agents/<name>.md агенты роли
|
||||
.claude/agent-memory/<name>/MEMORY.md память агентов (коммитится)
|
||||
.claude/agent-memory/<name>/MEMORY.md память агентов (коммитится; см. раздел 11: симлинк не работает, применяется запасной вариант)
|
||||
notes/ уроки, решения владельца, рецепты роли (markdown, коммитится)
|
||||
<рабочие каталоги>
|
||||
```
|
||||
@ -183,7 +183,7 @@ SDD-контроллер и implementer-ы остаются описанием
|
||||
### 6.5 Память агентов через симлинк
|
||||
|
||||
`memory: project` пишет в `<cwd-проект>/.claude/agent-memory/<name>/`. Чтобы память оказалась в git репозитория направления,
|
||||
`<workspace>/.claude/agent-memory/<name>` — симлинк в `<dir>/.claude/agent-memory/<name>`. Проверяется первой (раздел 9).
|
||||
`<workspace>/.claude/agent-memory/<name>` — симлинк в `<dir>/.claude/agent-memory/<name>`. Проверяется первой (раздел 9). (см. раздел 11: симлинк не работает; применяется запасной вариант)
|
||||
Запасной вариант: поле `memory` не используется, промпт агента велит читать `<dir>/notes/agent-memory.md` при старте и дописывать в конце.
|
||||
|
||||
## 7. Память
|
||||
@ -233,7 +233,7 @@ SDD-контроллер и implementer-ы остаются описанием
|
||||
| `skills/role/SKILL.md` | новый, раздел 5 |
|
||||
| `agents/briefing.md` | новый, 6.1 |
|
||||
| `hooks/role-hint.sh` + `hooks/settings.hooks.json` | новый SessionStart-хук; слияние блока в install.sh как у harness-check |
|
||||
| `link-roles.sh` | новый: по таблице репозиториев в `CLAUDE.md` workspace (или по аргументам) создаёт симлинки `.claude/roles`, `.claude/agents`, `.claude/agent-memory`; идемпотентно; печатает, что сделано |
|
||||
| `link-roles.sh` | новый: по таблице репозиториев в `CLAUDE.md` workspace (или по аргументам) создаёт симлинки `.claude/roles`, `.claude/agents` (и `.claude/agent-memory`, см. раздел 11: симлинк не работает, в реализации не создаётся); идемпотентно; печатает, что сделано |
|
||||
| `install.sh` | ставит `roles/`, `skills/role`, `agents/briefing.md`, `hooks/role-hint.sh`, `link-roles.sh` в `~/.claude/harness/`; `--check` проверяет их наличие |
|
||||
| `project-template/` | `.claude/roles/*.md` заготовки; `role-repo/` шаблон репозитория направления (CLAUDE.md, `.claude/role`, `notes/README.md`) |
|
||||
| `adopt-project.sh` / `new-project.sh` | режим `--workspace`: создать `CLAUDE.md` workspace, `.claude/roles/`, вызвать `link-roles.sh`; существующий режим не меняется |
|
||||
@ -271,7 +271,7 @@ SDD-контроллер и implementer-ы остаются описанием
|
||||
|
||||
## 11. Открытые вопросы
|
||||
|
||||
- Работает ли `memory: project` через симлинк каталога (проверка 1). Пока не проверено, в плане первой задачей.
|
||||
- Работает ли `memory: project` через симлинк каталога — проверено 2026-10-11 (план, Task 1): нет. Агент с `memory: project` в песочнице, где `.claude/agent-memory/probe` — симлинк на каталог вне рабочей копии, сообщил, что память только для чтения и MEMORY.md не загружен; ничего не записано ни в целевой каталог симлинка, ни в обход него (повтор через промпт «Используй агента probe» и с `--add-dir` на целевой каталог дал то же). Контроль с обычным каталогом `.claude/agent-memory/probe` записал MEMORY.md с `probe-ok 2026-10-11`. В части 2 применяется запасной вариант 6.5.
|
||||
- Имена репозиториев в Gitea: предложено `RSHB/arm-ka-admin`, `RSHB/arm-ka-qa`, `RSHB/arm-ka-analysis`; подтверждает владелец при создании.
|
||||
- Алиасы `cc-<роль>`: в профиле shell владельца, обвязка только документирует.
|
||||
|
||||
@ -285,3 +285,17 @@ SDD-контроллер и implementer-ы остаются описанием
|
||||
Цена ошибки: выжимка приходит после первого ответа; если это мешает, флаг `--wait`.
|
||||
- 11.10.2026. Спека лежит в `docs/superpowers/specs/` репозитория обвязки, а не в `openspec/`: обвязка к самой себе не
|
||||
подключена, OpenSpec в ней нет. Вопрос «подключать ли обвязку к обвязке» открыт, владельцу не задавался. Цена ошибки: перенос файла.
|
||||
- 11.10.2026 (план). Тесты пакета — `tests/run.sh` на bash в репозитории, в дополнение к самотесту `doctor.sh` (спека, раздел 8, упоминала только doctor). Обоснование: TDD по задачам плана невозможен против установленных копий. Цена ошибки: лишний каталог.
|
||||
- 11.10.2026 (план). Хук `role-hint.sh` регистрируется записью без `matcher` (документация: отсутствующий matcher = все источники), а не перечислением `startup|resume|clear|compact`. Цена ошибки: одна строка в `settings.hooks.json`.
|
||||
- 11.10.2026 (план). Формат таблицы репозиториев workspace зафиксирован: `| Каталог | Репозиторий | Роль |`; `link-roles.sh` и `doctor.sh` узнают workspace по заголовку этой таблицы. Цена ошибки: правка двух regex.
|
||||
- 11.10.2026 (план). `role-hint.sh` понимает заголовок без учёта регистра и с префиксом `cc-` (алиасы из README). Цена ошибки: нет.
|
||||
- 11.10.2026 (план). Часть 2 (awp-ca: проектные файлы ролей, агенты jira-admin/qa-runner, репозитории направлений, чистка памяти, песочница, перенос) — отдельный план в workspace awp-ca после merge этой ветки; каталог для роли analyst — `analysis/`. Цена ошибки: нет.
|
||||
- 11.10.2026 (план). Чеклист закрытия получил п. 7 (память задачи, спека 7.1) и новый п. 8 (итоговое сообщение); нумерация старых п. 1–6 сохранена.
|
||||
- 12.10.2026 (SDD, R-4, design #11). Песочница spike (Task 1) создана в scratchpad сессии, а не в `/tmp`: правило окружения. Цена ошибки: нет.
|
||||
- 12.10.2026 (SDD, R-5, design #12). Коммит Task 2 с чужим trailer (`Claude Haiku 5.5` вместо `Claude Fable 5.1`) исправлен `git commit --amend` до любого push; остальным исполнителям trailer напомнен в общем брифе. Цена ошибки: переписана локальная история одного коммита, force-push не требовался.
|
||||
- 12.10.2026 (SDD, R-6, design #13). Дефект плана в Task 3: grep `спека обновляется в том же PR` не совпадал с собственным текстом `dev.md` плана. Принят ослабленный шаблон `спека .*обновляется в том же PR`, текст роли оставлен дословным. Цена ошибки: чуть слабее проверка.
|
||||
- 12.10.2026 (SDD, R-7, design #14). Дефект плана в Task 5: grep `без аргумента` не совпадал с заголовком `## Без аргумента`. Принят шаблон `[Бб]ез аргумента`. Цена ошибки: чуть слабее проверка.
|
||||
- 12.10.2026 (SDD, R-8, design #15). Task 10: фразы плана «память агентов через симлинк работает» заменены на результат spike (не работает, запасной вариант 6.5). Создание симлинков `agent-memory` в `link-roles.sh` сначала оставлено; отменено рулингом R-9.
|
||||
- 12.10.2026 (SDD, R-9, design #16). Отмена R-8 по финальному ревью (I-2): симлинк `agent-memory/<name>` ломает `memory: project` (память только для чтения), поэтому `link-roles.sh` больше не создаёт `agent-memory`, `doctor.sh` их не проверяет, шаблоны указывают на `notes/agent-memory.md`; в спеке добавлены указатели на раздел 11. Цена ошибки: если позже симлинк заработает, линковку придётся вернуть.
|
||||
- 12.10.2026 (SDD, R-10, design #17). Финальное ревью I-1: в глобальный `CLAUDE.md` добавлено правило «сабагент роль не выбирает и `/role` не вызывает; процесс dev — `~/.claude/roles/dev.md`, если промпт велит» (файл 3058 Б из 3072), а в `roles/dev.md` «Делегировать» — указание контроллеру SDD и ревьюеру ветки читать `dev.md` и передавать дальше. Цена ошибки: 150 Б бюджета глобального файла.
|
||||
- 12.10.2026 (SDD, R-11, design #18). Объём fix-волны после финального ревью: I-1, I-2, M-1 (+T9.2), M-2, M-3, M-5, M-7, T10.1, T6.1, T10.3, T10.4. Перенесено в часть 2 (проектный слой awp-ca): M-4 (команды «Читать на старте» для workspace, дубль `brief:`, строка OQ у analyst) и M-6 (правило codegraph «на период замера» и ссылка на несуществующий `/harness-retro` в общем `dev.md`). Цена ошибки: до части 2 в общем слое остаётся проектная формулировка.
|
||||
|
||||
@ -1,47 +1,24 @@
|
||||
# Процесс разработки
|
||||
# Правила сессии
|
||||
|
||||
## Роли инструментов
|
||||
- Superpowers ведёт процесс внутри одной фичи: brainstorming → writing-plans → исполнение → ревью → finishing-a-development-branch.
|
||||
- Gitea (CLI `tea`) хранит задачи между сессиями. Одна фича = одна issue = одна ветка = один PR. Issue заводит агент (`tea issues create`) из формулировки цели, до brainstorming. Порядок: сначала worktree на временной ветке (хук не даёт писать на main), затем issue, затем ветка переименовывается в `feat/<issue-id>-<slug>`.
|
||||
- OpenSpec хранит требования. `openspec/specs/` — текущее поведение системы, `openspec/changes/<id>/` — дельта текущей фичи. Спека — единственный источник истины при рулингах.
|
||||
- Ledger superpowers (`.superpowers/sdd/`) — прогресс только внутри текущего плана. Задачи плана в Gitea не дублировать.
|
||||
## Роль
|
||||
- Сессия работает в одной из ролей: `dev`, `admin`, `analyst`, `qa`. Правила роли загружает `/role <r>`; новая роль заменяет предыдущую целиком.
|
||||
- Подсказка `[claude-harness] Роль сессии: <r>` от хука при старте — вызвать `/role <r>` до первого ответа.
|
||||
- Без подсказки: если роль очевидна из первого сообщения (Jira, Гант, спринт → admin; стенд, прогон, скриншот, макет → qa; ТЗ, OQ, спека, методика → analyst; срез, issue, PR, тест, миграция → dev) — объявить одной строкой «Роль: admin; если не так, скажи» и вызвать `/role`. Иначе — один вопрос с четырьмя вариантами.
|
||||
- Процесс роли — в `~/.claude/roles/<r>.md`, не здесь. Сабагент роль не выбирает и `/role` не вызывает; процесс dev — `~/.claude/roles/dev.md`, если промпт велит.
|
||||
|
||||
## Маршрутизация
|
||||
- Сообщение `[claude-harness]` в начале сессии означает, что обвязка к проекту не подключена: действовать по его инструкции до всего остального. Скрипт `~/.claude/harness/adopt-project.sh` — единственная разрешённая запись на main помимо архивации OpenSpec.
|
||||
- Bounded-задача (флаг, фикс, мелкая правка): без OpenSpec change и без плана. Дизайн в чате, затем создать файл `.bounded` в корне worktree (разрешает правки в src/ без плана; файл в `.gitignore`, в коммит не попадает), TDD, коммит, PR.
|
||||
- Architectural-задача: brainstorming пишет спеку не в `docs/superpowers/specs/`, а в `openspec/changes/<id>/`. В поле **Spec** плана указывается этот путь.
|
||||
- Перед architectural brainstorming прочитать релевантные файлы в `openspec/specs/` и ARCHITECTURE.md, если есть.
|
||||
- Карта кода — codegraph (MCP). MCP-сервер запускается в корне основного checkout и по умолчанию отвечает только по его индексу; код ветки в worktree он не видит. Поэтому сразу после создания worktree выполнить в нём `codegraph init` (индекс локальный, в git не попадает), а во всех вызовах `codegraph_explore` передавать `projectPath` = абсолютный путь worktree. Перед изменением implementer вызывает `codegraph_explore` по своей области вместо grep/Read по файлам. Если ответ всё же пуст — один раз отметить в ledger и дальше использовать Read, не тратя раунды на переиндексацию.
|
||||
## Хуки и ветки
|
||||
- guard-main блокирует запись на main/master и detached HEAD: работа только в worktree на ветке. require-plan: правки в коде только при плане для ветки или файле `.bounded`.
|
||||
- Сообщение `[claude-harness]` при старте — инструкция обвязки, выполняется до всего остального.
|
||||
- Хуки не обходить (`echo >`, API вместо `tea`). Если хук мешает — сказать владельцу.
|
||||
- codegraph: после создания worktree — `codegraph init` в нём; в `codegraph_explore` — `projectPath` = абсолютный путь worktree.
|
||||
|
||||
## Исполнение
|
||||
- Работа только в worktree. Ветка называется по issue: `feat/<issue-id>-<slug>`.
|
||||
- Режим по умолчанию: subagent-driven-development. Контроллер SDD запускается как фоновый сабагент **на модели sonnet** (не на модели сессии) — сессия только ждёт его отчёт. Для планов до 5 задач — executing-plans (Native).
|
||||
- Модели исполнителей выбираются по SDD Model Selection: задача с готовым кодом в плане — haiku, интеграция — sonnet, ревью всей ветки — opus. Модель в dispatch указывать всегда.
|
||||
## Память
|
||||
- Состояние задачи живёт в трекере (Gitea issue, Jira), не в памяти.
|
||||
- В репозиторий направления (`<dir>/notes/`, память агента) пишутся уроки, решения владельца, рецепты. Коммит в той же ветке, что и работа.
|
||||
- В локальную auto-memory — только ловушки этой машины и обвязки и индекс со ссылками на `notes/` направлений.
|
||||
|
||||
## Завершение
|
||||
- finishing-a-development-branch создаёт PR в Gitea (`tea pulls create`) и ссылается на issue. Комментарии к PR обрабатываются через receiving-code-review: агент читает их из Gitea, правит в той же ветке, отвечает в PR. Merge — только по явной команде человека.
|
||||
- После merge по команде «закрывай» выполняется весь чеклист закрытия, без остановок между пунктами:
|
||||
1. `openspec archive <id>`, коммит и push архива прямо в main (единственное разрешённое исключение из правила «в main только через PR»: хук пропускает коммиты, затрагивающие только `openspec/`).
|
||||
2. Issue закрывается (`tea issues close <id>`, комментарий со ссылкой на PR) только если все её пункты выполнены. Если PR закрывает часть пунктов (типично для follow-up issue) — issue остаётся открытой, в неё пишется комментарий: какие маркеры закрыты, каким PR и коммитом.
|
||||
3. Удалить SDD workspace (`.superpowers/sdd/<plan>/`).
|
||||
4. Удалить ветку фичи на сервере и локально (`git push origin --delete`, `git branch -D`), удалить worktree (`git worktree remove`). Остальные worktree не трогать.
|
||||
5. Обновить основной checkout: если рабочее дерево там чистое — `git pull --ff-only`; если есть незакоммиченные изменения — не трогать, а перечислить их в итоговом сообщении.
|
||||
6. Все находки со статусом `deferred` (см. «Маркировка») собрать в одну issue Gitea `tea issues create` с заголовком `Follow-ups from #<issue-id>`: маркер, одна строка, откуда взялось. Если отложенного нет — issue не создавать.
|
||||
7. Итоговое сообщение: что сделано по пунктам 1–6, номер follow-up issue, что осталось человеку.
|
||||
Пункты, не применимые к bounded-задаче (archive, SDD workspace), пропускаются с пометкой в итоговом сообщении.
|
||||
Любой другой push в main — только через PR.
|
||||
- Список "Rulings I made" из финального сообщения копируется в описание PR.
|
||||
- Любое решение, принятое агентом без обсуждения с человеком — на этапе спеки, плана или исполнения — дописывается в `openspec/changes/<id>/design.md` в раздел `## Decisions made without discussion`: дата, решение, обоснование, что стоит ошибка. Ledger SDD удаляется, чат теряется; design.md архивируется вместе со спекой и остаётся в истории.
|
||||
|
||||
## Маркировка находок и решений
|
||||
- Каждая находка и каждое решение в ledger, отчётах, PR и design.md несёт маркер источника. Маркер присваивается один раз и не меняется при переносе между документами.
|
||||
- `T<n>.<k>` — k-я minor-находка ревьюера задачи n плана (`T4.1`, `T4.2`).
|
||||
- `C-n` / `I-n` / `M-n` — critical / important / minor из финального ревью всей ветки.
|
||||
- `R-n` — рулинг контроллера, сквозная нумерация в пределах фичи; рядом номер решения в design.md: `R-5 (design #33)`.
|
||||
- `O-n` — наблюдение контроллера вне ревью (неточные отчёты, инструменты, окружение).
|
||||
- `H-n` — замечание человека из PR, обработанное через receiving-code-review.
|
||||
- Каждая находка заканчивается одним из статусов: `fixed <commit>`, `deferred`, `rejected <причина>`. Находка без статуса в финальном отчёте — ошибка контроллера.
|
||||
- Финальный отчёт контроллера содержит таблицу всех находок с маркером и статусом; `deferred` попадают в описание PR и в follow-up issue при закрытии (чеклист, п. 6).
|
||||
## Маркировка
|
||||
- Каждая находка и каждое решение несёт маркер источника (`T<n>.<k>`, `C-n`/`I-n`/`M-n`, `R-n`, `O-n`, `H-n`, `RT-n`) и статус `fixed <commit>` / `deferred` / `rejected <причина>`. Маркер присваивается один раз. Подробности — в `~/.claude/roles/dev.md`.
|
||||
|
||||
## Стиль
|
||||
- Отчёты сабагентов, рулинги и ledger не сжимать. Caveman включается только по явной команде.
|
||||
- Отвечать по-русски. Отчёты сабагентов, рулинги и ledger не сжимать; caveman — только по явной команде.
|
||||
|
||||
17
hooks/role-hint.sh
Executable file
17
hooks/role-hint.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# SessionStart hook (все источники): если заголовок сессии начинается с имени роли, напомнить вызвать /role.
|
||||
# Ничего не блокирует: любой сбой → тихий exit 0. Заголовок приходит в поле session_title (claude -n "<роль> …", /rename).
|
||||
set -uo pipefail
|
||||
input=$(cat 2>/dev/null || true)
|
||||
[ -n "$input" ] || exit 0
|
||||
title=$(printf '%s' "$input" | python3 -c 'import sys,json
|
||||
try: print((json.load(sys.stdin) or {}).get("session_title") or "")
|
||||
except Exception: print("")' 2>/dev/null) || exit 0
|
||||
[ -n "$title" ] || exit 0
|
||||
# первое слово: нижний регистр, без ведущих пробелов и префикса алиаса cc-; только латинские буквы.
|
||||
# LC_ALL=C — чтобы BSD tr/sed работали побайтово и не спотыкались о кириллицу в заголовке.
|
||||
first=$(printf '%s' "$title" | LC_ALL=C tr '[:upper:]' '[:lower:]' | LC_ALL=C sed -E 's/^[[:space:]]*(cc-)?([a-z]*).*$/\2/')
|
||||
case "$first" in
|
||||
dev|admin|analyst|qa) echo "[claude-harness] Роль сессии: $first. Вызови /role $first до первого ответа." ;;
|
||||
esac
|
||||
exit 0
|
||||
@ -29,6 +29,14 @@
|
||||
"command": "\"$HOME/.claude/hooks/harness-check.sh\""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$HOME/.claude/hooks/role-hint.sh\""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
21
install.sh
21
install.sh
@ -24,6 +24,12 @@ check() {
|
||||
if [ -f "$CC/settings.json" ] && grep -q 'codegraph prompt-hook' "$CC/settings.json"; then
|
||||
echo " WARN codegraph prompt-hook в settings.json — рекомендуется отключить на время пилота"
|
||||
fi
|
||||
echo "== Установленная часть обвязки ($CC)"
|
||||
for f in CLAUDE.md hooks/guard-main.sh hooks/require-plan.sh hooks/harness-check.sh hooks/role-hint.sh \
|
||||
roles/dev.md roles/admin.md roles/analyst.md roles/qa.md skills/role/SKILL.md agents/briefing.md \
|
||||
harness/adopt-project.sh harness/new-project.sh harness/link-roles.sh skills/harness-doctor/SKILL.md; do
|
||||
[ -f "$CC/$f" ] && echo " ok $f" || { echo " MISS $f (bash install.sh)"; ok=1; }
|
||||
done
|
||||
return $ok
|
||||
}
|
||||
|
||||
@ -39,15 +45,21 @@ fi
|
||||
cp "$HERE/global/CLAUDE.md" "$CC/CLAUDE.md"
|
||||
|
||||
# 2. Хуки
|
||||
cp "$HERE/hooks/guard-main.sh" "$HERE/hooks/require-plan.sh" "$HERE/hooks/harness-check.sh" "$CC/hooks/"
|
||||
cp "$HERE/hooks/guard-main.sh" "$HERE/hooks/require-plan.sh" "$HERE/hooks/harness-check.sh" "$HERE/hooks/role-hint.sh" "$CC/hooks/"
|
||||
# 2a. Скрипты бутстрапа проекта — агент запускает их из сессии
|
||||
mkdir -p "$CC/harness"
|
||||
cp -R "$HERE/adopt-project.sh" "$HERE/new-project.sh" "$HERE/project-template" "$CC/harness/"
|
||||
rm -rf "$CC/harness/project-template"
|
||||
cp -R "$HERE/adopt-project.sh" "$HERE/new-project.sh" "$HERE/link-roles.sh" "$HERE/project-template" "$CC/harness/"
|
||||
chmod +x "$CC/harness/"*.sh
|
||||
# 2b. Skill диагностики
|
||||
mkdir -p "$CC/skills"
|
||||
rm -rf "$CC/skills/harness-doctor" && cp -R "$HERE/skills/harness-doctor" "$CC/skills/harness-doctor"
|
||||
chmod +x "$CC/skills/harness-doctor/scripts/"*.sh
|
||||
# 2c. Роли, skill /role, агент briefing
|
||||
mkdir -p "$CC/roles" "$CC/skills/role" "$CC/agents"
|
||||
cp "$HERE/roles/"*.md "$CC/roles/"
|
||||
cp "$HERE/skills/role/SKILL.md" "$CC/skills/role/SKILL.md"
|
||||
cp "$HERE/agents/briefing.md" "$CC/agents/briefing.md"
|
||||
chmod +x "$CC/hooks/"*.sh
|
||||
|
||||
# 3. caveman off by default
|
||||
@ -68,7 +80,8 @@ def ours(entry):
|
||||
pre[:] = [e for e in pre if not ours(e)]
|
||||
pre.extend(h["hooks"]["PreToolUse"])
|
||||
ss = hooks.setdefault("SessionStart", [])
|
||||
ss[:] = [e for e in ss if not any("harness-check.sh" in x.get("command","") for x in e.get("hooks",[]))]
|
||||
ours_ss = ("harness-check.sh", "role-hint.sh")
|
||||
ss[:] = [e for e in ss if not any(any(n in x.get("command","") for n in ours_ss) for x in e.get("hooks",[]))]
|
||||
ss.extend(h["hooks"]["SessionStart"])
|
||||
for ev in ("SessionStart","PreCompact"):
|
||||
if ev in hooks:
|
||||
@ -80,6 +93,6 @@ json.dump(s, open(sp,"w"), ensure_ascii=False, indent=2)
|
||||
print("settings.json: hooks merged")
|
||||
PY
|
||||
|
||||
echo "done: $CC/CLAUDE.md, $CC/skills/harness-doctor, $CC/hooks/{guard-main,require-plan,harness-check}.sh, $CC/harness/, ~/.config/caveman/config.json"
|
||||
echo "done: $CC/CLAUDE.md, $CC/skills/harness-doctor, $CC/hooks/{guard-main,require-plan,harness-check,role-hint}.sh, $CC/roles/, $CC/skills/role, $CC/agents/briefing.md, $CC/harness/, ~/.config/caveman/config.json"
|
||||
echo "Плагины Claude Code ставятся вручную: /plugin install superpowers@claude-plugins-official"
|
||||
check || true
|
||||
|
||||
59
link-roles.sh
Executable file
59
link-roles.sh
Executable file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# claude-harness: симлинки из <workspace>/.claude/ в репозитории направлений (роли и агенты; память агентов симлинком не работает, спека раздел 11).
|
||||
# Запуск: bash link-roles.sh [workspace] [--map <dir>=<role> ...]
|
||||
# Источник пар «каталог → роль»: таблица | Каталог | Репозиторий | Роль | в <workspace>/CLAUDE.md (роли admin|analyst|qa)
|
||||
# и/или пары --map. Идемпотентно. Обычные файлы на месте ссылок не трогает (ПРОПУЩЕН).
|
||||
set -euo pipefail
|
||||
WS="."; MAPS=()
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--map) [ $# -ge 2 ] || { echo "link-roles: --map требует <dir>=<role>" >&2; exit 2; }; MAPS+=("$2"); shift 2 ;;
|
||||
*) WS="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
cd "$WS"; WS="$(pwd)"
|
||||
pairs=()
|
||||
if [ -f CLAUDE.md ]; then
|
||||
while IFS= read -r line; do pairs+=("$line"); done < <(
|
||||
python3 - CLAUDE.md <<'PY'
|
||||
import sys
|
||||
for line in open(sys.argv[1], encoding="utf-8"):
|
||||
if not line.startswith("|"): continue
|
||||
cells = [c.strip() for c in line.strip().strip("|").split("|")]
|
||||
if len(cells) < 3: continue
|
||||
d, role = cells[0].rstrip("/"), cells[2]
|
||||
if role in ("admin", "analyst", "qa") and d and not d.startswith("-"):
|
||||
print(f"{d}={role}")
|
||||
PY
|
||||
)
|
||||
fi
|
||||
pairs+=("${MAPS[@]+"${MAPS[@]}"}")
|
||||
[ ${#pairs[@]} -gt 0 ] || { echo "link-roles: нет пар каталог=роль (таблица в CLAUDE.md или --map)"; exit 0; }
|
||||
mkdir -p .claude/roles .claude/agents
|
||||
|
||||
link() { # $1 = путь ссылки (относительно WS), $2 = цель (относительно каталога ссылки)
|
||||
local l="$1" t="$2"
|
||||
if [ -L "$l" ]; then
|
||||
if [ "$(readlink "$l")" = "$t" ]; then echo " есть $l"; return 0; fi
|
||||
rm "$l"
|
||||
elif [ -e "$l" ]; then
|
||||
echo " ПРОПУЩЕН $l — обычный файл/каталог, не симлинк; убери его вручную, если нужна ссылка"; return 0
|
||||
fi
|
||||
ln -s "$t" "$l"; echo " создан $l → $t"
|
||||
}
|
||||
|
||||
for p in "${pairs[@]}"; do
|
||||
d=${p%%=*}; r=${p#*=}; d=${d%/}
|
||||
echo "$d ($r)"
|
||||
[ -d "$d" ] || { echo " WARN каталог $d не найден"; continue; }
|
||||
if [ -f "$d/.claude/roles/$r.md" ]; then
|
||||
link ".claude/roles/$r.md" "../../$d/.claude/roles/$r.md"
|
||||
else
|
||||
echo " WARN нет $d/.claude/roles/$r.md — проектный файл роли не слинкован"
|
||||
fi
|
||||
for a in "$d"/.claude/agents/*.md; do
|
||||
[ -f "$a" ] || continue
|
||||
n=$(basename "$a" .md)
|
||||
link ".claude/agents/$n.md" "../../$d/.claude/agents/$n.md"
|
||||
done
|
||||
done
|
||||
@ -3,6 +3,39 @@
|
||||
# Запуск: bash new-project.sh <папка> [ssh-url репозитория в Gitea]
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
# Режим workspace: bash new-project.sh --workspace <папка> [--role admin] [--role qa] [--role analyst]
|
||||
if [ "${1:-}" = "--workspace" ]; then
|
||||
DIR="${2:?usage: new-project.sh --workspace <dir> [--role <r> ...]}"; shift 2
|
||||
ROLES=()
|
||||
while [ $# -gt 0 ]; do case "$1" in --role) ROLES+=("$2"); shift 2 ;; *) echo "ERR: неизвестный аргумент $1"; exit 1 ;; esac; done
|
||||
mkdir -p "$DIR" && cd "$DIR"; NAME="$(basename "$(pwd)")"
|
||||
[ -f CLAUDE.md ] || sed "s/{{PROJECT_NAME}}/$NAME/g" "$HERE/project-template/workspace/CLAUDE.md" > CLAUDE.md
|
||||
mkdir -p .claude/roles
|
||||
[ -e .claude/roles/dev.md ] || sed "s/{{PROJECT_NAME}}/$NAME/g" "$HERE/project-template/workspace/roles/dev.md" > .claude/roles/dev.md
|
||||
for r in "${ROLES[@]+"${ROLES[@]}"}"; do
|
||||
case "$r" in admin|analyst|qa) ;; *) echo "ERR: роль $r не из admin|analyst|qa"; exit 1 ;; esac
|
||||
d="$r"; [ "$r" = analyst ] && d="analysis"
|
||||
if [ -d "$d/.git" ]; then echo "$d/: уже git, не трогаю"; continue; fi
|
||||
mkdir -p "$d/.claude/roles" "$d/notes"
|
||||
sed -e "s/{{PROJECT_NAME}}/$NAME/g" -e "s/{{ROLE}}/$r/g" -e "s#{{DIR}}#$d#g" "$HERE/project-template/role-repo/CLAUDE.md" > "$d/CLAUDE.md"
|
||||
sed -e "s/{{PROJECT_NAME}}/$NAME/g" -e "s/{{ROLE}}/$r/g" -e "s#{{DIR}}#$d#g" "$HERE/project-template/role-repo/ROLE.md" > "$d/.claude/roles/$r.md"
|
||||
cp "$HERE/project-template/role-repo/notes/README.md" "$d/notes/README.md"
|
||||
echo "$r" > "$d/.claude/role"
|
||||
git -C "$d" init -q -b main && git -C "$d" add -A && git -C "$d" commit -qm "chore: репозиторий направления $r (claude-harness)"
|
||||
# строка таблицы: заменить заготовку {{owner/repo-<r>}} на имя каталога, если заготовка есть
|
||||
sed -i.bak "s#| $d/ | {{owner/repo-[a-z]*}} | $r |#| $d/ | <owner>/$NAME-$d | $r |#" CLAUDE.md && rm -f CLAUDE.md.bak
|
||||
echo "$d/: репозиторий направления $r создан"
|
||||
done
|
||||
bash "$HERE/link-roles.sh" "$(pwd)"
|
||||
cat <<MSG
|
||||
|
||||
Workspace $NAME готов. Дальше:
|
||||
1. Заполни {{...}} в CLAUDE.md и в .claude/roles/dev.md; в репозиториях направлений — .claude/roles/<r>.md.
|
||||
2. Заведи репозитории направлений в Gitea и добавь remote (tea repos create …; git remote add origin …).
|
||||
3. cd $(pwd) && claude -n "dev …"
|
||||
MSG
|
||||
exit 0
|
||||
fi
|
||||
DIR="${1:?usage: new-project.sh <dir> [git-remote-url]}"
|
||||
REMOTE="${2:-}"
|
||||
NAME="$(basename "$DIR")"
|
||||
|
||||
5
project-template/role-repo/CLAUDE.md
Normal file
5
project-template/role-repo/CLAUDE.md
Normal file
@ -0,0 +1,5 @@
|
||||
# {{DIR}} — репозиторий направления {{ROLE}}
|
||||
|
||||
Кухня роли `{{ROLE}}` проекта {{PROJECT_NAME}}: рабочие материалы, `notes/` (уроки, решения владельца, рецепты),
|
||||
`.claude/roles/{{ROLE}}.md` (проектный слой роли), `.claude/agents/` (агенты роли), память агентов — `notes/agent-memory.md` (спека 6.5; коммитится).
|
||||
Сессии запускаются из корня workspace; этот файл загружается при чтении файлов отсюда. Публикация в витрину `docs/` — через PR.
|
||||
15
project-template/role-repo/ROLE.md
Normal file
15
project-template/role-repo/ROLE.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Роль: {{ROLE}} — проектный слой {{PROJECT_NAME}}
|
||||
Эта роль заменяет предыдущую роль сессии целиком.
|
||||
|
||||
## Процесс
|
||||
- {{Инструменты роли в этом проекте, трекер, где лежат артефакты.}}
|
||||
|
||||
## Читать на старте
|
||||
- {{DIR}}/README.md
|
||||
- brief: {{DIR}}/notes/
|
||||
|
||||
## Не делать
|
||||
- {{Операционные ограничения проекта для этой роли.}}
|
||||
|
||||
## Делегировать
|
||||
- {{агент → для чего, если есть}}
|
||||
3
project-template/role-repo/notes/README.md
Normal file
3
project-template/role-repo/notes/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# notes/
|
||||
|
||||
Уроки, решения владельца (с датой) и рецепты роли. Коммитятся в той же ветке, что и работа. Состояние задач сюда не пишется — оно в трекере.
|
||||
25
project-template/workspace/CLAUDE.md
Normal file
25
project-template/workspace/CLAUDE.md
Normal file
@ -0,0 +1,25 @@
|
||||
# {{PROJECT_NAME}}
|
||||
|
||||
## Что это
|
||||
{{Одним абзацем: что за проект, для кого, что сейчас делается.}}
|
||||
|
||||
## Репозитории
|
||||
Сессии запускаются из этого каталога. Роль сессии выбирается через `/role <r>` (подсказка хука по заголовку сессии `claude -n "<роль> …"`); правила роли — в `~/.claude/roles/<r>.md` (общие) и `.claude/roles/<r>.md` (проектные). Ссылки на проектные файлы ролей и агентов создаёт `~/.claude/harness/link-roles.sh` по этой таблице.
|
||||
|
||||
| Каталог | Репозиторий | Роль |
|
||||
|---|---|---|
|
||||
| {{code-repo}}/ | {{owner/repo}} | dev |
|
||||
| admin/ | {{owner/repo-admin}} | admin |
|
||||
| analysis/ | {{owner/repo-analysis}} | analyst |
|
||||
| qa/ | {{owner/repo-qa}} | qa |
|
||||
| docs/ | {{owner/repo-docs}} | — |
|
||||
|
||||
Правило: `docs/` публикует, репозиторий направления работает. Роль-владелец разделов `docs/`: {{context/, model/ — analyst; dev/, contracts/ — dev; user/ — qa; source/ — никто}}.
|
||||
|
||||
## Источники истины
|
||||
- {{Модель данных, доменные правила, ТЗ — пути.}}
|
||||
- Задачи: {{Gitea <owner> для кода; Jira <проект> для заказчика}}.
|
||||
|
||||
## Ограничения для всех ролей
|
||||
- Хуки guard-main и require-plan действуют в каждом git-репозитории здесь; workspace сам не git.
|
||||
- {{Стенд, порты, что нельзя трогать.}}
|
||||
16
project-template/workspace/roles/dev.md
Normal file
16
project-template/workspace/roles/dev.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Роль: dev — разработчик (проектный слой {{PROJECT_NAME}})
|
||||
Эта роль заменяет предыдущую роль сессии целиком.
|
||||
|
||||
## Процесс
|
||||
- {{Репозитории кода и их CLAUDE.md; worktree `.wt/<задача>/` и что в него линкуется; `.bounded`; venv/сборка; стенд.}}
|
||||
- {{Источники истины для рулингов: модель данных, доменные правила.}}
|
||||
|
||||
## Читать на старте
|
||||
- {{путь к инвариантам / ARCHITECTURE.md}}
|
||||
- brief: `tea issues ls --state open`
|
||||
|
||||
## Не делать
|
||||
- {{Инварианты проекта: что не мутируется, кто единственный писатель, чего не хранить.}}
|
||||
|
||||
## Делегировать
|
||||
- SDD-контроллер → исполнение плана.
|
||||
25
roles/admin.md
Normal file
25
roles/admin.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Роль: admin — руководитель/администратор проекта
|
||||
Эта роль заменяет предыдущую роль сессии целиком.
|
||||
|
||||
## Процесс
|
||||
- Задачи ведутся в трекере заказчика (Jira) или в репозитории направления (`admin/`). Gitea-issue и OpenSpec change для задач этой роли не заводятся.
|
||||
- Вход в задачу — superpowers:brainstorming; по умолчанию задачи bounded: дизайн в чате, затем работа. Писать планы только для многошаговых работ со скриптами.
|
||||
- Код (скрипты выгрузок, генераторы CSV, просмотрщики) — только в репозитории направления, TDD, коммит на ветке, PR по правилам репозитория. Код в репозиториях разработки не трогать.
|
||||
- Тексты для внешних людей (заказчик, команда, Jira) самодостаточны: без внутренних маркеров (`T1.1`, `R-5`), номеров PR, путей к файлам и упоминаний агентов. Артефакты для передачи кладутся в `deliverables/` репозитория направления.
|
||||
- Уроки, решения владельца и рецепты роли — в `admin/notes/`, коммит в той же ветке, что и работа. Состояние задач — в Jira или в CSV репозитория, не в auto-memory.
|
||||
|
||||
## Читать на старте
|
||||
- `CLAUDE.md` workspace — таблица репозиториев и источники истины.
|
||||
- `admin/README.md` — что лежит в репозитории направления и как им пользоваться.
|
||||
- brief: `admin/notes/` — уроки и решения владельца.
|
||||
- brief: последний каталог `admin/jira-sync/runs/<дата>/` — что делалось в прошлый раз.
|
||||
|
||||
## Не делать
|
||||
- Не менять код репозиториев разработки и не заводить им issue — передать владельцу или `/role dev`.
|
||||
- Не выполнять записывающие операции против реальной Jira, если проектный файл роли это не разрешает явно.
|
||||
- Не подключать процесс superpowers→Gitea→OpenSpec к задачам этой роли.
|
||||
- Не писать в auto-memory состояние спринта, задач и выгрузок.
|
||||
|
||||
## Делегировать
|
||||
- `briefing` → выжимка строк `brief:` при `/role admin`.
|
||||
- `jira-admin` (проектный агент, если есть) → сухие прогоны discover/plan, перегенерация CSV, сверка Ганта, черновики описаний задач.
|
||||
23
roles/analyst.md
Normal file
23
roles/analyst.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Роль: analyst — аналитик требований
|
||||
Эта роль заменяет предыдущую роль сессии целиком.
|
||||
|
||||
## Процесс
|
||||
- Работа с требованиями: спеки, открытые вопросы (OQ), решения владельца, сверка ТЗ с кодом и с тем, что показывает стенд.
|
||||
- Код не пишется и не правится. Если сверка показала расхождение кода с ТЗ — это находка для владельца, не правка.
|
||||
- Публикация в витрину `docs/` — через PR в репозиторий витрины; черновики и протоколы живут в репозитории направления (`analysis/`).
|
||||
- Противоречия между документами не решаются молча: фиксируются как открытый вопрос с вариантами, решение принимает владелец и записывается в `analysis/notes/decisions.md` с датой.
|
||||
- Вход в задачу — superpowers:brainstorming; задачи по умолчанию bounded.
|
||||
|
||||
## Читать на старте
|
||||
- `CLAUDE.md` workspace — таблица репозиториев и источники истины.
|
||||
- Проектный файл роли — какие документы считаются источниками истины и как оформляются OQ.
|
||||
- brief: `analysis/notes/decisions.md` — решения владельца.
|
||||
- brief: список открытых OQ (путь в проектном файле роли).
|
||||
|
||||
## Не делать
|
||||
- Не писать и не править код, миграции, тесты.
|
||||
- Не менять DBML, модель данных и доменные правила без решения владельца, записанного в `decisions.md`.
|
||||
- Не переписывать исходные документы заказчика (`docs/source/`), только ссылаться на них.
|
||||
|
||||
## Делегировать
|
||||
- `briefing` → выжимка строк `brief:` при `/role analyst`.
|
||||
72
roles/dev.md
Normal file
72
roles/dev.md
Normal file
@ -0,0 +1,72 @@
|
||||
# Роль: dev — разработчик
|
||||
Эта роль заменяет предыдущую роль сессии целиком.
|
||||
|
||||
## Процесс
|
||||
|
||||
### Инструменты и их роли
|
||||
- superpowers ведёт процесс внутри одной фичи: brainstorming → writing-plans → исполнение → ревью → finishing-a-development-branch.
|
||||
- Gitea (CLI `tea`) хранит задачи между сессиями. Одна фича = одна issue = одна ветка = один PR. Issue заводит агент (`tea issues create`) из формулировки цели, до brainstorming. Порядок: сначала worktree на временной ветке (хук не даёт писать на main), затем issue, затем ветка переименовывается в `feat/<issue-id>-<slug>`.
|
||||
- OpenSpec хранит требования. `openspec/specs/` — текущее поведение системы, `openspec/changes/<id>/` — дельта текущей фичи. Спека — единственный источник истины при рулингах.
|
||||
- Ledger superpowers (`.superpowers/sdd/`) — прогресс только внутри текущего плана. Задачи плана в Gitea не дублировать.
|
||||
- Карта кода — codegraph (MCP). Сервер отвечает по индексу корня основного checkout, код ветки в worktree он не видит: после создания worktree выполнить в нём `codegraph init`, во всех вызовах `codegraph_explore` передавать `projectPath` = абсолютный путь worktree. Implementer перед изменением вызывает `codegraph_explore` по своей области вместо grep/Read. Пустой ответ — один раз отметить в ledger и дальше использовать Read. codegraph обязателен на период замера (решение владельца 11.10.2026); число вызовов считает `/harness-retro`.
|
||||
|
||||
### Маршрутизация
|
||||
- Сообщение `[claude-harness] В проекте … не подключена обвязка` в начале сессии: действовать по его инструкции до всего остального. Скрипт `~/.claude/harness/adopt-project.sh` — единственная разрешённая запись на main.
|
||||
- Bounded-задача (флаг, фикс, мелкая правка): без OpenSpec change и без плана. Дизайн в чате, затем файл `.bounded` в корне worktree (разрешает правки в коде без плана; в `.gitignore`), TDD, коммит, PR. Если меняется поведение API или схемы — спека в `openspec/specs/` обновляется в том же PR. Перед PR — verification-before-completion.
|
||||
- Architectural-задача: brainstorming пишет спеку не в `docs/superpowers/specs/`, а в `openspec/changes/<id>/`. В поле **Spec** плана указывается этот путь. Перед architectural brainstorming прочитать релевантные файлы в `openspec/specs/` и ARCHITECTURE.md, если есть.
|
||||
|
||||
### Исполнение
|
||||
- Работа только в worktree. Ветка называется по issue: `feat/<issue-id>-<slug>`. Пути worktree в планах и брифах — абсолютные.
|
||||
- Пока работает фоновый SDD-контроллер, в главной сессии не делать `cd` в основной checkout: cwd оболочки общий, контроллер и implementer-ы получат `BLOCKED: shell write on main` и остановятся.
|
||||
- Режим по умолчанию: subagent-driven-development. Контроллер SDD запускается фоновым сабагентом **на модели sonnet** (не на модели сессии); сессия только ждёт его отчёт. Для планов до 5 задач — executing-plans (Native). Режим не спрашивать, он задан здесь.
|
||||
- Модели исполнителей по SDD Model Selection: задача с готовым кодом в плане — haiku, интеграция — sonnet, ревью всей ветки — opus. Модель в dispatch указывать всегда.
|
||||
- Ревьюер задачи проверяет trailer коммита (`Co-Authored-By`, как указано в плане); чужой trailer — находка `T<n>.<k>`, переписывать историю нельзя (force-push запрещён).
|
||||
- План содержит раздел «Предусловия окружения» (стенд погашен, порты свободны, ключи не в окружении), контроллер проверяет его один раз перед первой задачей.
|
||||
|
||||
### Завершение
|
||||
- finishing-a-development-branch создаёт PR в Gitea (`tea pulls create`) со ссылкой на issue. Комментарии к PR — через receiving-code-review: читать из Gitea, править в той же ветке, отвечать в PR. Merge — только по явной команде человека.
|
||||
- После merge по команде «закрывай» выполняется весь чеклист без остановок:
|
||||
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 и коммитом.
|
||||
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 запрещены.
|
||||
5. Обновить основной checkout: дерево чистое — `git pull --ff-only`; есть незакоммиченные изменения — не трогать, перечислить их в итоговом сообщении.
|
||||
6. Все находки со статусом `deferred` собрать в одну issue `Follow-ups from #<issue-id>`: маркер, одна строка, откуда взялось. Нет отложенного — issue не создавать.
|
||||
7. Файл auto-memory задачи, если был, свернуть в строку `registry-closed.md` (issue, PR, follow-up, дата); уроки из него перенести в `notes/` направления.
|
||||
8. Итоговое сообщение: что сделано по пунктам 1–7, номер follow-up issue, что осталось человеку.
|
||||
Неприменимые к bounded-задаче пункты (archive, SDD workspace) пропускаются с пометкой.
|
||||
- Список «Rulings I made» из финального сообщения копируется в описание PR.
|
||||
- Любое решение, принятое без обсуждения с человеком — на этапе спеки, плана или исполнения — дописывается в `openspec/changes/<id>/design.md`, раздел `## Decisions made without discussion`: дата, решение, обоснование, цена ошибки. Ledger удаляется, чат теряется; design.md архивируется со спекой.
|
||||
|
||||
### Маркировка находок и решений
|
||||
- Каждая находка и каждое решение в ledger, отчётах, PR и design.md несёт маркер источника; маркер присваивается один раз и не меняется при переносе.
|
||||
- `T<n>.<k>` — k-я minor-находка ревьюера задачи n плана.
|
||||
- `C-n` / `I-n` / `M-n` — critical / important / minor из финального ревью ветки.
|
||||
- `R-n` — рулинг контроллера, сквозная нумерация в пределах фичи; рядом номер решения в design.md: `R-5 (design #33)`.
|
||||
- `O-n` — наблюдение контроллера вне ревью (неточные отчёты, инструменты, окружение).
|
||||
- `H-n` — замечание человека из PR, обработанное через receiving-code-review.
|
||||
- `RT-n` — находка ретроспективы обвязки.
|
||||
- Каждая находка заканчивается статусом `fixed <commit>`, `deferred` или `rejected <причина>`. Находка без статуса в финальном отчёте — ошибка контроллера.
|
||||
- Финальный отчёт контроллера содержит таблицу всех находок с маркером и статусом; `deferred` попадают в описание PR и в follow-up issue (чеклист, п. 6).
|
||||
|
||||
### Стиль
|
||||
- Отчёты сабагентов, рулинги и ledger не сжимать. Caveman — только по явной команде.
|
||||
|
||||
## Читать на старте
|
||||
- `tea issues ls` — открытые задачи и follow-up.
|
||||
- `openspec list` — активные change.
|
||||
- `docs/superpowers/plans/` в worktree — незавершённый план, если есть; ledger `.superpowers/sdd/`.
|
||||
- brief: `git log --oneline -15` по каждому репозиторию из таблицы workspace с ролью dev.
|
||||
- brief: `tea issues ls --state open` — активные задачи и follow-up issues.
|
||||
|
||||
## Не делать
|
||||
- Не писать на main и на detached HEAD; не обходить хуки (`echo >`, API Gitea вместо `tea`, правка `settings.json` ради «чтобы прошло»). Если хук мешает — сказать владельцу.
|
||||
- Не делать merge PR и force-push.
|
||||
- Не вести задачи Jira и документы заказчика — это роль admin (`/role admin`).
|
||||
- Не прогонять сценарии на стенде со сверкой макетов — это роль qa.
|
||||
- Не хранить состояние задачи в auto-memory: оно в issue и PR.
|
||||
|
||||
## Делегировать
|
||||
- SDD-контроллер (superpowers, фоновый сабагент на sonnet) → исполнение плана; implementer-ы haiku/sonnet, финальное ревью opus.
|
||||
- В промпт SDD-контроллера и ревьюера ветки включать указание: «прочитай `~/.claude/roles/dev.md` (Исполнение, Завершение, Маркировка) и передавай его своим сабагентам» — сабагенты получают `~/.claude/CLAUDE.md`, но не вывод `/role`.
|
||||
- `briefing` → выжимка строк `brief:` при `/role dev`.
|
||||
24
roles/qa.md
Normal file
24
roles/qa.md
Normal file
@ -0,0 +1,24 @@
|
||||
# Роль: qa — тестирование
|
||||
Эта роль заменяет предыдущую роль сессии целиком.
|
||||
|
||||
## Процесс
|
||||
- Стенд, прогоны сценариев, сверка каждого экрана с макетом, гайды и инструкции пользователя.
|
||||
- Код репозиториев разработки не меняется. Запись только в репозиторий направления (`qa/`): сценарии, журналы прогонов `qa/runs/<дата>/`, скриншоты, находки.
|
||||
- Находки — списком со скриншотами и шагами воспроизведения, по одной на расхождение; issue в кодовый репозиторий заводит dev по решению владельца.
|
||||
- Перед прогоном проверить предусловия стенда из проектного файла роли (порты, токен, данные); если стенд занят другой сессией — не гасить, сказать владельцу.
|
||||
- Уроки и ловушки стенда — в `qa/notes/`, коммит в той же ветке, что и работа.
|
||||
|
||||
## Читать на старте
|
||||
- `CLAUDE.md` workspace — таблица репозиториев.
|
||||
- Проектный файл роли — стенд, Playwright, макеты, чек-лист UI.
|
||||
- brief: `qa/notes/` — ловушки стенда и рецепты.
|
||||
- brief: последний каталог `qa/runs/<дата>/` — что проверялось в прошлый раз и контрольные цифры.
|
||||
|
||||
## Не делать
|
||||
- Не менять код, миграции, конфиги стенда.
|
||||
- Не гасить и не перезапускать стенд и его сервисы — это делает владелец.
|
||||
- Не заводить issue в кодовые репозитории без решения владельца.
|
||||
|
||||
## Делегировать
|
||||
- `briefing` → выжимка строк `brief:` при `/role qa`.
|
||||
- `qa-runner` (проектный агент, если есть) → прогон сценария на стенде, сверка экранов с PNG макета, список расхождений в `qa/runs/<дата>/`.
|
||||
@ -25,6 +25,8 @@ description: Диагностика обвязки Claude Code (claude-harness)
|
||||
- `MISS`/`WARN hook …` → `bash <пакет>/install.sh`; «не зарегистрирован» — settings.json правился руками, тот же install.sh сольёт заново.
|
||||
- `WARN в окружении есть *_API_KEY` → ключ виден всем сабагентам и делает интеграционные тесты платными при каждом `npm test`. Убрать из профиля shell, передавать только в строке запуска. Это находка для пользователя, агент сам профиль не правит.
|
||||
- Глобальный CLAUDE.md без «маркировка: да» или «projectPath: да» → старая версия, `install.sh`.
|
||||
- `MISS roles/<r>.md`, `MISS skill role`, `MISS agent briefing`, `MISS link-roles.sh` → `bash <пакет>/install.sh` (версия ≥ 0.3.0).
|
||||
- `WARN глобальный CLAUDE.md > 3 КБ` → в `~/.claude/CLAUDE.md` дописано лишнее; процесс роли живёт в `~/.claude/roles/<r>.md`, перенести туда.
|
||||
|
||||
**Самотест хуков** — любое `WARN … (ожидалось N)` означает, что хук установлен, но не делает того, что должен. Причины по убыванию частоты: правлен руками; `python3` не в PATH сессии; settings.json зовёт другой путь. Чинится `install.sh`; если и после него WARN — приложить строку к находке, это дефект пакета.
|
||||
|
||||
@ -38,9 +40,11 @@ description: Диагностика обвязки Claude Code (claude-harness)
|
||||
- «SDD workspace: …» при отсутствии активной фичи → хвост незакрытой фичи; проверить чеклист закрытия.
|
||||
- `WARN .bounded не в .gitignore` → добавить строку, иначе уедет в main.
|
||||
- `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`.
|
||||
- `WARN индекс auto-memory > 3 КБ` → индекс снова стал журналом состояния; свернуть закрытое в `registry-closed.md`, уроки — в `notes/` направления (политика памяти в глобальном CLAUDE.md).
|
||||
|
||||
## Чинится само (без вопроса)
|
||||
`install.sh`, `codegraph init` в worktree, `.claude/code-paths`, строка в `.gitignore` на ветке. Всё остальное — находка пользователю: профиль shell, плагины, токены, удаление чего-либо.
|
||||
`install.sh`, `link-roles.sh` в workspace, `codegraph init` в worktree, `.claude/code-paths`, строка в `.gitignore` на ветке. Всё остальное — находка пользователю: профиль shell, плагины, токены, удаление чего-либо.
|
||||
|
||||
## Чего не делать
|
||||
Не «улучшать» хуки по месту: их источник — пакет claude-harness, правка идёт туда и ставится install.sh. Не отключать хук ради того, чтобы «прошло».
|
||||
|
||||
@ -16,13 +16,19 @@ if [ -f "$CC/plugins/installed_plugins.json" ]; then
|
||||
for p in beads template-bridge; do grep -q "\"$p@" "$CC/plugins/installed_plugins.json" && warn "plugin $p установлен (конфликт)"; done
|
||||
else warn "нет $CC/plugins/installed_plugins.json"; fi
|
||||
[ -f "$CC/CLAUDE.md" ] && ok "глобальный CLAUDE.md ($(grep -c . "$CC/CLAUDE.md") строк; маркировка: $(grep -q 'R-n' "$CC/CLAUDE.md" && echo да || echo нет); codegraph projectPath: $(grep -q projectPath "$CC/CLAUDE.md" && echo да || echo нет))" || miss "глобальный CLAUDE.md"
|
||||
for h in guard-main require-plan harness-check; do
|
||||
for h in guard-main require-plan harness-check role-hint; do
|
||||
f="$CC/hooks/$h.sh"
|
||||
if [ -x "$f" ]; then
|
||||
grep -q "$h.sh" "$CC/settings.json" 2>/dev/null && ok "hook $h.sh установлен и зарегистрирован" || warn "hook $h.sh есть, но не зарегистрирован в settings.json"
|
||||
else miss "hook $h.sh"; fi
|
||||
done
|
||||
[ -x "$CC/harness/adopt-project.sh" ] && ok "~/.claude/harness/ (adopt/new-project)" || miss "~/.claude/harness/adopt-project.sh"
|
||||
[ -x "$CC/harness/link-roles.sh" ] && ok "~/.claude/harness/link-roles.sh" || miss "~/.claude/harness/link-roles.sh"
|
||||
rmiss=(); for r in dev admin analyst qa; do [ -f "$CC/roles/$r.md" ] || rmiss+=("$r"); done
|
||||
if [ ${#rmiss[@]} -eq 0 ]; then ok "roles/ (dev admin analyst qa)"; else for r in "${rmiss[@]}"; do miss "roles/$r.md"; done; fi
|
||||
[ -f "$CC/skills/role/SKILL.md" ] && ok "skill role" || miss "skill role (~/.claude/skills/role/SKILL.md)"
|
||||
[ -f "$CC/agents/briefing.md" ] && ok "agent briefing" || miss "agent briefing (~/.claude/agents/briefing.md)"
|
||||
if [ -f "$CC/CLAUDE.md" ]; then n=$(wc -c < "$CC/CLAUDE.md" | tr -d ' '); [ "$n" -le 3072 ] && ok "глобальный CLAUDE.md $n Б (≤ 3 КБ)" || warn "глобальный CLAUDE.md $n Б > 3 КБ"; fi
|
||||
[ -f "$HOME/.config/caveman/config.json" ] && ok "caveman config: $(tr -d ' \n' < "$HOME/.config/caveman/config.json" | cut -c1-40)" || warn "нет ~/.config/caveman/config.json"
|
||||
grep -q 'codegraph prompt-hook' "$CC/settings.json" 2>/dev/null && warn "codegraph prompt-hook в settings.json"
|
||||
env | grep -E '^(AITUNNEL|OPENAI|GROQ|ANTHROPIC)_API_KEY=' | cut -d= -f1 | while read -r k; do warn "в окружении есть $k — виден всем агентам, тесты с ключом платные"; done
|
||||
@ -45,6 +51,12 @@ if [ -x "$CC/hooks/require-plan.sh" ]; then
|
||||
hk require-plan "{\"cwd\":\"$T\",\"tool_input\":{\"file_path\":\"$T/api/src/a.py\"}}" 2 "api/src/ без плана"
|
||||
touch "$T/.bounded"; hk require-plan "{\"cwd\":\"$T\",\"tool_input\":{\"file_path\":\"$T/src/a.ts\"}}" 0 "src/ с .bounded"
|
||||
fi
|
||||
if [ -x "$CC/hooks/role-hint.sh" ]; then
|
||||
o=$(printf '{"session_title":"admin: спринт"}' | bash "$CC/hooks/role-hint.sh" 2>/dev/null)
|
||||
case "$o" in *"Роль сессии: admin"*) ok "role-hint: заголовок admin → подсказка" ;; *) warn "role-hint: заголовок admin → '$o' (ожидалась подсказка)" ;; esac
|
||||
o=$(printf '{"cwd":"/tmp","source":"resume"}' | bash "$CC/hooks/role-hint.sh" 2>/dev/null)
|
||||
[ -z "$o" ] && ok "role-hint: без заголовка → тишина" || warn "role-hint: без заголовка → '$o' (ожидалась тишина)"
|
||||
fi
|
||||
rm -rf "$T"
|
||||
|
||||
echo "## Проект: $(cd "$DIR" 2>/dev/null && pwd)"
|
||||
@ -74,4 +86,23 @@ if root=$(git -C "$DIR" rev-parse --show-toplevel 2>/dev/null); then
|
||||
info "remote: $(git -C "$root" remote get-url origin)"
|
||||
(cd "$root" && tea issues ls --limit 5 2>/dev/null | sed 's/^/ /') || warn "tea не отвечает (логин/токен?)"
|
||||
fi
|
||||
else info "не git-репозиторий"; fi
|
||||
elif [ -f "$DIR/CLAUDE.md" ] && grep -Eq '^\|[[:space:]]*Каталог[[:space:]]*\|[[:space:]]*Репозиторий[[:space:]]*\|[[:space:]]*Роль[[:space:]]*\|' "$DIR/CLAUDE.md"; then
|
||||
ws=$(cd "$DIR" && pwd)
|
||||
info "workspace (не git): $ws"
|
||||
n=$(wc -c < "$ws/CLAUDE.md" | tr -d ' '); [ "$n" -le 5120 ] && ok "CLAUDE.md workspace $n Б (≤ 5 КБ)" || warn "CLAUDE.md workspace $n Б > 5 КБ"
|
||||
for kind in roles agents; do
|
||||
for l in "$ws/.claude/$kind"/*; do
|
||||
[ -e "$l" ] || [ -L "$l" ] || continue
|
||||
name="$kind/$(basename "$l")"
|
||||
if [ -L "$l" ]; then [ -e "$l" ] && ok "$name → $(readlink "$l")" || warn "$name — битая ссылка на $(readlink "$l")"
|
||||
else info "$name — обычный файл (не ссылка)"; fi
|
||||
done
|
||||
done
|
||||
grep -E '^\|' "$ws/CLAUDE.md" | awk -F'|' '{gsub(/ /,"",$4); gsub(/^ +| +$/,"",$2); if (($4=="admin"||$4=="analyst"||$4=="qa") && $2!="" && $2 !~ /^-/) print $2, $4}' | while read -r d r; do
|
||||
[ -f "$ws/${d%/}/.claude/roles/$r.md" ] || warn "нет ${d%/}/.claude/roles/$r.md — запусти link-roles.sh после создания"
|
||||
done
|
||||
# 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"
|
||||
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
|
||||
[ -x "$CC/harness/link-roles.sh" ] && info "обновить ссылки: bash $CC/harness/link-roles.sh $ws"
|
||||
else info "не git-репозиторий и не workspace (нет таблицы репозиториев в CLAUDE.md)"; fi
|
||||
|
||||
42
skills/role/SKILL.md
Normal file
42
skills/role/SKILL.md
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
name: role
|
||||
description: Переключить сессию в роль dev | admin | analyst | qa — загрузить общий и проектный файлы роли, её память и выжимку briefing. Вызывать по подсказке хука «Роль сессии», по правилу выбора роли из глобального CLAUDE.md или по просьбе владельца.
|
||||
argument-hint: dev|admin|analyst|qa
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# /role — роль сессии
|
||||
|
||||
Аргумент: `$0` — имя роли. Workspace — текущий каталог сессии (cwd), не git-корень.
|
||||
|
||||
## Без аргумента
|
||||
Если `$0` пуст: напечатать текущую роль (та, чей файл читался последним в этой сессии; если ни одного — «роль не выбрана»)
|
||||
и список доступных: для каждой из `dev admin analyst qa` — есть ли `~/.claude/roles/<r>.md` и `./.claude/roles/<r>.md`.
|
||||
Остановиться.
|
||||
|
||||
## С аргументом
|
||||
Если `$0` не из `dev admin analyst qa` — одной строкой сказать, какие роли есть, и остановиться.
|
||||
|
||||
1. Прочитать `~/.claude/roles/$0.md` (общий слой). Прочитать `./.claude/roles/$0.md` (проектный слой); если файла нет —
|
||||
сказать одной строкой «проектного файла роли $0 нет, работаю по общему» и продолжить. При конфликте правил побеждает проектный слой.
|
||||
Новая роль заменяет предыдущую целиком: правила прежней роли больше не действуют.
|
||||
2. Открыть индекс auto-memory (путь указан в системном промпте, раздел Memory, файл `MEMORY.md`). Найти раздел роли
|
||||
(`## Разработка` для dev, `## Администрирование` для admin, `## Аналитика` для analyst, `## QA` для qa) и прочитать файлы,
|
||||
перечисленные в нём. Раздел `## Общее` читается всегда. Если раздела нет — сказать одной строкой и продолжить.
|
||||
3. Из раздела «Читать на старте» обоих слоёв собрать строки. Строки с префиксом `brief:` — список для агента `briefing`;
|
||||
остальные — прочитать в контекст сейчас (путь → Read, команда → Bash, только чтение).
|
||||
Если строк `brief:` больше нуля — запустить агента в фоне:
|
||||
`Agent`, `subagent_type: briefing`, `run_in_background: true`, промпт: «Роль: $0. Workspace: <cwd>. Источники:» и список
|
||||
строк `brief:` без префикса, по одной на строку. Не ждать результата.
|
||||
4. Ответить ровно пятью строками:
|
||||
- Роль: `$0` — <описание из первой строки файла роли>.
|
||||
- Процесс: <одна строка из раздела «Процесс» — что действует по умолчанию>.
|
||||
- Прочитано: <перечень файлов обоих слоёв и файлов памяти, через запятую>.
|
||||
- Не делать: <раздел «Не делать» одной строкой>.
|
||||
- Ждёт решения владельца: <из памяти роли; если нет — «ничего»>.
|
||||
Когда придёт выжимка `briefing` — добавить её одним сообщением как есть, без пересказа и без предложений действий.
|
||||
|
||||
## Не делать
|
||||
- Не читать файлы других ролей и их разделы памяти.
|
||||
- Не выполнять записывающие команды из «Читать на старте».
|
||||
- Не спрашивать подтверждения роли, если она пришла из подсказки хука или аргумента.
|
||||
27
tests/run.sh
Executable file
27
tests/run.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# Тесты обвязки: bash, без зависимостей, работают с источниками в репозитории.
|
||||
# Запуск: bash tests/run.sh [подстрока имени файла теста]
|
||||
set -uo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"; ROOT="$(dirname "$HERE")"
|
||||
pass=0; fail=0
|
||||
REAL_HOME="$HOME"
|
||||
# git-идентичность для коммитов в тестах не зависит от машины
|
||||
export GIT_AUTHOR_NAME=test GIT_AUTHOR_EMAIL=test@example.invalid GIT_COMMITTER_NAME=test GIT_COMMITTER_EMAIL=test@example.invalid
|
||||
ok(){ pass=$((pass+1)); printf ' ok %s\n' "$1"; }
|
||||
fail(){ fail=$((fail+1)); printf ' FAIL %s\n %s\n' "$1" "$2"; }
|
||||
assert_eq(){ [ "$2" = "$3" ] && ok "$1" || fail "$1" "получено: '$2', ожидалось: '$3'"; }
|
||||
assert_file(){ [ -e "$2" ] && ok "$1" || fail "$1" "нет файла $2"; }
|
||||
assert_grep(){ grep -qE -- "$2" "$3" 2>/dev/null && ok "$1" || fail "$1" "в $3 нет /$2/"; }
|
||||
assert_max_bytes(){ local n; n=$(wc -c < "$2" | tr -d ' '); [ "$n" -le "$3" ] && ok "$1 ($n Б)" || fail "$1" "$n Б > $3"; }
|
||||
assert_symlink(){ [ -L "$2" ] && [ -e "$2" ] && ok "$1" || fail "$1" "$2 не симлинк или битый"; }
|
||||
for t in "$HERE"/test-*.sh; do
|
||||
[ -n "${1:-}" ] && [[ "$(basename "$t")" != *"$1"* ]] && continue
|
||||
echo "## $(basename "$t")"
|
||||
TMP=$(mktemp -d); export TMP ROOT
|
||||
export HOME="$TMP/home"; mkdir -p "$HOME"
|
||||
# shellcheck disable=SC1090
|
||||
. "$t"
|
||||
export HOME="$REAL_HOME"; rm -rf "$TMP"
|
||||
done
|
||||
echo "passed $pass, failed $fail"
|
||||
[ "$fail" = 0 ]
|
||||
35
tests/test-doctor.sh
Normal file
35
tests/test-doctor.sh
Normal file
@ -0,0 +1,35 @@
|
||||
# doctor.sh: новые проверки — файлы ролей, самотест role-hint, режим workspace, размеры.
|
||||
D="$ROOT/skills/harness-doctor/scripts/doctor.sh"
|
||||
CC="$TMP/cc"; mkdir -p "$CC/hooks" "$CC/roles" "$CC/skills/role" "$CC/agents" "$CC/harness" "$CC/projects"
|
||||
cp "$ROOT"/hooks/*.sh "$CC/hooks/"; chmod +x "$CC/hooks/"*.sh
|
||||
cp "$ROOT"/roles/*.md "$CC/roles/"; cp "$ROOT/skills/role/SKILL.md" "$CC/skills/role/"; cp "$ROOT/agents/briefing.md" "$CC/agents/"
|
||||
cp "$ROOT/link-roles.sh" "$ROOT/adopt-project.sh" "$CC/harness/"; cp "$ROOT/global/CLAUDE.md" "$CC/CLAUDE.md"
|
||||
printf '{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"\\"$HOME/.claude/hooks/role-hint.sh\\""}]}]}}' > "$CC/settings.json"
|
||||
# workspace с одной целой и одной битой ссылкой
|
||||
W="$TMP/ws"; mkdir -p "$W/admin/.claude/roles" "$W/.claude/roles" "$W/.claude/agents"
|
||||
echo '# Роль: admin — x' > "$W/admin/.claude/roles/admin.md"
|
||||
printf '# p\n\n| Каталог | Репозиторий | Роль |\n|---|---|---|\n| admin/ | o/a | admin |\n' > "$W/CLAUDE.md"
|
||||
ln -s ../../admin/.claude/roles/admin.md "$W/.claude/roles/admin.md"
|
||||
ln -s ../../qa/.claude/agents/none.md "$W/.claude/agents/none.md"
|
||||
slug=$(printf '%s' "$W" | sed 's#[^A-Za-z0-9]#-#g'); mkdir -p "$CC/projects/$slug/memory"; head -c 4000 /dev/zero | tr '\0' 'x' > "$CC/projects/$slug/memory/MEMORY.md"
|
||||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W" 2>&1)
|
||||
assert_grep "машина: roles" 'ok +roles/ \(dev admin analyst qa\)' <(printf '%s' "$out")
|
||||
assert_grep "машина: skill role" 'ok +skill role' <(printf '%s' "$out")
|
||||
assert_grep "машина: агент briefing" 'ok +agent briefing' <(printf '%s' "$out")
|
||||
assert_grep "машина: link-roles.sh" 'ok +.*link-roles.sh' <(printf '%s' "$out")
|
||||
assert_grep "самотест: role-hint admin" 'ok +role-hint: заголовок admin' <(printf '%s' "$out")
|
||||
assert_grep "самотест: role-hint тишина" 'ok +role-hint: без заголовка' <(printf '%s' "$out")
|
||||
assert_grep "проект: режим workspace" 'workspace' <(printf '%s' "$out")
|
||||
assert_grep "проект: целая ссылка" 'ok +roles/admin.md' <(printf '%s' "$out")
|
||||
assert_grep "проект: битая ссылка" 'WARN +agents/none.md' <(printf '%s' "$out")
|
||||
assert_grep "проект: индекс памяти > 3 КБ" 'WARN +индекс auto-memory' <(printf '%s' "$out")
|
||||
rm -f "$CC/roles/qa.md"
|
||||
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")
|
||||
# workspace с «выровненной» шапкой таблицы (лишние пробелы) определяется так же
|
||||
W6="$TMP/ws6"; mkdir -p "$W6/admin/.claude/roles" "$W6/.claude/roles"
|
||||
printf '# p\n\n| Каталог | Репозиторий | Роль |\n|---|---|---|\n| admin/ | o/a | admin |\n| | x | qa |\n| - | y | qa |\n' > "$W6/CLAUDE.md"
|
||||
out=$(CLAUDE_CONFIG_DIR="$CC" HOME="$TMP/home" bash "$D" "$W6" 2>&1)
|
||||
assert_grep "workspace с выровненной шапкой" 'workspace \(не git\)' <(printf '%s' "$out")
|
||||
assert_grep "выровненная шапка: нет файла роли admin" 'нет admin/\.claude/roles/admin\.md' <(printf '%s' "$out")
|
||||
assert_eq "пустой и «-» каталоги пропущены" "$(printf '%s' "$out" | grep -c 'нет .*roles/qa\.md')" "0"
|
||||
24
tests/test-install.sh
Normal file
24
tests/test-install.sh
Normal file
@ -0,0 +1,24 @@
|
||||
# install.sh: новые файлы ставятся, повторный запуск не дублирует хуки, --check их видит.
|
||||
TH="$TMP/home"; CLAUDE_CONFIG_DIR="$TH/.claude"; mkdir -p "$CLAUDE_CONFIG_DIR"
|
||||
echo '{"hooks":{"SessionStart":[{"matcher":"startup","hooks":[{"type":"command","command":"\"$HOME/.claude/hooks/harness-check.sh\""}]}]}}' > "$CLAUDE_CONFIG_DIR/settings.json"
|
||||
HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" >/dev/null 2>&1; HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" >/dev/null 2>&1
|
||||
CC="$CLAUDE_CONFIG_DIR"
|
||||
for r in dev admin analyst qa; do assert_file "roles/$r.md установлен" "$CC/roles/$r.md"; done
|
||||
assert_file "skills/role/SKILL.md установлен" "$CC/skills/role/SKILL.md"
|
||||
assert_file "agents/briefing.md установлен" "$CC/agents/briefing.md"
|
||||
assert_file "hooks/role-hint.sh установлен" "$CC/hooks/role-hint.sh"
|
||||
assert_eq "hooks/role-hint.sh исполняемый" "$([ -x "$CC/hooks/role-hint.sh" ] && echo x)" "x"
|
||||
assert_file "harness/link-roles.sh установлен" "$CC/harness/link-roles.sh"
|
||||
assert_file "harness/project-template/workspace" "$CC/harness/project-template/workspace/CLAUDE.md"
|
||||
assert_file "harness/project-template/role-repo" "$CC/harness/project-template/role-repo/ROLE.md"
|
||||
cnt=$(python3 -c "import json,sys; s=json.load(open(sys.argv[1])); print(sum(1 for e in s['hooks']['SessionStart'] for h in e.get('hooks',[]) if 'role-hint.sh' in h['command']))" "$CC/settings.json")
|
||||
assert_eq "role-hint.sh в settings.json один раз после двух установок" "$cnt" "1"
|
||||
cnt=$(python3 -c "import json,sys; s=json.load(open(sys.argv[1])); print(sum(1 for e in s['hooks']['SessionStart'] for h in e.get('hooks',[]) if 'harness-check.sh' in h['command']))" "$CC/settings.json")
|
||||
assert_eq "harness-check.sh в settings.json один раз" "$cnt" "1"
|
||||
out=$(HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" --check 2>&1)
|
||||
assert_grep "--check: roles" 'roles/' <(printf '%s' "$out")
|
||||
assert_grep "--check: skill role" 'skills/role' <(printf '%s' "$out")
|
||||
assert_grep "--check: agent briefing" 'agents/briefing' <(printf '%s' "$out")
|
||||
rm "$CC/agents/briefing.md"
|
||||
out=$(HOME="$TH" CLAUDE_CONFIG_DIR="$CLAUDE_CONFIG_DIR" bash "$ROOT/install.sh" --check 2>&1)
|
||||
assert_grep "--check: MISS briefing" 'MISS +.*briefing' <(printf '%s' "$out")
|
||||
56
tests/test-link-roles.sh
Normal file
56
tests/test-link-roles.sh
Normal file
@ -0,0 +1,56 @@
|
||||
# link-roles.sh: симлинки ролей, агентов и памяти из workspace в репозитории направлений.
|
||||
L="$ROOT/link-roles.sh"
|
||||
assert_file "link-roles.sh существует" "$L"
|
||||
W="$TMP/ws"; mkdir -p "$W/admin/.claude/roles" "$W/admin/.claude/agents" "$W/qa/.claude/roles" "$W/docs" "$W/arm-core"
|
||||
echo '# Роль: admin — проектный слой' > "$W/admin/.claude/roles/admin.md"
|
||||
printf -- '---\nname: jira-admin\nmodel: sonnet\n---\n' > "$W/admin/.claude/agents/jira-admin.md"
|
||||
echo '# Роль: qa — проектный слой' > "$W/qa/.claude/roles/qa.md"
|
||||
cat > "$W/CLAUDE.md" <<'EOT'
|
||||
# Проект
|
||||
|
||||
| Каталог | Репозиторий | Роль |
|
||||
|---|---|---|
|
||||
| arm-core/ | RSHB/arm-core | dev |
|
||||
| admin/ | RSHB/arm-ka-admin | admin |
|
||||
| qa | RSHB/arm-ka-qa | qa |
|
||||
| docs/ | RSHB/arm-ka-docs | — |
|
||||
| source/ | — | |
|
||||
EOT
|
||||
out=$(bash "$L" "$W" 2>&1); rc=$?
|
||||
assert_eq "первый запуск: exit 0" "$rc" "0"
|
||||
assert_symlink "roles/admin.md → admin/" "$W/.claude/roles/admin.md"
|
||||
assert_symlink "roles/qa.md → qa/ (без завершающего /)" "$W/.claude/roles/qa.md"
|
||||
assert_eq "roles/admin.md относительный" "$(readlink "$W/.claude/roles/admin.md")" "../../admin/.claude/roles/admin.md"
|
||||
assert_symlink "agents/jira-admin.md" "$W/.claude/agents/jira-admin.md"
|
||||
assert_eq "agent-memory не создаётся (спека, раздел 11)" "$([ -e "$W/.claude/agent-memory" ] || [ -e "$W/admin/.claude/agent-memory" ] && echo есть || echo нет)" "нет"
|
||||
assert_eq "dev не линкуется" "$(ls "$W/.claude/roles" | grep -c dev)" "0"
|
||||
assert_eq "docs/source пропущены" "$(ls "$W/.claude/roles" | wc -l | tr -d ' ')" "2"
|
||||
assert_grep "печатает 'создан'" 'создан' <(printf '%s' "$out")
|
||||
out2=$(bash "$L" "$W" 2>&1); rc=$?
|
||||
assert_eq "второй запуск: exit 0" "$rc" "0"
|
||||
assert_eq "второй запуск: ничего не создано" "$(printf '%s' "$out2" | grep -c 'создан')" "0"
|
||||
assert_grep "второй запуск: 'есть'" 'есть' <(printf '%s' "$out2")
|
||||
assert_eq "второй запуск: ссылка прежняя" "$(readlink "$W/.claude/agents/jira-admin.md")" "../../admin/.claude/agents/jira-admin.md"
|
||||
# чужой обычный файл на месте ссылки не затирается
|
||||
rm "$W/.claude/roles/qa.md"; echo 'мой файл' > "$W/.claude/roles/qa.md"
|
||||
out3=$(bash "$L" "$W" 2>&1)
|
||||
assert_eq "обычный файл сохранён" "$(cat "$W/.claude/roles/qa.md")" "мой файл"
|
||||
assert_grep "обычный файл → ПРОПУЩЕН" 'ПРОПУЩЕН' <(printf '%s' "$out3")
|
||||
# --map без CLAUDE.md
|
||||
W2="$TMP/ws2"; mkdir -p "$W2/an/.claude/roles"; echo x > "$W2/an/.claude/roles/analyst.md"
|
||||
bash "$L" "$W2" --map an=analyst >/dev/null 2>&1
|
||||
assert_symlink "--map an=analyst" "$W2/.claude/roles/analyst.md"
|
||||
# источник роли отсутствует → предупреждение, не ошибка
|
||||
W3="$TMP/ws3"; mkdir -p "$W3/qa"
|
||||
printf '| Каталог | Репозиторий | Роль |\n|---|---|---|\n| qa/ | x | qa |\n' > "$W3/CLAUDE.md"
|
||||
out4=$(bash "$L" "$W3" 2>&1); rc=$?
|
||||
assert_eq "нет файла роли в репозитории: exit 0" "$rc" "0"
|
||||
assert_grep "нет файла роли: WARN" 'WARN' <(printf '%s' "$out4")
|
||||
# CLAUDE.md без таблицы и без --map: не падает
|
||||
W4="$TMP/ws4"; mkdir -p "$W4"; echo '# пусто' > "$W4/CLAUDE.md"
|
||||
bash "$L" "$W4" >/dev/null 2>&1; assert_eq "нет пар: exit 0" "$?" "0"
|
||||
# --map без значения: русская ошибка и exit 2
|
||||
W5="$TMP/ws5"; mkdir -p "$W5"
|
||||
err=$(bash "$L" "$W5" --map 2>&1 >/dev/null); rc=$?
|
||||
assert_eq "--map без значения: exit 2" "$rc" "2"
|
||||
assert_grep "--map без значения: сообщение" 'link-roles: --map требует <dir>=<role>' <(printf '%s' "$err")
|
||||
22
tests/test-role-hint.sh
Normal file
22
tests/test-role-hint.sh
Normal file
@ -0,0 +1,22 @@
|
||||
# role-hint.sh: подсказка роли по session_title, никогда не блокирует.
|
||||
H="$ROOT/hooks/role-hint.sh"
|
||||
assert_file "role-hint.sh существует" "$H"
|
||||
run(){ printf '%s' "$1" | bash "$H" 2>/dev/null; echo "rc=$?"; }
|
||||
assert_eq "admin: … → подсказка" "$(run '{"session_title":"admin: спринт 3","source":"startup"}')" \
|
||||
"[claude-harness] Роль сессии: admin. Вызови /role admin до первого ответа.
|
||||
rc=0"
|
||||
assert_eq "qa без двоеточия → подсказка" "$(run '{"session_title":"qa прогон Q1"}')" \
|
||||
"[claude-harness] Роль сессии: qa. Вызови /role qa до первого ответа.
|
||||
rc=0"
|
||||
assert_eq "регистр и пробелы в начале" "$(run '{"session_title":" Admin — Jira"}')" \
|
||||
"[claude-harness] Роль сессии: admin. Вызови /role admin до первого ответа.
|
||||
rc=0"
|
||||
assert_eq "префикс алиаса cc-" "$(run '{"session_title":"cc-analyst ТЗ v4"}')" \
|
||||
"[claude-harness] Роль сессии: analyst. Вызови /role analyst до первого ответа.
|
||||
rc=0"
|
||||
assert_eq "чужое слово → тишина" "$(run '{"session_title":"harness: роли"}')" "rc=0"
|
||||
assert_eq "слово с продолжением (devops) → тишина" "$(run '{"session_title":"devops"}')" "rc=0"
|
||||
assert_eq "без заголовка → тишина" "$(run '{"cwd":"/tmp","source":"resume"}')" "rc=0"
|
||||
assert_eq "пустой stdin → тишина, exit 0" "$(run '')" "rc=0"
|
||||
assert_eq "не JSON → тишина, exit 0" "$(run 'not json at all')" "rc=0"
|
||||
assert_grep "settings.hooks.json регистрирует role-hint" 'role-hint\.sh' "$ROOT/hooks/settings.hooks.json"
|
||||
19
tests/test-roles.sh
Normal file
19
tests/test-roles.sh
Normal file
@ -0,0 +1,19 @@
|
||||
# Формат файлов ролей (спека 3.1), одинаковый для общего и проектного слоя.
|
||||
check_role_file(){ # $1 = путь, $2 = ожидаемое имя роли
|
||||
local f="$1" r="$2" h
|
||||
assert_file "$f существует" "$f" || return
|
||||
assert_eq "$f: строка 1" "$(sed -n '1p' "$f" | sed -E 's/^(# Роль: [a-z]+) — .*/\1/')" "# Роль: $r"
|
||||
assert_eq "$f: строка 2" "$(sed -n '2p' "$f")" "Эта роль заменяет предыдущую роль сессии целиком."
|
||||
h=$(grep -E '^## ' "$f" | tr '\n' '|')
|
||||
assert_eq "$f: четыре раздела по порядку" "$h" "## Процесс|## Читать на старте|## Не делать|## Делегировать|"
|
||||
}
|
||||
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: архив всегда отдельным PR" 'всегда отдельным PR' "$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: маркировка R-n" 'R-n' "$ROOT/roles/dev.md"
|
||||
assert_grep "admin: Jira вместо Gitea" 'Jira' "$ROOT/roles/admin.md"
|
||||
assert_grep "analyst: код не пишется" 'не пиш' "$ROOT/roles/analyst.md"
|
||||
assert_grep "qa: код не меняется" 'не меня' "$ROOT/roles/qa.md"
|
||||
for r in dev admin analyst qa; do assert_grep "$r: есть строка brief:" '^- brief:' "$ROOT/roles/$r.md"; done
|
||||
15
tests/test-sizes.sh
Normal file
15
tests/test-sizes.sh
Normal file
@ -0,0 +1,15 @@
|
||||
# Размеры и обязательные строки глобального CLAUDE.md (спека 1, 4, 7.1, 8).
|
||||
G="$ROOT/global/CLAUDE.md"
|
||||
assert_max_bytes "global/CLAUDE.md ≤ 3 КБ" "$G" 3072
|
||||
assert_grep "правило выбора роли" 'Роль: admin; если не так' "$G"
|
||||
assert_grep "/role упомянут" '/role' "$G"
|
||||
assert_grep "политика памяти: трекер" 'Состояние задачи живёт в трекере' "$G"
|
||||
assert_grep "политика памяти: notes/" 'notes/' "$G"
|
||||
assert_grep "политика памяти: auto-memory" 'auto-memory' "$G"
|
||||
assert_grep "маркировка R-n" 'R-n' "$G"
|
||||
assert_grep "codegraph projectPath" 'projectPath' "$G"
|
||||
assert_grep "хук [claude-harness]" '\[claude-harness\]' "$G"
|
||||
assert_grep "сабагент не вызывает /role" 'Сабагент роль не выбирает и `/role` не вызывает' "$G"
|
||||
assert_grep "полный путь роли dev" '~/\.claude/roles/dev\.md' "$G"
|
||||
assert_grep "worktree" 'worktree' "$G"
|
||||
assert_file "таблица адресов" "$ROOT/docs/retro/2026-10-11-claude-md-addresses.md"
|
||||
20
tests/test-skill-agent.sh
Normal file
20
tests/test-skill-agent.sh
Normal file
@ -0,0 +1,20 @@
|
||||
# Skill /role и агент briefing: frontmatter и обязательные шаги (спека 5, 6.1).
|
||||
S="$ROOT/skills/role/SKILL.md"; A="$ROOT/agents/briefing.md"
|
||||
assert_file "SKILL.md существует" "$S"
|
||||
assert_eq "skill: первая строка ---" "$(sed -n '1p' "$S")" "---"
|
||||
assert_grep "skill: name: role" '^name: role$' "$S"
|
||||
assert_grep "skill: argument-hint" '^argument-hint: dev[|]admin[|]analyst[|]qa$' "$S"
|
||||
assert_grep "skill: user-invocable" '^user-invocable: true$' "$S"
|
||||
assert_grep "skill: читает общий слой" '~/.claude/roles/' "$S"
|
||||
assert_grep "skill: читает проектный слой" '\.claude/roles/' "$S"
|
||||
assert_grep "skill: собирает brief:" 'brief:' "$S"
|
||||
assert_grep "skill: запускает briefing в фоне" 'subagent_type: briefing' "$S"
|
||||
assert_grep "skill: без аргумента — список ролей" '[Бб]ез аргумента' "$S"
|
||||
assert_grep "skill: использует \$0" '\$0' "$S"
|
||||
assert_file "briefing.md существует" "$A"
|
||||
assert_grep "agent: name" '^name: briefing$' "$A"
|
||||
assert_grep "agent: model sonnet" '^model: sonnet$' "$A"
|
||||
assert_grep "agent: tools" '^tools: Read, Glob, Grep, Bash$' "$A"
|
||||
assert_eq "agent: без memory" "$(grep -c '^memory:' "$A")" "0"
|
||||
for h in 'Активные задачи' 'Решения за последние дни' 'Открытые вопросы' 'Ловушки'; do assert_grep "agent: раздел $h" "$h" "$A"; done
|
||||
assert_grep "agent: до 60 строк" '60 строк' "$A"
|
||||
45
tests/test-workspace.sh
Normal file
45
tests/test-workspace.sh
Normal file
@ -0,0 +1,45 @@
|
||||
# Шаблоны workspace/role-repo и режим --workspace в adopt-project.sh / new-project.sh.
|
||||
T="$ROOT/project-template"
|
||||
assert_file "workspace/CLAUDE.md" "$T/workspace/CLAUDE.md"
|
||||
assert_max_bytes "workspace/CLAUDE.md ≤ 5 КБ" "$T/workspace/CLAUDE.md" 5120
|
||||
assert_grep "workspace/CLAUDE.md: таблица репозиториев" '^\| Каталог \| Репозиторий \| Роль \|' "$T/workspace/CLAUDE.md"
|
||||
assert_grep "workspace/CLAUDE.md: роли → /role" '/role' "$T/workspace/CLAUDE.md"
|
||||
assert_file "workspace/roles/dev.md" "$T/workspace/roles/dev.md"
|
||||
assert_grep "workspace/roles/dev.md: строка 1" '^# Роль: dev ' "$T/workspace/roles/dev.md"
|
||||
assert_file "role-repo/CLAUDE.md" "$T/role-repo/CLAUDE.md"
|
||||
assert_file "role-repo/ROLE.md" "$T/role-repo/ROLE.md"
|
||||
assert_file "role-repo/notes/README.md" "$T/role-repo/notes/README.md"
|
||||
# new-project.sh --workspace
|
||||
W="$TMP/ws"
|
||||
out=$(bash "$ROOT/new-project.sh" --workspace "$W" --role admin --role qa 2>&1); rc=$?
|
||||
assert_eq "new-project --workspace: exit 0" "$rc" "0"
|
||||
assert_file "ws CLAUDE.md" "$W/CLAUDE.md"
|
||||
assert_grep "ws CLAUDE.md: имя подставлено" "$(basename "$W")" "$W/CLAUDE.md"
|
||||
assert_grep "ws CLAUDE.md: строка admin в таблице" '^\| admin/ \| .* \| admin \|' "$W/CLAUDE.md"
|
||||
assert_file "ws .claude/roles/dev.md" "$W/.claude/roles/dev.md"
|
||||
assert_eq "admin/ — git на main" "$(git -C "$W/admin" branch --show-current)" "main"
|
||||
assert_eq "admin/.claude/role" "$(cat "$W/admin/.claude/role")" "admin"
|
||||
assert_grep "admin/.claude/roles/admin.md: строка 1" '^# Роль: admin ' "$W/admin/.claude/roles/admin.md"
|
||||
assert_file "admin/notes/README.md" "$W/admin/notes/README.md"
|
||||
assert_eq "admin: коммит есть" "$(git -C "$W/admin" log --oneline | wc -l | tr -d ' ')" "1"
|
||||
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_eq "workspace сам не git" "$([ -d "$W/.git" ] && echo git || echo no)" "no"
|
||||
# adopt-project.sh --workspace на существующем каталоге без CLAUDE.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=$?
|
||||
assert_eq "adopt --workspace: exit 0" "$rc" "0"
|
||||
assert_file "adopt: CLAUDE.md создан" "$W2/CLAUDE.md"
|
||||
assert_file "adopt: .claude/roles/dev.md создан" "$W2/.claude/roles/dev.md"
|
||||
assert_grep "adopt: печатает allow-правило" 'git push origin --delete' <(printf '%s' "$out")
|
||||
echo 'моё' > "$W2/.claude/roles/dev.md"
|
||||
bash "$ROOT/adopt-project.sh" --workspace "$W2" >/dev/null 2>&1
|
||||
assert_eq "adopt повторно: dev.md не затёрт" "$(cat "$W2/.claude/roles/dev.md")" "моё"
|
||||
# старые режимы не затронуты
|
||||
P="$TMP/oldp"
|
||||
out=$(bash "$ROOT/new-project.sh" "$P" 2>&1); rc=$?
|
||||
assert_eq "new-project (старый режим): exit 0" "$rc" "0"
|
||||
assert_eq "new-project (старый режим): git" "$([ -d "$P/.git" ] && echo git || echo no)" "git"
|
||||
assert_file "new-project (старый режим): ARCHITECTURE.md" "$P/ARCHITECTURE.md"
|
||||
out=$(bash "$ROOT/adopt-project.sh" "$TMP/notgit" 2>&1); rc=$?
|
||||
assert_eq "adopt (старый режим): не git → exit 1" "$rc" "1"
|
||||
Loading…
x
Reference in New Issue
Block a user