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

113 KiB
Raw Blame History

Роли и память (часть 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 (не в репозитории)

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: Запустить агента из корня песочницы и попросить записать память
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: Проверить, куда легла память
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:

- Работает ли `memory: project` через симлинк каталога (проверка 1). Пока не проверено, в плане первой задачей.

на (подставить исход):

- Работает ли `memory: project` через симлинк каталога — проверено 2026-10-11 (план, Task 1): <A: да, MEMORY.md записан в целевой каталог симлинка; / B: нет, память легла в <путь>, в части 2 применяется запасной вариант 6.5; / C: не проверено: <причина>>.
  • Step 5: Убрать песочницу и закоммитить
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:

#!/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:

# 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:

#!/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
chmod +x hooks/role-hint.sh
  • Step 5: Зарегистрировать хук в блоке настроек

В hooks/settings.hooks.json заменить массив SessionStart:

    "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: Коммит
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:

# Формат файлов ролей (спека 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-правило) и уроками ретро. Текст файла:

# Роль: 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
# Роль: 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
# Роль: 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
# Роль: 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: Коммит
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:

# Размеры и обязательные строки глобального 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
# Правила сессии

## Роль
- Сессия работает в одной из ролей: `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 и раньше) и его новый адрес:

# Куда переехал глобальный 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: Коммит
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:

# 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
---
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
---
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: Коммит
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>"

Files:

  • Create: link-roles.sh
  • Create: tests/test-link-roles.sh

Interfaces:

  • Consumes: формат таблицы репозиториев в CLAUDE.md workspace (вводится здесь, используется шаблоном в Task 7 и doctor.sh в Task 8):

    | Каталог | Репозиторий | Роль |
    |---|---|---|
    | 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:

# 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
#!/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
chmod +x link-roles.sh
  • Step 4: Запустить тест

Run: bash tests/run.sh link-roles Expected: все ok, failed 0.

  • Step 5: Коммит
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:

# Шаблоны 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:

# {{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:

# Роль: dev — разработчик (проектный слой {{PROJECT_NAME}})
Эта роль заменяет предыдущую роль сессии целиком.

## Процесс
- {{Репозитории кода и их CLAUDE.md; worktree `.wt/<задача>/` и что в него линкуется; `.bounded`; venv/сборка; стенд.}}
- {{Источники истины для рулингов: модель данных, доменные правила.}}

## Читать на старте
- {{путь к инвариантам / ARCHITECTURE.md}}
- brief: `tea issues ls --state open`

## Не делать
- {{Инварианты проекта: что не мутируется, кто единственный писатель, чего не хранить.}}

## Делегировать
- SDD-контроллер → исполнение плана.

project-template/role-repo/CLAUDE.md:

# {{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):

# Роль: {{ROLE}} — проектный слой {{PROJECT_NAME}}
Эта роль заменяет предыдущую роль сессии целиком.

## Процесс
- {{Инструменты роли в этом проекте, трекер, где лежат артефакты.}}

## Читать на старте
- {{DIR}}/README.md
- brief: {{DIR}}/notes/

## Не делать
- {{Операционные ограничения проекта для этой роли.}}

## Делегировать
- {{агент → для чего, если есть}}

project-template/role-repo/notes/README.md:

# notes/

Уроки, решения владельца (с датой) и рецепты роли. Коммитятся в той же ветке, что и работа. Состояние задач сюда не пишется — оно в трекере.
  • Step 4: Добавить режим --workspace в adopt-project.sh

Вставить после строки 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
  • Step 5: Добавить режим --workspace в new-project.sh

Вставить после 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

Примечание для 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: Коммит
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):

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

[ -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" в блоке самотеста добавить:

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 на:

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 чеклист прочтения

В раздел «Машина» добавить:

- `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`, перенести туда.

В раздел «Проект» добавить:

- «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: Коммит
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>"

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:

# 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() после цикла по бинарникам добавить:

  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/" на:

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/" заменить на:

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 диагностики» добавить:

# 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 заменить строку

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",[]))]

В финальном 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: Коммит
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 — раздел «Роли»

Вставить перед ## Стоимость:

## Роли

Одна сессия — одна роль: `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`.

Также в таблице раздела «Хуки» добавить строку:

| Подсказка роли по заголовку сессии | `role-hint.sh`, SessionStart (все источники) | читает `session_title`; если начинается с `dev|admin|analyst|qa` — одна строка-подсказка вызвать `/role`; ничего не блокирует |

И в «Дисциплина между сессиями» первое предложение дополнить: «Новая сессия начинается с роли (/role), затем tea issues ls и openspec list…».

  • Step 2: README.md

В таблицу «Состав» добавить строку:

| Роль сессии | `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

Новый раздел после «Существующий проект»:

## 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 репозитория

В «Архитектура» добавить пункты:

- `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 спеки добавить:

- 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: Коммит
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, алиасы в профиле).