113 KiB
Роли и память (часть 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
- Заголовок сессии с пробелами в начале, в верхнем регистре или с префиксом алиаса (
" Admin: спринт","cc-qa прогон") — владелец ждёт подсказку роли, а не тишину. Тест в Task 2. link-roles.shповторно и при уже существующем обычном файле на месте ссылки (.claude/roles/admin.md— файл, не симлинк) — второй запуск ничего не ломает, чужой файл не затирается, печатается предупреждение. Тест в Task 6.role-hint.shна пустом stdin, не-JSON и JSON безsession_title— молчит иexit 0, сессия стартует. Тест в Task 2.install.shдважды подряд —role-hint.shзарегистрирован вsettings.jsonровно один раз,harness-check.shне задвоился. Тест в Task 9.- Таблица репозиториев в 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>"
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):| Каталог | Репозиторий | Роль | |---|---|---| | 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>"
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:
# 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
bash install.shиз main — ставит роли, skill, агента, хук.- Проверка 3 и 4 раздела 9 спеки (четыре сессии с
/context,/role adminв dev-сессии) — в песочнице awp-ca, часть 2. - Решение по открытым вопросам 2 и 3 спеки (имена репозиториев в Gitea, алиасы в профиле).