claude-harness/docs/superpowers/plans/2026-10-11-roles-and-memory.md
Andrey Epifancev f086b66906 docs(plan): роли и память, часть 1 — обвязка (10 задач)
Refs #2

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 21:37:11 +04:00

1574 lines
113 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Роли и память (часть 1, обвязка) — план реализации
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Обвязка claude-harness получает слой ролей: общие файлы ролей, skill `/role`, агент `briefing`, хук `role-hint`, скрипт `link-roles.sh`, шаблоны workspace и репозитория направления, установка и диагностика, глобальный CLAUDE.md ≤ 3 КБ.
**Architecture:** Всё новое — bash и markdown в пакете, ставится в `~/.claude` через `install.sh`, как уже стоят хуки и `harness-doctor`. Файлы ролей не загружаются автоматически: их читает skill `/role` (общий слой из `~/.claude/roles/`, проектный из `<workspace>/.claude/roles/`). Хук `role-hint.sh` только подсказывает роль по `session_title`, ничего не блокирует. Новый набор тестов `tests/run.sh` (bash, без зависимостей) проверяет источники в репозитории; `doctor.sh` проверяет установленное состояние.
**Tech Stack:** bash (`set -uo pipefail`), встроенный `python3` для JSON, markdown. Claude Code 2.1.296: SessionStart-хуки получают `session_title`; агенты — frontmatter `name/description/model/tools/memory`; skills — `argument-hint`, `user-invocable`, `$ARGUMENTS`/`$0`.
**Spec:** `docs/superpowers/specs/2026-10-11-roles-and-memory-design.md` (утверждена владельцем 11.10.2026). Этот план покрывает раздел 8 спеки и проверки 2 и 5 раздела 9, плюс проверку 1 (память через симлинк) как первую задачу. Разделы 3.3, 6.2, 6.3, 7.3 и остальные проверки раздела 9 — это работа в workspace `awp-ca`, отдельный план там после merge этой ветки.
**Ветка:** `feat/2-roles-and-memory`, worktree `/Users/andrey.epifancev/projects/cc-skills/claude-harness/.wt/roles`. Issue Gitea #2. Все пути в шагах — относительно корня worktree, если не сказано иное. Коммиты с trailer `Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>`; ревьюер задачи проверяет trailer.
## Global Constraints
- Хуки `guard-main.sh`, `require-plan.sh`, `harness-check.sh` не меняются (спека, разделы 1 и 8).
- Глобальный `global/CLAUDE.md` ≤ 3 КБ (3072 байта); шаблон CLAUDE.md workspace ≤ 5 КБ (5120 байт); индекс auto-memory ≤ 3 КБ — последнее проверяет только `doctor.sh`, чистка памяти в части 2.
- Ни одно правило из нынешнего `global/CLAUDE.md` (9155 байт) не теряется: у каждого абзаца есть новый адрес, таблица адресов — в `docs/retro/2026-10-11-claude-md-addresses.md` и в описании PR.
- Формат файла роли одинаков для обоих слоёв (спека 3.1): строка 1 `# Роль: <r> — <описание>`, строка 2 `Эта роль заменяет предыдущую роль сессии целиком.`, затем ровно четыре раздела второго уровня в порядке `## Процесс`, `## Читать на старте`, `## Не делать`, `## Делегировать`. Внутри разделов допускаются `###`.
- Имена ролей: `dev`, `admin`, `analyst`, `qa`. Других нет.
- Агенты: модель указывается явно (`model: sonnet`).
- Все новые скрипты: `#!/usr/bin/env bash`, `set -uo pipefail` (хук) или `set -euo pipefail` (скрипты), русские сообщения, идемпотентность, печать того, что сделано.
- Хук `role-hint.sh` никогда не блокирует: любой сбой разбора → `exit 0` без вывода.
- Язык текстов — русский. Caveman не включать.
- Установленные копии в `~/.claude` по месту не правятся; тесты в `tests/` работают с источниками, ставя `install.sh` во временный `CLAUDE_CONFIG_DIR`.
## Review Focus
1. Заголовок сессии с пробелами в начале, в верхнем регистре или с префиксом алиаса (`" Admin: спринт"`, `"cc-qa прогон"`) — владелец ждёт подсказку роли, а не тишину. Тест в Task 2.
2. `link-roles.sh` повторно и при уже существующем обычном файле на месте ссылки (`.claude/roles/admin.md` — файл, не симлинк) — второй запуск ничего не ломает, чужой файл не затирается, печатается предупреждение. Тест в Task 6.
3. `role-hint.sh` на пустом stdin, не-JSON и JSON без `session_title` — молчит и `exit 0`, сессия стартует. Тест в Task 2.
4. `install.sh` дважды подряд — `role-hint.sh` зарегистрирован в `settings.json` ровно один раз, `harness-check.sh` не задвоился. Тест в Task 9.
5. Таблица репозиториев в CLAUDE.md workspace с лишними пробелами, каталогом без завершающего `/`, строкой без роли (`source/`, роль «—») — `link-roles.sh` берёт только строки с ролью `admin|analyst|qa`, не падает на остальных. Тест в Task 6.
---
### Task 1: Проверка памяти агента через симлинк (spike, результат — запись в спеке)
Открытый вопрос спеки (раздел 11, проверка 1 раздела 9): пишет ли агент с `memory: project` через симлинк каталога `.claude/agent-memory/<name>` в репозиторий направления. Ответ определяет, нужен ли запасной вариант 6.5 в части 2. Код не остаётся; остаётся запись в спеке.
**Files:**
- Modify: `docs/superpowers/specs/2026-10-11-roles-and-memory-design.md` (раздел 11 «Открытые вопросы», первый пункт)
**Interfaces:**
- Consumes: ничего.
- Produces: факт «симлинк работает / не работает» и путь, куда реально легла память.
- [ ] **Step 1: Собрать песочницу в scratchpad (не в репозитории)**
```bash
SB=$(mktemp -d /tmp/memprobe.XXXX); echo "$SB"
mkdir -p "$SB/ws/.claude/agents" "$SB/ws/.claude/agent-memory" "$SB/dir/.claude/agents" "$SB/dir/.claude/agent-memory/probe"
cat > "$SB/dir/.claude/agents/probe.md" <<'EOF'
---
name: probe
description: Проверка памяти через симлинк. Использовать только по явной просьбе.
model: haiku
tools: Read, Write, Edit
memory: project
---
Ты проверяешь память. Выполни задание из промпта буквально и ответь одной строкой.
EOF
ln -s ../../../dir/.claude/agents/probe.md "$SB/ws/.claude/agents/probe.md"
ln -s ../../../dir/.claude/agent-memory/probe "$SB/ws/.claude/agent-memory/probe"
ls -la "$SB/ws/.claude/agents" "$SB/ws/.claude/agent-memory"
```
Ожидается: два симлинка, оба разрешаются (`readlink -f` даёт путь внутри `$SB/dir`).
- [ ] **Step 2: Запустить агента из корня песочницы и попросить записать память**
```bash
cd "$SB/ws" && claude -p --agent probe "Запиши в свою память (MEMORY.md) строку «probe-ok 2026-10-11» и ответь «готово»." 2>&1 | tail -5
```
Если `--agent probe` не находит агента (сообщение «unknown agent»), повторить с `claude -p "Используй агента probe: запиши в его память строку «probe-ok 2026-10-11»"`.
- [ ] **Step 3: Проверить, куда легла память**
```bash
echo '--- через симлинк'; find "$SB/dir/.claude/agent-memory" -type f -exec sh -c 'echo "$1"; cat "$1"' _ {} \;
echo '--- мимо симлинка'; find "$SB/ws/.claude" -path '*agent-memory*' -type f -not -path "$SB/ws/.claude/agent-memory/probe/*"
echo '--- auto-memory проекта'; ls -d "$HOME/.claude/projects/"*memprobe* 2>/dev/null
```
Ожидается один из трёх исходов:
- A. Файл `MEMORY.md` с `probe-ok` лежит в `$SB/dir/.claude/agent-memory/probe/` — симлинк работает.
- B. Файл лежит в `$SB/ws/.claude/agent-memory/probe/` как обычный каталог (симлинк заменён) или в другом месте — симлинк не работает, нужен запасной вариант 6.5.
- C. Агент не записал ничего (память выключена или агент не вызвался) — повторить Step 2 с `/memory` в интерактивной сессии; если и так ничего, зафиксировать как «не проверено, причина …».
- [ ] **Step 4: Записать результат в спеку**
В `docs/superpowers/specs/2026-10-11-roles-and-memory-design.md` заменить первый пункт раздела 11:
```markdown
- Работает ли `memory: project` через симлинк каталога (проверка 1). Пока не проверено, в плане первой задачей.
```
на (подставить исход):
```markdown
- Работает ли `memory: project` через симлинк каталога — проверено 2026-10-11 (план, Task 1): <A: да, MEMORY.md записан в целевой каталог симлинка; / B: нет, память легла в <путь>, в части 2 применяется запасной вариант 6.5; / C: не проверено: <причина>>.
```
- [ ] **Step 5: Убрать песочницу и закоммитить**
```bash
rm -rf "$SB"
git add docs/superpowers/specs/2026-10-11-roles-and-memory-design.md
git commit -m "docs(spec): результат проверки памяти агента через симлинк
Refs #2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>"
```
---
### Task 2: Тестовый прогон `tests/run.sh` и хук `role-hint.sh`
**Files:**
- Create: `tests/run.sh`
- Create: `tests/test-role-hint.sh`
- Create: `hooks/role-hint.sh`
- Modify: `hooks/settings.hooks.json`
**Interfaces:**
- Produces для всех следующих задач: `tests/run.sh [подстрока-имени]` запускает все `tests/test-*.sh` (или один) и возвращает 0 только если все `ok`. Хелперы, доступные внутри тестов: `ok "<имя>"`, `fail "<имя>" "<почему>"`, `assert_eq "<имя>" "<получено>" "<ожидалось>"`, `assert_file "<имя>" <путь>`, `assert_grep "<имя>" "<регэксп>" <файл>`, `assert_max_bytes "<имя>" <файл> <байт>`, `assert_symlink "<имя>" <путь>`; переменная `ROOT` — корень репозитория, `TMP` — свежий временный каталог на каждый тест-файл (удаляется после).
- Produces: `hooks/role-hint.sh` — SessionStart-хук; stdin JSON; stdout одна строка `[claude-harness] Роль сессии: <r>. Вызови /role <r> до первого ответа.` или ничего; всегда `exit 0`.
- [ ] **Step 1: Написать прогон тестов**
`tests/run.sh`:
```bash
#!/usr/bin/env bash
# Тесты обвязки: bash, без зависимостей, работают с источниками в репозитории.
# Запуск: bash tests/run.sh [подстрока имени файла теста]
set -uo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"; ROOT="$(dirname "$HERE")"
pass=0; fail=0
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
# shellcheck disable=SC1090
. "$t"
rm -rf "$TMP"
done
echo "passed $pass, failed $fail"
[ "$fail" = 0 ]
```
- [ ] **Step 2: Написать падающий тест хука**
`tests/test-role-hint.sh`:
```bash
# 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"
```
- [ ] **Step 3: Запустить, убедиться, что падает**
Run: `bash tests/run.sh role-hint`
Expected: `FAIL role-hint.sh существует` и далее FAIL по всем случаям; итог `failed 11`.
- [ ] **Step 4: Написать хук**
`hooks/role-hint.sh`:
```bash
#!/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
```
```bash
chmod +x hooks/role-hint.sh
```
- [ ] **Step 5: Зарегистрировать хук в блоке настроек**
В `hooks/settings.hooks.json` заменить массив `SessionStart`:
```json
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "\"$HOME/.claude/hooks/harness-check.sh\""
}
]
},
{
"hooks": [
{
"type": "command",
"command": "\"$HOME/.claude/hooks/role-hint.sh\""
}
]
}
]
```
Запись без `matcher` срабатывает на всех источниках (startup, resume, clear, compact, fork) — так документировано для хуков Claude Code.
- [ ] **Step 6: Запустить тест, убедиться, что проходит**
Run: `bash tests/run.sh role-hint`
Expected: 11 `ok`, `failed 0`. Также `python3 -m json.tool hooks/settings.hooks.json >/dev/null` без ошибок.
- [ ] **Step 7: Коммит**
```bash
git add tests/run.sh tests/test-role-hint.sh hooks/role-hint.sh hooks/settings.hooks.json
git commit -m "feat(hooks): role-hint.sh — подсказка роли по session_title; tests/run.sh
Refs #2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>"
```
---
### Task 3: Общие файлы ролей `roles/{dev,admin,analyst,qa}.md`
**Files:**
- Create: `tests/test-roles.sh`
- Create: `roles/dev.md`, `roles/admin.md`, `roles/analyst.md`, `roles/qa.md`
**Interfaces:**
- Produces: четыре файла формата 3.1 (см. Global Constraints). `roles/dev.md` — единственное место, где теперь живёт процесс разработки (маршрутизация, исполнение, завершение, маркировка, стиль), перенесённый из `global/CLAUDE.md`. Task 4 сжимает `global/CLAUDE.md`, опираясь на то, что всё остальное лежит здесь. Task 5 (`/role`) читает эти файлы по пути `~/.claude/roles/<r>.md`. Строки `brief:` в «Читать на старте» — вход агента `briefing` (Task 5).
- [ ] **Step 1: Написать падающий тест формата**
`tests/test-roles.sh`:
```bash
# Формат файлов ролей (спека 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
```
- [ ] **Step 2: Запустить, убедиться, что падает**
Run: `bash tests/run.sh roles`
Expected: FAIL «roles/dev.md существует» и остальные; `failed` > 0.
- [ ] **Step 3: Написать `roles/dev.md`**
Содержание — нынешний `global/CLAUDE.md` целиком по смыслу, переложенный в четыре раздела, с правками по решениям владельца 11.10.2026 (архив отдельным PR; п. 4 через allow-правило) и уроками ретро. Текст файла:
````markdown
# Роль: 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`); затем `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.
- `briefing` → выжимка строк `brief:` при `/role dev`.
````
- [ ] **Step 4: Написать `roles/admin.md`**
````markdown
# Роль: 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, сверка Ганта, черновики описаний задач.
````
- [ ] **Step 5: Написать `roles/analyst.md`**
````markdown
# Роль: 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`.
````
- [ ] **Step 6: Написать `roles/qa.md`**
````markdown
# Роль: 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/<дата>/`.
````
- [ ] **Step 7: Запустить тест**
Run: `bash tests/run.sh roles`
Expected: все `ok`, `failed 0`.
- [ ] **Step 8: Коммит**
```bash
git add tests/test-roles.sh roles/
git commit -m "feat(roles): общие файлы ролей dev/admin/analyst/qa (формат 3.1)
Refs #2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>"
```
---
### Task 4: Глобальный `global/CLAUDE.md` ≤ 3 КБ и таблица адресов
**Files:**
- Modify: `global/CLAUDE.md` (полная замена)
- Create: `docs/retro/2026-10-11-claude-md-addresses.md`
- Create: `tests/test-sizes.sh`
**Interfaces:**
- Consumes: `roles/dev.md` из Task 3 — туда переехал процесс.
- Produces: глобальные правила, которые остаются в каждой сессии любой роли: язык, хуки, worktree, маркировка (кратко), политика памяти (спека 7.1), правило выбора роли (спека 4), «процесс роли — в /role». `doctor.sh` (Task 8) проверяет в нём строки `R-n`, `projectPath` и `/role`.
- [ ] **Step 1: Написать падающий тест размеров и содержания**
`tests/test-sizes.sh`:
```bash
# Размеры и обязательные строки глобального 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 "worktree" 'worktree' "$G"
assert_file "таблица адресов" "$ROOT/docs/retro/2026-10-11-claude-md-addresses.md"
```
- [ ] **Step 2: Запустить, убедиться, что падает**
Run: `bash tests/run.sh sizes`
Expected: FAIL «global/CLAUDE.md ≤ 3 КБ» (сейчас 9155 Б) и FAIL по строкам роли/памяти.
- [ ] **Step 3: Переписать `global/CLAUDE.md`**
```markdown
# Правила сессии
## Роль
- Сессия работает в одной из ролей: `dev`, `admin`, `analyst`, `qa`. Правила роли загружает `/role <r>`; новая роль заменяет предыдущую целиком.
- Подсказка `[claude-harness] Роль сессии: <r>` от хука при старте — вызвать `/role <r>` до первого ответа.
- Без подсказки: если роль очевидна из первого сообщения (Jira, Гант, спринт → admin; стенд, прогон, скриншот, макет → qa; ТЗ, OQ, спека, методика → analyst; срез, issue, PR, тест, миграция → dev) — объявить одной строкой «Роль: admin; если не так, скажи» и вызвать `/role`. Иначе — один вопрос с четырьмя вариантами.
- Процесс и чеклисты роли — в её файле, не здесь.
## Хуки и ветки
- guard-main блокирует запись на main/master и detached HEAD: работа только в worktree на ветке. require-plan: правки в коде только при плане для ветки или файле `.bounded`.
- Сообщение `[claude-harness]` при старте — инструкция обвязки, выполняется до всего остального.
- Хуки не обходить (`echo >`, API вместо `tea`). Если хук мешает — сказать владельцу.
- codegraph: после создания worktree — `codegraph init` в нём; в `codegraph_explore` — `projectPath` = абсолютный путь worktree.
## Память
- Состояние задачи живёт в трекере (Gitea issue, Jira), не в памяти.
- В репозиторий направления (`<dir>/notes/`, память агента) пишутся уроки, решения владельца, рецепты. Коммит в той же ветке, что и работа.
- В локальную auto-memory — только ловушки этой машины и обвязки и индекс со ссылками на `notes/` направлений.
## Маркировка
- Каждая находка и каждое решение несёт маркер источника (`T<n>.<k>`, `C-n`/`I-n`/`M-n`, `R-n`, `O-n`, `H-n`, `RT-n`) и статус `fixed <commit>` / `deferred` / `rejected <причина>`. Маркер присваивается один раз. Подробности — в роли dev.
## Стиль
- Отвечать по-русски. Отчёты сабагентов, рулинги и ledger не сжимать; caveman — только по явной команде.
```
- [ ] **Step 4: Написать таблицу адресов**
`docs/retro/2026-10-11-claude-md-addresses.md` — каждый абзац старого `global/CLAUDE.md` (версия 0.2.0, коммит d2496aa и раньше) и его новый адрес:
```markdown
# Куда переехал глобальный 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` → Стиль |
Новое, чего не было: раздел «Роль» (спека 4), раздел «Память» (спека 7.1), запрет обхода хуков (ретро, `feedback-no-harness-bypass`), `cd` при фоновом контроллере (RT-2), абсолютные пути worktree, trailer коммита (RT-9), предусловия окружения (RT-8).
```
- [ ] **Step 5: Запустить тесты**
Run: `bash tests/run.sh`
Expected: все файлы тестов `ok`, `failed 0`. Вывести `wc -c global/CLAUDE.md` — число ≤ 3072.
- [ ] **Step 6: Коммит**
```bash
git add global/CLAUDE.md docs/retro/2026-10-11-claude-md-addresses.md tests/test-sizes.sh
git commit -m "feat(global): CLAUDE.md ≤ 3 КБ — роль, хуки, память, маркировка; таблица адресов
Refs #2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>"
```
---
### Task 5: Skill `/role` и агент `briefing`
**Files:**
- Create: `skills/role/SKILL.md`
- Create: `agents/briefing.md`
- Create: `tests/test-skill-agent.sh`
**Interfaces:**
- Consumes: формат файлов ролей (Task 3), пути `~/.claude/roles/<r>.md` и `<cwd>/.claude/roles/<r>.md`.
- Produces: skill `role` (ставится в `~/.claude/skills/role/`), агент `briefing` (ставится в `~/.claude/agents/briefing.md`). Install (Task 9) копирует их по этим путям.
- [ ] **Step 1: Написать падающий тест frontmatter**
`tests/test-skill-agent.sh`:
```bash
# 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"
```
- [ ] **Step 2: Запустить, убедиться, что падает**
Run: `bash tests/run.sh skill-agent`
Expected: FAIL по всем пунктам.
- [ ] **Step 3: Написать `skills/role/SKILL.md`**
````markdown
---
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` — добавить её одним сообщением как есть, без пересказа и без предложений действий.
## Не делать
- Не читать файлы других ролей и их разделы памяти.
- Не выполнять записывающие команды из «Читать на старте».
- Не спрашивать подтверждения роли, если она пришла из подсказки хука или аргумента.
````
- [ ] **Step 4: Написать `agents/briefing.md`**
````markdown
---
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, памяти).
**Открытые вопросы** — что ждёт решения владельца, с источником.
**Ловушки** — предупреждения из уроков и памяти, актуальные для этой роли, по строке.
Если раздел пуст — строка «— нет». Если источник не найден — строка «не найдено: <путь>» в «Ловушках».
````
- [ ] **Step 5: Запустить тест**
Run: `bash tests/run.sh skill-agent`
Expected: все `ok`.
- [ ] **Step 6: Коммит**
```bash
git add skills/role/SKILL.md agents/briefing.md tests/test-skill-agent.sh
git commit -m "feat: skill /role и агент briefing
Refs #2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>"
```
---
### Task 6: `link-roles.sh`
**Files:**
- Create: `link-roles.sh`
- Create: `tests/test-link-roles.sh`
**Interfaces:**
- Consumes: формат таблицы репозиториев в CLAUDE.md workspace (вводится здесь, используется шаблоном в Task 7 и `doctor.sh` в Task 8):
```markdown
| Каталог | Репозиторий | Роль |
|---|---|---|
| arm-core/ | RSHB/arm-core | dev |
| admin/ | RSHB/arm-ka-admin | admin |
| docs/ | RSHB/arm-ka-docs | — |
```
Строка учитывается, если третья ячейка — `admin`, `analyst` или `qa`; первая ячейка — каталог относительно workspace, завершающий `/` необязателен. Строки с `dev`, `—` и любым другим значением пропускаются.
- Produces: `link-roles.sh [workspace] [--map <dir>=<role> ...]`. В `<workspace>/.claude/` создаёт симлинки: `roles/<role>.md → ../../<dir>/.claude/roles/<role>.md`; `agents/<name>.md → ../../<dir>/.claude/agents/<name>.md` для каждого файла агента в репозитории направления; `agent-memory/<name> → ../../<dir>/.claude/agent-memory/<name>` для каждого агента (целевой каталог создаётся, если его нет). Идемпотентен; печатает `создан`/`есть`/`ПРОПУЩЕН` по каждой ссылке; выход 0, если нет ошибок разбора. `--map` добавляет пары к таблице или заменяет её, если CLAUDE.md нет.
- [ ] **Step 1: Написать падающий тест**
`tests/test-link-roles.sh`:
```bash
# 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" <<'EOF'
# Проект
| Каталог | Репозиторий | Роль |
|---|---|---|
| arm-core/ | RSHB/arm-core | dev |
| admin/ | RSHB/arm-ka-admin | admin |
| qa | RSHB/arm-ka-qa | qa |
| docs/ | RSHB/arm-ka-docs | — |
| source/ | — | |
EOF
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_symlink "agent-memory/jira-admin" "$W/.claude/agent-memory/jira-admin"
assert_file "целевой каталог памяти создан" "$W/admin/.claude/agent-memory/jira-admin"
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"
# чужой обычный файл на месте ссылки не затирается
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")
```
- [ ] **Step 2: Запустить, убедиться, что падает**
Run: `bash tests/run.sh link-roles`
Expected: FAIL «link-roles.sh существует» и остальные.
- [ ] **Step 3: Написать `link-roles.sh`**
```bash
#!/usr/bin/env bash
# claude-harness: симлинки из <workspace>/.claude/ в репозитории направлений (роли, агенты, память агентов).
# Запуск: 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) 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 re, 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 .claude/agent-memory
link() { # $1 = путь ссылки (относительно WS), $2 = цель (относительно каталога ссылки)
local l="$1" t="$2"
if [ -L "$l" ]; then
[ "$(readlink "$l")" = "$t" ] && { echo " есть $l"; return; }
rm "$l"
elif [ -e "$l" ]; then
echo " ПРОПУЩЕН $l — обычный файл/каталог, не симлинк; убери его вручную, если нужна ссылка"; return
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"
mkdir -p "$d/.claude/agent-memory/$n"
link ".claude/agent-memory/$n" "../../$d/.claude/agent-memory/$n"
done
done
```
```bash
chmod +x link-roles.sh
```
- [ ] **Step 4: Запустить тест**
Run: `bash tests/run.sh link-roles`
Expected: все `ok`, `failed 0`.
- [ ] **Step 5: Коммит**
```bash
git add link-roles.sh tests/test-link-roles.sh
git commit -m "feat: link-roles.sh — симлинки ролей, агентов и памяти из workspace
Refs #2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>"
```
---
### Task 7: Шаблоны workspace и репозитория направления, режим `--workspace`
**Files:**
- Create: `project-template/workspace/CLAUDE.md`
- Create: `project-template/workspace/roles/dev.md`
- Create: `project-template/role-repo/CLAUDE.md`
- Create: `project-template/role-repo/ROLE.md`
- Create: `project-template/role-repo/notes/README.md`
- Modify: `adopt-project.sh` (добавить ветку `--workspace` до проверки git)
- Modify: `new-project.sh` (добавить режим `--workspace <dir> [--role <r> ...]`)
- Create: `tests/test-workspace.sh`
**Interfaces:**
- Consumes: `link-roles.sh` (Task 6), формат таблицы репозиториев, формат файла роли.
- Produces: `adopt-project.sh --workspace [dir]` — для каталога, который не git: создаёт `CLAUDE.md` из шаблона workspace (если нет), `.claude/roles/dev.md` (если нет), печатает строку allow-правила для удаления веток, вызывает `link-roles.sh`. `new-project.sh --workspace <dir> [--role admin --role qa ...]` — создаёт workspace и по каталогу-репозиторию на роль из `role-repo/` (`<dir>/<role>/`, `git init -b main`, коммит), затем `link-roles.sh`. Существующие режимы обоих скриптов не меняются.
- [ ] **Step 1: Написать падающий тест**
`tests/test-workspace.sh`:
```bash
# Шаблоны 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")" "моё"
```
- [ ] **Step 2: Запустить, убедиться, что падает**
Run: `bash tests/run.sh workspace`
Expected: FAIL по шаблонам и режимам.
- [ ] **Step 3: Написать шаблоны**
`project-template/workspace/CLAUDE.md`:
```markdown
# {{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.
- {{Стенд, порты, что нельзя трогать.}}
```
`project-template/workspace/roles/dev.md`:
```markdown
# Роль: dev — разработчик (проектный слой {{PROJECT_NAME}})
Эта роль заменяет предыдущую роль сессии целиком.
## Процесс
- {{Репозитории кода и их CLAUDE.md; worktree `.wt/<задача>/` и что в него линкуется; `.bounded`; venv/сборка; стенд.}}
- {{Источники истины для рулингов: модель данных, доменные правила.}}
## Читать на старте
- {{путь к инвариантам / ARCHITECTURE.md}}
- brief: `tea issues ls --state open`
## Не делать
- {{Инварианты проекта: что не мутируется, кто единственный писатель, чего не хранить.}}
## Делегировать
- SDD-контроллер → исполнение плана.
```
`project-template/role-repo/CLAUDE.md`:
```markdown
# {{DIR}} — репозиторий направления {{ROLE}}
Кухня роли `{{ROLE}}` проекта {{PROJECT_NAME}}: рабочие материалы, `notes/` (уроки, решения владельца, рецепты),
`.claude/roles/{{ROLE}}.md` (проектный слой роли), `.claude/agents/` (агенты роли), `.claude/agent-memory/` (их память, коммитится).
Сессии запускаются из корня workspace; этот файл загружается при чтении файлов отсюда. Публикация в витрину `docs/` — через PR.
```
`project-template/role-repo/ROLE.md` (ставится как `.claude/roles/<role>.md`):
```markdown
# Роль: {{ROLE}} — проектный слой {{PROJECT_NAME}}
Эта роль заменяет предыдущую роль сессии целиком.
## Процесс
- {{Инструменты роли в этом проекте, трекер, где лежат артефакты.}}
## Читать на старте
- {{DIR}}/README.md
- brief: {{DIR}}/notes/
## Не делать
- {{Операционные ограничения проекта для этой роли.}}
## Делегировать
- {{агент → для чего, если есть}}
```
`project-template/role-repo/notes/README.md`:
```markdown
# notes/
Уроки, решения владельца (с датой) и рецепты роли. Коммитятся в той же ветке, что и работа. Состояние задач сюда не пишется — оно в трекере.
```
- [ ] **Step 4: Добавить режим `--workspace` в `adopt-project.sh`**
Вставить после строки `HERE="$(cd "$(dirname "$0")" && pwd)"` (до разбора аргументов):
```bash
# Режим 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
```
- [ ] **Step 5: Добавить режим `--workspace` в `new-project.sh`**
Вставить после `HERE="$(cd "$(dirname "$0")" && pwd)"`:
```bash
# Режим 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
```
Примечание для implementer: строка таблицы для `analysis/` в шаблоне — `| analysis/ | {{owner/repo-analysis}} | analyst |`, `sed` выше подставляет её по `$d` и `$r`; для `admin` и `qa` имена каталога и роли совпадают.
- [ ] **Step 6: Запустить тест**
Run: `bash tests/run.sh workspace`
Expected: все `ok`. Затем `bash tests/run.sh` — всё зелёное.
- [ ] **Step 7: Коммит**
```bash
git add project-template/workspace project-template/role-repo adopt-project.sh new-project.sh tests/test-workspace.sh
git commit -m "feat: шаблоны workspace и role-repo, режим --workspace в adopt/new-project
Refs #2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>"
```
---
### Task 8: `doctor.sh` — роли, role-hint, workspace, размеры
**Files:**
- Modify: `skills/harness-doctor/scripts/doctor.sh`
- Modify: `skills/harness-doctor/SKILL.md` (чеклист прочтения — новые строки)
- Create: `tests/test-doctor.sh`
**Interfaces:**
- Consumes: установленные пути из Task 9 (`$CC/roles/`, `$CC/skills/role/`, `$CC/agents/briefing.md`, `$CC/hooks/role-hint.sh`, `$CC/harness/link-roles.sh`); формат таблицы репозиториев.
- Produces: в разделе «Машина» — строки `ok`/`MISS` по новым файлам и `WARN глобальный CLAUDE.md > 3 КБ`; в «Самотест хуков» — две проверки `role-hint`; в «Проект» для не-git каталога с таблицей репозиториев — режим workspace: симлинки `.claude/roles|agents|agent-memory` целы/битые, размер CLAUDE.md ≤ 5 КБ, размер индекса памяти `MEMORY.md` ≤ 3 КБ.
- [ ] **Step 1: Написать падающий тест**
`tests/test-doctor.sh` — запускает `doctor.sh` с `CLAUDE_CONFIG_DIR`, указывающим на временную установку из источников (без настоящего `install.sh`, чтобы не зависеть от Task 9):
```bash
# 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" 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" bash "$D" "$W" 2>&1)
assert_grep "машина: MISS roles/qa.md" 'MISS +roles/qa.md' <(printf '%s' "$out")
```
- [ ] **Step 2: Запустить, убедиться, что падает**
Run: `bash tests/run.sh doctor`
Expected: FAIL по всем новым строкам (старые строки doctor печатаются, но новых нет).
- [ ] **Step 3: Дополнить раздел «Машина»**
В `doctor.sh` после строки с `[ -x "$CC/harness/adopt-project.sh" ] && ok ...` добавить:
```bash
[ -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
```
И в цикле `for h in guard-main require-plan harness-check; do` добавить `role-hint`: `for h in guard-main require-plan harness-check role-hint; do`.
- [ ] **Step 4: Дополнить самотест хуков**
Перед `rm -rf "$T"` в блоке самотеста добавить:
```bash
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
```
- [ ] **Step 5: Добавить режим workspace в раздел «Проект»**
Заменить последнюю строку блока `else info "не git-репозиторий"; fi` на:
```bash
elif [ -f "$DIR/CLAUDE.md" ] && grep -q '^| Каталог | Репозиторий | Роль |' "$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 agent-memory; 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") 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
```
- [ ] **Step 6: Дополнить `SKILL.md` чеклист прочтения**
В раздел «**Машина**» добавить:
```markdown
- `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`, перенести туда.
```
В раздел «**Проект**» добавить:
```markdown
- «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).
```
В «Чинится само» добавить `link-roles.sh` в workspace.
- [ ] **Step 7: Запустить тесты**
Run: `bash tests/run.sh doctor` затем `bash tests/run.sh`
Expected: всё `ok`. Дополнительно вручную: `bash skills/harness-doctor/scripts/doctor.sh .` в worktree печатает старые разделы без регрессий (строки `ok hook …`, самотест guard-main/require-plan).
- [ ] **Step 8: Коммит**
```bash
git add skills/harness-doctor tests/test-doctor.sh
git commit -m "feat(doctor): проверки ролей, самотест role-hint, режим workspace, размеры
Refs #2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>"
```
---
### Task 9: `install.sh` ставит роли, skill, агента, хук, link-roles
**Files:**
- Modify: `install.sh`
- Create: `tests/test-install.sh`
**Interfaces:**
- Consumes: все артефакты Task 2–8.
- Produces: установленное состояние в `$CC`: `roles/*.md`, `skills/role/SKILL.md`, `agents/briefing.md`, `hooks/role-hint.sh`, `harness/link-roles.sh`; `settings.json` с одной записью `role-hint.sh` в `SessionStart` после любого числа запусков; `--check` печатает `ok`/`MISS` по этим файлам.
- [ ] **Step 1: Написать падающий тест**
`tests/test-install.sh`:
```bash
# install.sh: новые файлы ставятся, повторный запуск не дублирует хуки, --check их видит.
export HOME="$TMP/home"; export CLAUDE_CONFIG_DIR="$TMP/home/.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"
bash "$ROOT/install.sh" >/dev/null 2>&1; 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=$(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=$(bash "$ROOT/install.sh" --check 2>&1)
assert_grep "--check: MISS briefing" 'MISS +.*briefing' <(printf '%s' "$out")
```
- [ ] **Step 2: Запустить, убедиться, что падает**
Run: `bash tests/run.sh install`
Expected: FAIL по `roles/`, `skills/role`, `agents/briefing.md`, `role-hint.sh`, `link-roles.sh`, `--check`.
- [ ] **Step 3: Дополнить `install.sh`**
В `check()` после цикла по бинарникам добавить:
```bash
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
```
В разделе установки заменить строку `cp "$HERE/hooks/guard-main.sh" "$HERE/hooks/require-plan.sh" "$HERE/hooks/harness-check.sh" "$CC/hooks/"` на:
```bash
cp "$HERE/hooks/guard-main.sh" "$HERE/hooks/require-plan.sh" "$HERE/hooks/harness-check.sh" "$HERE/hooks/role-hint.sh" "$CC/hooks/"
```
Строку `cp -R "$HERE/adopt-project.sh" "$HERE/new-project.sh" "$HERE/project-template" "$CC/harness/"` заменить на:
```bash
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/"
```
После блока «2b. Skill диагностики» добавить:
```bash
# 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"
```
В Python-блоке слияния `settings.json` заменить строку
```python
ss[:] = [e for e in ss if not any("harness-check.sh" in x.get("command","") for x in e.get("hooks",[]))]
```
на
```python
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",[]))]
```
В финальном `echo "done: ..."` добавить `$CC/roles/, $CC/skills/role, $CC/agents/briefing.md, $CC/hooks/role-hint.sh`.
- [ ] **Step 4: Запустить тест**
Run: `bash tests/run.sh install` затем `bash tests/run.sh`
Expected: всё `ok`, `failed 0`.
- [ ] **Step 5: Коммит**
```bash
git add install.sh tests/test-install.sh
git commit -m "feat(install): роли, skill /role, briefing, role-hint, link-roles; --check видит их
Refs #2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>"
```
---
### Task 10: Документация — HARNESS.md «Роли», README, CHANGELOG 0.3.0, CLAUDE.md репозитория
**Files:**
- Modify: `docs/HARNESS.md` (новый раздел «Роли» после «Дисциплина между сессиями»)
- Modify: `README.md` (таблица «Состав», «Файлы», «Установка», новый раздел «Роли»)
- Modify: `CHANGELOG.md` (запись 0.3.0)
- Modify: `CLAUDE.md` (разделы «Архитектура», «Команды», «Тесты» — новые файлы и `tests/run.sh`)
- Modify: `docs/superpowers/specs/2026-10-11-roles-and-memory-design.md` (раздел «Decisions made without discussion» — решения плана)
**Interfaces:**
- Consumes: всё выше.
- Produces: документация, по которой владелец переносит роли в awp-ca (часть 2).
- [ ] **Step 1: HARNESS.md — раздел «Роли»**
Вставить перед `## Стоимость`:
```markdown
## Роли
Одна сессия — одна роль: `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` → `<dir>/.claude/agent-memory/<name>/`, коммитится). `adopt-project.sh --workspace` и `new-project.sh --workspace --role …` собирают это из шаблонов; `doctor.sh` проверяет ссылки и размеры.
Политика памяти (глобальный CLAUDE.md): состояние задачи — в трекере; уроки, решения владельца и рецепты — в `notes/` направления и памяти агентов (в git); в локальной auto-memory — только ловушки машины и индекс со ссылками. При закрытии файл auto-memory задачи сворачивается в строку `registry-closed.md`.
```
Также в таблице раздела «Хуки» добавить строку:
```markdown
| Подсказка роли по заголовку сессии | `role-hint.sh`, SessionStart (все источники) | читает `session_title`; если начинается с `dev|admin|analyst|qa` — одна строка-подсказка вызвать `/role`; ничего не блокирует |
```
И в «Дисциплина между сессиями» первое предложение дополнить: «Новая сессия начинается с роли (`/role`), затем `tea issues ls` и `openspec list`…».
- [ ] **Step 2: README.md**
В таблицу «Состав» добавить строку:
```markdown
| Роль сессии | `roles/`, skill `/role`, агент `briefing`, хук `role-hint.sh` | правила, файлы и память своей роли | этот пакет |
```
В блок «Файлы» добавить строки:
```
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
```
Новый раздел после «Существующий проект»:
```markdown
## 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`, раздел «Роли».
```
В «Установка» шаг 4 дополнить: «`bash tests/run.sh` — тесты пакета должны быть зелёными».
- [ ] **Step 3: CHANGELOG.md**
В начало после `# Changelog`:
```markdown
## 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 в репозитории направлений по таблице `| Каталог | Репозиторий | Роль |`.
- Шаблоны `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/`.
```
- [ ] **Step 4: CLAUDE.md репозитория**
В «Архитектура» добавить пункты:
```markdown
- `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`.
```
В «Команды» добавить первой строкой: `` - `bash tests/run.sh [имя]` — весь набор тестов. Зелёным считается только весь набор. ``
В «Тесты» заменить первый пункт на: «`tests/run.sh` — автоматические тесты источников (хуки через фиктивный JSON, скрипты во временных каталогах, размеры). Самотест хуков в `doctor.sh` проверяет установленные копии; перед ним `bash install.sh`.»
В «Архитектура» строку про `hooks/` дополнить: `role-hint.sh` (SessionStart, все источники): подсказка роли по `session_title`.
- [ ] **Step 5: Спека — решения плана**
В раздел `## Decisions made without discussion` спеки добавить:
```markdown
- 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 сохранена.
```
- [ ] **Step 6: Проверка**
Run: `bash tests/run.sh`; `grep -c '{{' global/CLAUDE.md roles/*.md` → все 0; `python3 -m json.tool hooks/settings.hooks.json >/dev/null`.
Expected: тесты зелёные, плейсхолдеров в не-шаблонных файлах нет.
- [ ] **Step 7: Коммит**
```bash
git add docs/HARNESS.md README.md CHANGELOG.md CLAUDE.md docs/superpowers/specs/2026-10-11-roles-and-memory-design.md
git commit -m "docs: роли в HARNESS.md и README, CHANGELOG 0.3.0, решения плана в спеке
Refs #2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>"
```
---
## Предусловия окружения
- Worktree `/Users/andrey.epifancev/projects/cc-skills/claude-harness/.wt/roles` на ветке `feat/2-roles-and-memory`, `codegraph init` выполнен.
- Task 1 запускает `claude -p` (платный вызов, модель haiku); остальные задачи — только bash и python3.
- Тесты пишут только во временные каталоги (`mktemp -d`) и в `CLAUDE_CONFIG_DIR`/`HOME` внутри них; настоящий `~/.claude` не трогается до `bash install.sh` по команде владельца после merge.
- В главной сессии не делать `cd` в основной checkout, пока работает контроллер.
## Что остаётся владельцу после merge
1. `bash install.sh` из main — ставит роли, skill, агента, хук.
2. Проверка 3 и 4 раздела 9 спеки (четыре сессии с `/context`, `/role admin` в dev-сессии) — в песочнице awp-ca, часть 2.
3. Решение по открытым вопросам 2 и 3 спеки (имена репозиториев в Gitea, алиасы в профиле).