# Роли и память (часть 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/`, проектный из `/.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 `; ревьюер задачи проверяет 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 `# Роль: — <описание>`, строка 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/` в репозиторий направления. Ответ определяет, нужен ли запасной вариант 6.5 в части 2. Код не остаётся; остаётся запись в спеке. **Files:** - Modify: `docs/superpowers/specs/2026-10-11-roles-and-memory-design.md` (раздел 11 «Открытые вопросы», первый пункт) **Interfaces:** - Consumes: ничего. - Produces: факт «симлинк работает / не работает» и путь, куда реально легла память. - [ ] **Step 1: Собрать песочницу в scratchpad (не в репозитории)** ```bash SB=$(mktemp -d /tmp/memprobe.XXXX); echo "$SB" mkdir -p "$SB/ws/.claude/agents" "$SB/ws/.claude/agent-memory" "$SB/dir/.claude/agents" "$SB/dir/.claude/agent-memory/probe" cat > "$SB/dir/.claude/agents/probe.md" <<'EOF' --- name: probe description: Проверка памяти через симлинк. Использовать только по явной просьбе. model: haiku tools: Read, Write, Edit memory: project --- Ты проверяешь память. Выполни задание из промпта буквально и ответь одной строкой. EOF ln -s ../../../dir/.claude/agents/probe.md "$SB/ws/.claude/agents/probe.md" ln -s ../../../dir/.claude/agent-memory/probe "$SB/ws/.claude/agent-memory/probe" ls -la "$SB/ws/.claude/agents" "$SB/ws/.claude/agent-memory" ``` Ожидается: два симлинка, оба разрешаются (`readlink -f` даёт путь внутри `$SB/dir`). - [ ] **Step 2: Запустить агента из корня песочницы и попросить записать память** ```bash cd "$SB/ws" && claude -p --agent probe "Запиши в свою память (MEMORY.md) строку «probe-ok 2026-10-11» и ответь «готово»." 2>&1 | tail -5 ``` Если `--agent probe` не находит агента (сообщение «unknown agent»), повторить с `claude -p "Используй агента probe: запиши в его память строку «probe-ok 2026-10-11»"`. - [ ] **Step 3: Проверить, куда легла память** ```bash echo '--- через симлинк'; find "$SB/dir/.claude/agent-memory" -type f -exec sh -c 'echo "$1"; cat "$1"' _ {} \; echo '--- мимо симлинка'; find "$SB/ws/.claude" -path '*agent-memory*' -type f -not -path "$SB/ws/.claude/agent-memory/probe/*" echo '--- auto-memory проекта'; ls -d "$HOME/.claude/projects/"*memprobe* 2>/dev/null ``` Ожидается один из трёх исходов: - A. Файл `MEMORY.md` с `probe-ok` лежит в `$SB/dir/.claude/agent-memory/probe/` — симлинк работает. - B. Файл лежит в `$SB/ws/.claude/agent-memory/probe/` как обычный каталог (симлинк заменён) или в другом месте — симлинк не работает, нужен запасной вариант 6.5. - C. Агент не записал ничего (память выключена или агент не вызвался) — повторить Step 2 с `/memory` в интерактивной сессии; если и так ничего, зафиксировать как «не проверено, причина …». - [ ] **Step 4: Записать результат в спеку** В `docs/superpowers/specs/2026-10-11-roles-and-memory-design.md` заменить первый пункт раздела 11: ```markdown - Работает ли `memory: project` через симлинк каталога (проверка 1). Пока не проверено, в плане первой задачей. ``` на (подставить исход): ```markdown - Работает ли `memory: project` через симлинк каталога — проверено 2026-10-11 (план, Task 1): , в части 2 применяется запасной вариант 6.5; / C: не проверено: <причина>>. ``` - [ ] **Step 5: Убрать песочницу и закоммитить** ```bash rm -rf "$SB" git add docs/superpowers/specs/2026-10-11-roles-and-memory-design.md git commit -m "docs(spec): результат проверки памяти агента через симлинк Refs #2 Co-Authored-By: Claude Fable 5.1 " ``` --- ### 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] Роль сессии: . Вызови /role до первого ответа.` или ничего; всегда `exit 0`. - [ ] **Step 1: Написать прогон тестов** `tests/run.sh`: ```bash #!/usr/bin/env bash # Тесты обвязки: bash, без зависимостей, работают с источниками в репозитории. # Запуск: bash tests/run.sh [подстрока имени файла теста] set -uo pipefail HERE="$(cd "$(dirname "$0")" && pwd)"; ROOT="$(dirname "$HERE")" pass=0; fail=0 ok(){ pass=$((pass+1)); printf ' ok %s\n' "$1"; } fail(){ fail=$((fail+1)); printf ' FAIL %s\n %s\n' "$1" "$2"; } assert_eq(){ [ "$2" = "$3" ] && ok "$1" || fail "$1" "получено: '$2', ожидалось: '$3'"; } assert_file(){ [ -e "$2" ] && ok "$1" || fail "$1" "нет файла $2"; } assert_grep(){ grep -qE -- "$2" "$3" 2>/dev/null && ok "$1" || fail "$1" "в $3 нет /$2/"; } assert_max_bytes(){ local n; n=$(wc -c < "$2" | tr -d ' '); [ "$n" -le "$3" ] && ok "$1 ($n Б)" || fail "$1" "$n Б > $3"; } assert_symlink(){ [ -L "$2" ] && [ -e "$2" ] && ok "$1" || fail "$1" "$2 не симлинк или битый"; } for t in "$HERE"/test-*.sh; do [ -n "${1:-}" ] && [[ "$(basename "$t")" != *"$1"* ]] && continue echo "## $(basename "$t")" TMP=$(mktemp -d); export TMP ROOT # shellcheck disable=SC1090 . "$t" rm -rf "$TMP" done echo "passed $pass, failed $fail" [ "$fail" = 0 ] ``` - [ ] **Step 2: Написать падающий тест хука** `tests/test-role-hint.sh`: ```bash # role-hint.sh: подсказка роли по session_title, никогда не блокирует. H="$ROOT/hooks/role-hint.sh" assert_file "role-hint.sh существует" "$H" run(){ printf '%s' "$1" | bash "$H" 2>/dev/null; echo "rc=$?"; } assert_eq "admin: … → подсказка" "$(run '{"session_title":"admin: спринт 3","source":"startup"}')" \ "[claude-harness] Роль сессии: admin. Вызови /role admin до первого ответа. rc=0" assert_eq "qa без двоеточия → подсказка" "$(run '{"session_title":"qa прогон Q1"}')" \ "[claude-harness] Роль сессии: qa. Вызови /role qa до первого ответа. rc=0" assert_eq "регистр и пробелы в начале" "$(run '{"session_title":" Admin — Jira"}')" \ "[claude-harness] Роль сессии: admin. Вызови /role admin до первого ответа. rc=0" assert_eq "префикс алиаса cc-" "$(run '{"session_title":"cc-analyst ТЗ v4"}')" \ "[claude-harness] Роль сессии: analyst. Вызови /role analyst до первого ответа. rc=0" assert_eq "чужое слово → тишина" "$(run '{"session_title":"harness: роли"}')" "rc=0" assert_eq "слово с продолжением (devops) → тишина" "$(run '{"session_title":"devops"}')" "rc=0" assert_eq "без заголовка → тишина" "$(run '{"cwd":"/tmp","source":"resume"}')" "rc=0" assert_eq "пустой stdin → тишина, exit 0" "$(run '')" "rc=0" assert_eq "не JSON → тишина, exit 0" "$(run 'not json at all')" "rc=0" assert_grep "settings.hooks.json регистрирует role-hint" 'role-hint\.sh' "$ROOT/hooks/settings.hooks.json" ``` - [ ] **Step 3: Запустить, убедиться, что падает** Run: `bash tests/run.sh role-hint` Expected: `FAIL role-hint.sh существует` и далее FAIL по всем случаям; итог `failed 11`. - [ ] **Step 4: Написать хук** `hooks/role-hint.sh`: ```bash #!/usr/bin/env bash # SessionStart hook (все источники): если заголовок сессии начинается с имени роли, напомнить вызвать /role. # Ничего не блокирует: любой сбой → тихий exit 0. Заголовок приходит в поле session_title (claude -n "<роль> …", /rename). set -uo pipefail input=$(cat 2>/dev/null || true) [ -n "$input" ] || exit 0 title=$(printf '%s' "$input" | python3 -c 'import sys,json try: print((json.load(sys.stdin) or {}).get("session_title") or "") except Exception: print("")' 2>/dev/null) || exit 0 [ -n "$title" ] || exit 0 # первое слово: нижний регистр, без ведущих пробелов и префикса алиаса cc-; только латинские буквы. # LC_ALL=C — чтобы BSD tr/sed работали побайтово и не спотыкались о кириллицу в заголовке. first=$(printf '%s' "$title" | LC_ALL=C tr '[:upper:]' '[:lower:]' | LC_ALL=C sed -E 's/^[[:space:]]*(cc-)?([a-z]*).*$/\2/') case "$first" in dev|admin|analyst|qa) echo "[claude-harness] Роль сессии: $first. Вызови /role $first до первого ответа." ;; esac exit 0 ``` ```bash chmod +x hooks/role-hint.sh ``` - [ ] **Step 5: Зарегистрировать хук в блоке настроек** В `hooks/settings.hooks.json` заменить массив `SessionStart`: ```json "SessionStart": [ { "matcher": "startup", "hooks": [ { "type": "command", "command": "\"$HOME/.claude/hooks/harness-check.sh\"" } ] }, { "hooks": [ { "type": "command", "command": "\"$HOME/.claude/hooks/role-hint.sh\"" } ] } ] ``` Запись без `matcher` срабатывает на всех источниках (startup, resume, clear, compact, fork) — так документировано для хуков Claude Code. - [ ] **Step 6: Запустить тест, убедиться, что проходит** Run: `bash tests/run.sh role-hint` Expected: 11 `ok`, `failed 0`. Также `python3 -m json.tool hooks/settings.hooks.json >/dev/null` без ошибок. - [ ] **Step 7: Коммит** ```bash git add tests/run.sh tests/test-role-hint.sh hooks/role-hint.sh hooks/settings.hooks.json git commit -m "feat(hooks): role-hint.sh — подсказка роли по session_title; tests/run.sh Refs #2 Co-Authored-By: Claude Fable 5.1 " ``` --- ### 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/.md`. Строки `brief:` в «Читать на старте» — вход агента `briefing` (Task 5). - [ ] **Step 1: Написать падающий тест формата** `tests/test-roles.sh`: ```bash # Формат файлов ролей (спека 3.1), одинаковый для общего и проектного слоя. check_role_file(){ # $1 = путь, $2 = ожидаемое имя роли local f="$1" r="$2" h assert_file "$f существует" "$f" || return assert_eq "$f: строка 1" "$(sed -n '1p' "$f" | sed -E 's/^(# Роль: [a-z]+) — .*/\1/')" "# Роль: $r" assert_eq "$f: строка 2" "$(sed -n '2p' "$f")" "Эта роль заменяет предыдущую роль сессии целиком." h=$(grep -E '^## ' "$f" | tr '\n' '|') assert_eq "$f: четыре раздела по порядку" "$h" "## Процесс|## Читать на старте|## Не делать|## Делегировать|" } for r in dev admin analyst qa; do check_role_file "$ROOT/roles/$r.md" "$r"; done assert_grep "dev: чеклист закрытия перенесён" 'openspec archive' "$ROOT/roles/dev.md" assert_grep "dev: архив всегда отдельным PR" 'всегда отдельным PR' "$ROOT/roles/dev.md" assert_grep "dev: не cd при фоновом контроллере" 'cd' "$ROOT/roles/dev.md" assert_grep "dev: bounded обновляет спеку при смене API" 'спека обновляется в том же PR' "$ROOT/roles/dev.md" assert_grep "dev: маркировка R-n" 'R-n' "$ROOT/roles/dev.md" assert_grep "admin: Jira вместо Gitea" 'Jira' "$ROOT/roles/admin.md" assert_grep "analyst: код не пишется" 'не пиш' "$ROOT/roles/analyst.md" assert_grep "qa: код не меняется" 'не меня' "$ROOT/roles/qa.md" for r in dev admin analyst qa; do assert_grep "$r: есть строка brief:" '^- brief:' "$ROOT/roles/$r.md"; done ``` - [ ] **Step 2: Запустить, убедиться, что падает** Run: `bash tests/run.sh roles` Expected: FAIL «roles/dev.md существует» и остальные; `failed` > 0. - [ ] **Step 3: Написать `roles/dev.md`** Содержание — нынешний `global/CLAUDE.md` целиком по смыслу, переложенный в четыре раздела, с правками по решениям владельца 11.10.2026 (архив отдельным PR; п. 4 через allow-правило) и уроками ретро. Текст файла: ````markdown # Роль: dev — разработчик Эта роль заменяет предыдущую роль сессии целиком. ## Процесс ### Инструменты и их роли - superpowers ведёт процесс внутри одной фичи: brainstorming → writing-plans → исполнение → ревью → finishing-a-development-branch. - Gitea (CLI `tea`) хранит задачи между сессиями. Одна фича = одна issue = одна ветка = один PR. Issue заводит агент (`tea issues create`) из формулировки цели, до brainstorming. Порядок: сначала worktree на временной ветке (хук не даёт писать на main), затем issue, затем ветка переименовывается в `feat/-`. - OpenSpec хранит требования. `openspec/specs/` — текущее поведение системы, `openspec/changes//` — дельта текущей фичи. Спека — единственный источник истины при рулингах. - 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//`. В поле **Spec** плана указывается этот путь. Перед architectural brainstorming прочитать релевантные файлы в `openspec/specs/` и ARCHITECTURE.md, если есть. ### Исполнение - Работа только в worktree. Ветка называется по issue: `feat/-`. Пути 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.`, переписывать историю нельзя (force-push запрещён). - План содержит раздел «Предусловия окружения» (стенд погашен, порты свободны, ключи не в окружении), контроллер проверяет его один раз перед первой задачей. ### Завершение - finishing-a-development-branch создаёт PR в Gitea (`tea pulls create`) со ссылкой на issue. Комментарии к PR — через receiving-code-review: читать из Gitea, править в той же ветке, отвечать в PR. Merge — только по явной команде человека. - После merge по команде «закрывай» выполняется весь чеклист без остановок: 1. `openspec archive ` — всегда отдельным PR (ветка `chore/archive-` из main, `tea pulls create`, merge по команде человека). Прямых коммитов в main нет, исключений нет (решение владельца 11.10.2026). 2. Issue закрывается (`tea issues close `, комментарий со ссылкой на PR) только если все её пункты выполнены. Если PR закрывает часть пунктов — issue остаётся открытой, в неё пишется комментарий: какие маркеры закрыты, каким PR и коммитом. 3. Удалить SDD workspace (`.superpowers/sdd//`). 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 не создавать. 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//design.md`, раздел `## Decisions made without discussion`: дата, решение, обоснование, цена ошибки. Ledger удаляется, чат теряется; design.md архивируется со спекой. ### Маркировка находок и решений - Каждая находка и каждое решение в ledger, отчётах, PR и design.md несёт маркер источника; маркер присваивается один раз и не меняется при переносе. - `T.` — 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 `, `deferred` или `rejected <причина>`. Находка без статуса в финальном отчёте — ошибка контроллера. - Финальный отчёт контроллера содержит таблицу всех находок с маркером и статусом; `deferred` попадают в описание PR и в follow-up issue (чеклист, п. 6). ### Стиль - Отчёты сабагентов, рулинги и ledger не сжимать. Caveman — только по явной команде. ## Читать на старте - `tea issues ls` — открытые задачи и follow-up. - `openspec list` — активные change. - `docs/superpowers/plans/` в worktree — незавершённый план, если есть; ledger `.superpowers/sdd/`. - brief: `git log --oneline -15` по каждому репозиторию из таблицы workspace с ролью dev. - brief: `tea issues ls --state open` — активные задачи и follow-up issues. ## Не делать - Не писать на main и на detached HEAD; не обходить хуки (`echo >`, API Gitea вместо `tea`, правка `settings.json` ради «чтобы прошло»). Если хук мешает — сказать владельцу. - Не делать merge PR и force-push. - Не вести задачи Jira и документы заказчика — это роль admin (`/role admin`). - Не прогонять сценарии на стенде со сверкой макетов — это роль qa. - Не хранить состояние задачи в auto-memory: оно в issue и PR. ## Делегировать - SDD-контроллер (superpowers, фоновый сабагент на sonnet) → исполнение плана; implementer-ы haiku/sonnet, финальное ревью opus. - `briefing` → выжимка строк `brief:` при `/role dev`. ```` - [ ] **Step 4: Написать `roles/admin.md`** ````markdown # Роль: admin — руководитель/администратор проекта Эта роль заменяет предыдущую роль сессии целиком. ## Процесс - Задачи ведутся в трекере заказчика (Jira) или в репозитории направления (`admin/`). Gitea-issue и OpenSpec change для задач этой роли не заводятся. - Вход в задачу — superpowers:brainstorming; по умолчанию задачи bounded: дизайн в чате, затем работа. Писать планы только для многошаговых работ со скриптами. - Код (скрипты выгрузок, генераторы CSV, просмотрщики) — только в репозитории направления, TDD, коммит на ветке, PR по правилам репозитория. Код в репозиториях разработки не трогать. - Тексты для внешних людей (заказчик, команда, Jira) самодостаточны: без внутренних маркеров (`T1.1`, `R-5`), номеров PR, путей к файлам и упоминаний агентов. Артефакты для передачи кладутся в `deliverables/` репозитория направления. - Уроки, решения владельца и рецепты роли — в `admin/notes/`, коммит в той же ветке, что и работа. Состояние задач — в Jira или в CSV репозитория, не в auto-memory. ## Читать на старте - `CLAUDE.md` workspace — таблица репозиториев и источники истины. - `admin/README.md` — что лежит в репозитории направления и как им пользоваться. - brief: `admin/notes/` — уроки и решения владельца. - brief: последний каталог `admin/jira-sync/runs/<дата>/` — что делалось в прошлый раз. ## Не делать - Не менять код репозиториев разработки и не заводить им issue — передать владельцу или `/role dev`. - Не выполнять записывающие операции против реальной Jira, если проектный файл роли это не разрешает явно. - Не подключать процесс superpowers→Gitea→OpenSpec к задачам этой роли. - Не писать в auto-memory состояние спринта, задач и выгрузок. ## Делегировать - `briefing` → выжимка строк `brief:` при `/role admin`. - `jira-admin` (проектный агент, если есть) → сухие прогоны discover/plan, перегенерация CSV, сверка Ганта, черновики описаний задач. ```` - [ ] **Step 5: Написать `roles/analyst.md`** ````markdown # Роль: analyst — аналитик требований Эта роль заменяет предыдущую роль сессии целиком. ## Процесс - Работа с требованиями: спеки, открытые вопросы (OQ), решения владельца, сверка ТЗ с кодом и с тем, что показывает стенд. - Код не пишется и не правится. Если сверка показала расхождение кода с ТЗ — это находка для владельца, не правка. - Публикация в витрину `docs/` — через PR в репозиторий витрины; черновики и протоколы живут в репозитории направления (`analysis/`). - Противоречия между документами не решаются молча: фиксируются как открытый вопрос с вариантами, решение принимает владелец и записывается в `analysis/notes/decisions.md` с датой. - Вход в задачу — superpowers:brainstorming; задачи по умолчанию bounded. ## Читать на старте - `CLAUDE.md` workspace — таблица репозиториев и источники истины. - Проектный файл роли — какие документы считаются источниками истины и как оформляются OQ. - brief: `analysis/notes/decisions.md` — решения владельца. - brief: список открытых OQ (путь в проектном файле роли). ## Не делать - Не писать и не править код, миграции, тесты. - Не менять DBML, модель данных и доменные правила без решения владельца, записанного в `decisions.md`. - Не переписывать исходные документы заказчика (`docs/source/`), только ссылаться на них. ## Делегировать - `briefing` → выжимка строк `brief:` при `/role analyst`. ```` - [ ] **Step 6: Написать `roles/qa.md`** ````markdown # Роль: qa — тестирование Эта роль заменяет предыдущую роль сессии целиком. ## Процесс - Стенд, прогоны сценариев, сверка каждого экрана с макетом, гайды и инструкции пользователя. - Код репозиториев разработки не меняется. Запись только в репозиторий направления (`qa/`): сценарии, журналы прогонов `qa/runs/<дата>/`, скриншоты, находки. - Находки — списком со скриншотами и шагами воспроизведения, по одной на расхождение; issue в кодовый репозиторий заводит dev по решению владельца. - Перед прогоном проверить предусловия стенда из проектного файла роли (порты, токен, данные); если стенд занят другой сессией — не гасить, сказать владельцу. - Уроки и ловушки стенда — в `qa/notes/`, коммит в той же ветке, что и работа. ## Читать на старте - `CLAUDE.md` workspace — таблица репозиториев. - Проектный файл роли — стенд, Playwright, макеты, чек-лист UI. - brief: `qa/notes/` — ловушки стенда и рецепты. - brief: последний каталог `qa/runs/<дата>/` — что проверялось в прошлый раз и контрольные цифры. ## Не делать - Не менять код, миграции, конфиги стенда. - Не гасить и не перезапускать стенд и его сервисы — это делает владелец. - Не заводить issue в кодовые репозитории без решения владельца. ## Делегировать - `briefing` → выжимка строк `brief:` при `/role qa`. - `qa-runner` (проектный агент, если есть) → прогон сценария на стенде, сверка экранов с PNG макета, список расхождений в `qa/runs/<дата>/`. ```` - [ ] **Step 7: Запустить тест** Run: `bash tests/run.sh roles` Expected: все `ok`, `failed 0`. - [ ] **Step 8: Коммит** ```bash git add tests/test-roles.sh roles/ git commit -m "feat(roles): общие файлы ролей dev/admin/analyst/qa (формат 3.1) Refs #2 Co-Authored-By: Claude Fable 5.1 " ``` --- ### Task 4: Глобальный `global/CLAUDE.md` ≤ 3 КБ и таблица адресов **Files:** - Modify: `global/CLAUDE.md` (полная замена) - Create: `docs/retro/2026-10-11-claude-md-addresses.md` - Create: `tests/test-sizes.sh` **Interfaces:** - Consumes: `roles/dev.md` из Task 3 — туда переехал процесс. - Produces: глобальные правила, которые остаются в каждой сессии любой роли: язык, хуки, worktree, маркировка (кратко), политика памяти (спека 7.1), правило выбора роли (спека 4), «процесс роли — в /role». `doctor.sh` (Task 8) проверяет в нём строки `R-n`, `projectPath` и `/role`. - [ ] **Step 1: Написать падающий тест размеров и содержания** `tests/test-sizes.sh`: ```bash # Размеры и обязательные строки глобального CLAUDE.md (спека 1, 4, 7.1, 8). G="$ROOT/global/CLAUDE.md" assert_max_bytes "global/CLAUDE.md ≤ 3 КБ" "$G" 3072 assert_grep "правило выбора роли" 'Роль: admin; если не так' "$G" assert_grep "/role упомянут" '/role' "$G" assert_grep "политика памяти: трекер" 'Состояние задачи живёт в трекере' "$G" assert_grep "политика памяти: notes/" 'notes/' "$G" assert_grep "политика памяти: auto-memory" 'auto-memory' "$G" assert_grep "маркировка R-n" 'R-n' "$G" assert_grep "codegraph projectPath" 'projectPath' "$G" assert_grep "хук [claude-harness]" '\[claude-harness\]' "$G" assert_grep "worktree" 'worktree' "$G" assert_file "таблица адресов" "$ROOT/docs/retro/2026-10-11-claude-md-addresses.md" ``` - [ ] **Step 2: Запустить, убедиться, что падает** Run: `bash tests/run.sh sizes` Expected: FAIL «global/CLAUDE.md ≤ 3 КБ» (сейчас 9155 Б) и FAIL по строкам роли/памяти. - [ ] **Step 3: Переписать `global/CLAUDE.md`** ```markdown # Правила сессии ## Роль - Сессия работает в одной из ролей: `dev`, `admin`, `analyst`, `qa`. Правила роли загружает `/role `; новая роль заменяет предыдущую целиком. - Подсказка `[claude-harness] Роль сессии: ` от хука при старте — вызвать `/role ` до первого ответа. - Без подсказки: если роль очевидна из первого сообщения (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), не в памяти. - В репозиторий направления (`/notes/`, память агента) пишутся уроки, решения владельца, рецепты. Коммит в той же ветке, что и работа. - В локальную auto-memory — только ловушки этой машины и обвязки и индекс со ссылками на `notes/` направлений. ## Маркировка - Каждая находка и каждое решение несёт маркер источника (`T.`, `C-n`/`I-n`/`M-n`, `R-n`, `O-n`, `H-n`, `RT-n`) и статус `fixed ` / `deferred` / `rejected <причина>`. Маркер присваивается один раз. Подробности — в роли dev. ## Стиль - Отвечать по-русски. Отчёты сабагентов, рулинги и ledger не сжимать; caveman — только по явной команде. ``` - [ ] **Step 4: Написать таблицу адресов** `docs/retro/2026-10-11-claude-md-addresses.md` — каждый абзац старого `global/CLAUDE.md` (версия 0.2.0, коммит d2496aa и раньше) и его новый адрес: ```markdown # Куда переехал глобальный CLAUDE.md (0.2.0 → 0.3.0) Проверка критерия спеки «ни одно правило не потеряно». Старый файл — `global/CLAUDE.md` до коммита этой ветки (9155 Б). | Старый абзац (раздел → пункт) | Новый адрес | |---|---| | Роли инструментов → superpowers ведёт процесс | `roles/dev.md` → Процесс → Инструменты и их роли | | Роли инструментов → Gitea, одна фича = одна issue, порядок старта | `roles/dev.md` → Инструменты и их роли | | Роли инструментов → OpenSpec | `roles/dev.md` → Инструменты и их роли | | Роли инструментов → Ledger superpowers | `roles/dev.md` → Инструменты и их роли | | Маршрутизация → сообщение `[claude-harness]`, adopt-project.sh | `global/CLAUDE.md` → Хуки и ветки (сообщение); `roles/dev.md` → Маршрутизация (скрипт) | | Маршрутизация → bounded-задача | `roles/dev.md` → Маршрутизация (дополнено: спека при смене API, verification-before-completion) | | Маршрутизация → architectural-задача, спека в openspec/changes | `roles/dev.md` → Маршрутизация | | Маршрутизация → читать openspec/specs и ARCHITECTURE.md | `roles/dev.md` → Маршрутизация | | Маршрутизация → codegraph, projectPath, пустой ответ | `global/CLAUDE.md` → Хуки и ветки (init, projectPath); `roles/dev.md` → Инструменты (implementer, пустой ответ, замер) | | Исполнение → только в worktree, имя ветки | `roles/dev.md` → Исполнение | | Исполнение → SDD по умолчанию, контроллер на sonnet, executing-plans до 5 задач | `roles/dev.md` → Исполнение (дополнено: режим не спрашивать) | | Исполнение → модели исполнителей | `roles/dev.md` → Исполнение | | Завершение → finishing-a-development-branch, receiving-code-review, merge человеком | `roles/dev.md` → Завершение | | Завершение → чеклист закрытия п. 1 (архив в main) | `roles/dev.md` → Завершение п. 1 — изменено: архив всегда отдельным PR (решение владельца 11.10.2026) | | Завершение → чеклист п. 2–3 | `roles/dev.md` → Завершение п. 2–3 | | Завершение → чеклист п. 4 (удаление веток) | `roles/dev.md` → Завершение п. 4 — дополнено: из worktree, allow-правило | | Завершение → чеклист п. 5–7 | `roles/dev.md` → Завершение п. 5, 6, 8 (новый п. 7 — память, спека 7.1) | | Завершение → неприменимое к bounded пропускается | `roles/dev.md` → Завершение | | Завершение → любой другой push в main только через PR | `roles/dev.md` → Завершение п. 1 (без исключений) | | Завершение → «Rulings I made» в PR | `roles/dev.md` → Завершение | | Завершение → Decisions made without discussion | `roles/dev.md` → Завершение | | Маркировка → список маркеров | `global/CLAUDE.md` → Маркировка (кратко); `roles/dev.md` → Маркировка (полностью, + `RT-n`) | | Маркировка → статусы находок | `global/CLAUDE.md` → Маркировка; `roles/dev.md` → Маркировка | | Маркировка → таблица в финальном отчёте, deferred в PR и follow-up | `roles/dev.md` → Маркировка | | Стиль → не сжимать, caveman по команде | `global/CLAUDE.md` → Стиль | Новое, чего не было: раздел «Роль» (спека 4), раздел «Память» (спека 7.1), запрет обхода хуков (ретро, `feedback-no-harness-bypass`), `cd` при фоновом контроллере (RT-2), абсолютные пути worktree, trailer коммита (RT-9), предусловия окружения (RT-8). ``` - [ ] **Step 5: Запустить тесты** Run: `bash tests/run.sh` Expected: все файлы тестов `ok`, `failed 0`. Вывести `wc -c global/CLAUDE.md` — число ≤ 3072. - [ ] **Step 6: Коммит** ```bash git add global/CLAUDE.md docs/retro/2026-10-11-claude-md-addresses.md tests/test-sizes.sh git commit -m "feat(global): CLAUDE.md ≤ 3 КБ — роль, хуки, память, маркировка; таблица адресов Refs #2 Co-Authored-By: Claude Fable 5.1 " ``` --- ### 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/.md` и `/.claude/roles/.md`. - Produces: skill `role` (ставится в `~/.claude/skills/role/`), агент `briefing` (ставится в `~/.claude/agents/briefing.md`). Install (Task 9) копирует их по этим путям. - [ ] **Step 1: Написать падающий тест frontmatter** `tests/test-skill-agent.sh`: ```bash # Skill /role и агент briefing: frontmatter и обязательные шаги (спека 5, 6.1). S="$ROOT/skills/role/SKILL.md"; A="$ROOT/agents/briefing.md" assert_file "SKILL.md существует" "$S" assert_eq "skill: первая строка ---" "$(sed -n '1p' "$S")" "---" assert_grep "skill: name: role" '^name: role$' "$S" assert_grep "skill: argument-hint" '^argument-hint: dev[|]admin[|]analyst[|]qa$' "$S" assert_grep "skill: user-invocable" '^user-invocable: true$' "$S" assert_grep "skill: читает общий слой" '~/.claude/roles/' "$S" assert_grep "skill: читает проектный слой" '\.claude/roles/' "$S" assert_grep "skill: собирает brief:" 'brief:' "$S" assert_grep "skill: запускает briefing в фоне" 'subagent_type: briefing' "$S" assert_grep "skill: без аргумента — список ролей" 'без аргумента' "$S" assert_grep "skill: использует \$0" '\$0' "$S" assert_file "briefing.md существует" "$A" assert_grep "agent: name" '^name: briefing$' "$A" assert_grep "agent: model sonnet" '^model: sonnet$' "$A" assert_grep "agent: tools" '^tools: Read, Glob, Grep, Bash$' "$A" assert_eq "agent: без memory" "$(grep -c '^memory:' "$A")" "0" for h in 'Активные задачи' 'Решения за последние дни' 'Открытые вопросы' 'Ловушки'; do assert_grep "agent: раздел $h" "$h" "$A"; done assert_grep "agent: до 60 строк" '60 строк' "$A" ``` - [ ] **Step 2: Запустить, убедиться, что падает** Run: `bash tests/run.sh skill-agent` Expected: FAIL по всем пунктам. - [ ] **Step 3: Написать `skills/role/SKILL.md`** ````markdown --- name: role description: Переключить сессию в роль dev | admin | analyst | qa — загрузить общий и проектный файлы роли, её память и выжимку briefing. Вызывать по подсказке хука «Роль сессии», по правилу выбора роли из глобального CLAUDE.md или по просьбе владельца. argument-hint: dev|admin|analyst|qa user-invocable: true --- # /role — роль сессии Аргумент: `$0` — имя роли. Workspace — текущий каталог сессии (cwd), не git-корень. ## Без аргумента Если `$0` пуст: напечатать текущую роль (та, чей файл читался последним в этой сессии; если ни одного — «роль не выбрана») и список доступных: для каждой из `dev admin analyst qa` — есть ли `~/.claude/roles/.md` и `./.claude/roles/.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: . Источники:» и список строк `brief:` без префикса, по одной на строку. Не ждать результата. 4. Ответить ровно пятью строками: - Роль: `$0` — <описание из первой строки файла роли>. - Процесс: <одна строка из раздела «Процесс» — что действует по умолчанию>. - Прочитано: <перечень файлов обоих слоёв и файлов памяти, через запятую>. - Не делать: <раздел «Не делать» одной строкой>. - Ждёт решения владельца: <из памяти роли; если нет — «ничего»>. Когда придёт выжимка `briefing` — добавить её одним сообщением как есть, без пересказа и без предложений действий. ## Не делать - Не читать файлы других ролей и их разделы памяти. - Не выполнять записывающие команды из «Читать на старте». - Не спрашивать подтверждения роли, если она пришла из подсказки хука или аргумента. ```` - [ ] **Step 4: Написать `agents/briefing.md`** ````markdown --- name: briefing description: Выжимка состояния для роли при /role — активные задачи, решения за последние дни, открытые вопросы, ловушки. Только чтение. Запускается skill-ом /role в фоне, не вызывать для другой работы. model: sonnet tools: Read, Glob, Grep, Bash --- Ты готовишь выжимку для сессии в роли, названной в промпте. Тебе дан workspace (cwd) и список источников: пути (файлы или каталоги, относительно workspace) и команды. Правила: - Выполняй только читающие команды: `tea issues ls`, `tea pulls ls`, `openspec list`, `git log`, `git status`, `ls`, `cat`, `find`, `grep`. Любую другую команду из списка пропусти и назови в разделе «Ловушки» как «не выполнено: <команда>». - Каталог читай так: `MEMORY.md`/`README.md`/`decisions.md`, если есть, затем 5 самых свежих файлов по дате изменения. - Не редактируй файлы, не предлагай действий, не оценивай. Ответ — до 60 строк, ровно в такой форме, без вступления: **Активные задачи** — открытые issue/PR/срезы с номерами и состоянием, по строке на задачу. **Решения за последние дни** — решения владельца за 14 дней с датами (из notes/, decisions.md, памяти). **Открытые вопросы** — что ждёт решения владельца, с источником. **Ловушки** — предупреждения из уроков и памяти, актуальные для этой роли, по строке. Если раздел пуст — строка «— нет». Если источник не найден — строка «не найдено: <путь>» в «Ловушках». ```` - [ ] **Step 5: Запустить тест** Run: `bash tests/run.sh skill-agent` Expected: все `ok`. - [ ] **Step 6: Коммит** ```bash git add skills/role/SKILL.md agents/briefing.md tests/test-skill-agent.sh git commit -m "feat: skill /role и агент briefing Refs #2 Co-Authored-By: Claude Fable 5.1 " ``` --- ### Task 6: `link-roles.sh` **Files:** - Create: `link-roles.sh` - Create: `tests/test-link-roles.sh` **Interfaces:** - Consumes: формат таблицы репозиториев в CLAUDE.md workspace (вводится здесь, используется шаблоном в Task 7 и `doctor.sh` в Task 8): ```markdown | Каталог | Репозиторий | Роль | |---|---|---| | arm-core/ | RSHB/arm-core | dev | | admin/ | RSHB/arm-ka-admin | admin | | docs/ | RSHB/arm-ka-docs | — | ``` Строка учитывается, если третья ячейка — `admin`, `analyst` или `qa`; первая ячейка — каталог относительно workspace, завершающий `/` необязателен. Строки с `dev`, `—` и любым другим значением пропускаются. - Produces: `link-roles.sh [workspace] [--map = ...]`. В `/.claude/` создаёт симлинки: `roles/.md → ../..//.claude/roles/.md`; `agents/.md → ../..//.claude/agents/.md` для каждого файла агента в репозитории направления; `agent-memory/ → ../..//.claude/agent-memory/` для каждого агента (целевой каталог создаётся, если его нет). Идемпотентен; печатает `создан`/`есть`/`ПРОПУЩЕН` по каждой ссылке; выход 0, если нет ошибок разбора. `--map` добавляет пары к таблице или заменяет её, если CLAUDE.md нет. - [ ] **Step 1: Написать падающий тест** `tests/test-link-roles.sh`: ```bash # link-roles.sh: симлинки ролей, агентов и памяти из workspace в репозитории направлений. L="$ROOT/link-roles.sh" assert_file "link-roles.sh существует" "$L" W="$TMP/ws"; mkdir -p "$W/admin/.claude/roles" "$W/admin/.claude/agents" "$W/qa/.claude/roles" "$W/docs" "$W/arm-core" echo '# Роль: admin — проектный слой' > "$W/admin/.claude/roles/admin.md" printf -- '---\nname: jira-admin\nmodel: sonnet\n---\n' > "$W/admin/.claude/agents/jira-admin.md" echo '# Роль: qa — проектный слой' > "$W/qa/.claude/roles/qa.md" cat > "$W/CLAUDE.md" <<'EOF' # Проект | Каталог | Репозиторий | Роль | |---|---|---| | arm-core/ | RSHB/arm-core | dev | | admin/ | RSHB/arm-ka-admin | admin | | qa | RSHB/arm-ka-qa | qa | | docs/ | RSHB/arm-ka-docs | — | | source/ | — | | EOF out=$(bash "$L" "$W" 2>&1); rc=$? assert_eq "первый запуск: exit 0" "$rc" "0" assert_symlink "roles/admin.md → admin/" "$W/.claude/roles/admin.md" assert_symlink "roles/qa.md → qa/ (без завершающего /)" "$W/.claude/roles/qa.md" assert_eq "roles/admin.md относительный" "$(readlink "$W/.claude/roles/admin.md")" "../../admin/.claude/roles/admin.md" assert_symlink "agents/jira-admin.md" "$W/.claude/agents/jira-admin.md" assert_symlink "agent-memory/jira-admin" "$W/.claude/agent-memory/jira-admin" assert_file "целевой каталог памяти создан" "$W/admin/.claude/agent-memory/jira-admin" assert_eq "dev не линкуется" "$(ls "$W/.claude/roles" | grep -c dev)" "0" assert_eq "docs/source пропущены" "$(ls "$W/.claude/roles" | wc -l | tr -d ' ')" "2" assert_grep "печатает 'создан'" 'создан' <(printf '%s' "$out") out2=$(bash "$L" "$W" 2>&1); rc=$? assert_eq "второй запуск: exit 0" "$rc" "0" assert_eq "второй запуск: ничего не создано" "$(printf '%s' "$out2" | grep -c 'создан')" "0" # чужой обычный файл на месте ссылки не затирается rm "$W/.claude/roles/qa.md"; echo 'мой файл' > "$W/.claude/roles/qa.md" out3=$(bash "$L" "$W" 2>&1) assert_eq "обычный файл сохранён" "$(cat "$W/.claude/roles/qa.md")" "мой файл" assert_grep "обычный файл → ПРОПУЩЕН" 'ПРОПУЩЕН' <(printf '%s' "$out3") # --map без CLAUDE.md W2="$TMP/ws2"; mkdir -p "$W2/an/.claude/roles"; echo x > "$W2/an/.claude/roles/analyst.md" bash "$L" "$W2" --map an=analyst >/dev/null 2>&1 assert_symlink "--map an=analyst" "$W2/.claude/roles/analyst.md" # источник роли отсутствует → предупреждение, не ошибка W3="$TMP/ws3"; mkdir -p "$W3/qa" printf '| Каталог | Репозиторий | Роль |\n|---|---|---|\n| qa/ | x | qa |\n' > "$W3/CLAUDE.md" out4=$(bash "$L" "$W3" 2>&1); rc=$? assert_eq "нет файла роли в репозитории: exit 0" "$rc" "0" assert_grep "нет файла роли: WARN" 'WARN' <(printf '%s' "$out4") ``` - [ ] **Step 2: Запустить, убедиться, что падает** Run: `bash tests/run.sh link-roles` Expected: FAIL «link-roles.sh существует» и остальные. - [ ] **Step 3: Написать `link-roles.sh`** ```bash #!/usr/bin/env bash # claude-harness: симлинки из /.claude/ в репозитории направлений (роли, агенты, память агентов). # Запуск: bash link-roles.sh [workspace] [--map = ...] # Источник пар «каталог → роль»: таблица | Каталог | Репозиторий | Роль | в /CLAUDE.md (роли admin|analyst|qa) # и/или пары --map. Идемпотентно. Обычные файлы на месте ссылок не трогает (ПРОПУЩЕН). set -euo pipefail WS="."; MAPS=() while [ $# -gt 0 ]; do case "$1" in --map) MAPS+=("$2"); shift 2 ;; *) WS="$1"; shift ;; esac done cd "$WS"; WS="$(pwd)" pairs=() if [ -f CLAUDE.md ]; then while IFS= read -r line; do pairs+=("$line"); done < <( python3 - CLAUDE.md <<'PY' import re, sys for line in open(sys.argv[1], encoding="utf-8"): if not line.startswith("|"): continue cells = [c.strip() for c in line.strip().strip("|").split("|")] if len(cells) < 3: continue d, role = cells[0].rstrip("/"), cells[2] if role in ("admin", "analyst", "qa") and d and not d.startswith("-"): print(f"{d}={role}") PY ) fi pairs+=("${MAPS[@]+"${MAPS[@]}"}") [ ${#pairs[@]} -gt 0 ] || { echo "link-roles: нет пар каталог=роль (таблица в CLAUDE.md или --map)"; exit 0; } mkdir -p .claude/roles .claude/agents .claude/agent-memory link() { # $1 = путь ссылки (относительно WS), $2 = цель (относительно каталога ссылки) local l="$1" t="$2" if [ -L "$l" ]; then [ "$(readlink "$l")" = "$t" ] && { echo " есть $l"; return; } rm "$l" elif [ -e "$l" ]; then echo " ПРОПУЩЕН $l — обычный файл/каталог, не симлинк; убери его вручную, если нужна ссылка"; return fi ln -s "$t" "$l"; echo " создан $l → $t" } for p in "${pairs[@]}"; do d=${p%%=*}; r=${p#*=}; d=${d%/} echo "$d ($r)" [ -d "$d" ] || { echo " WARN каталог $d не найден"; continue; } if [ -f "$d/.claude/roles/$r.md" ]; then link ".claude/roles/$r.md" "../../$d/.claude/roles/$r.md" else echo " WARN нет $d/.claude/roles/$r.md — проектный файл роли не слинкован" fi for a in "$d"/.claude/agents/*.md; do [ -f "$a" ] || continue n=$(basename "$a" .md) link ".claude/agents/$n.md" "../../$d/.claude/agents/$n.md" mkdir -p "$d/.claude/agent-memory/$n" link ".claude/agent-memory/$n" "../../$d/.claude/agent-memory/$n" done done ``` ```bash chmod +x link-roles.sh ``` - [ ] **Step 4: Запустить тест** Run: `bash tests/run.sh link-roles` Expected: все `ok`, `failed 0`. - [ ] **Step 5: Коммит** ```bash git add link-roles.sh tests/test-link-roles.sh git commit -m "feat: link-roles.sh — симлинки ролей, агентов и памяти из workspace Refs #2 Co-Authored-By: Claude Fable 5.1 " ``` --- ### 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 [--role ...]`) - 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 [--role admin --role qa ...]` — создаёт workspace и по каталогу-репозиторию на роль из `role-repo/` (`//`, `git init -b main`, коммит), затем `link-roles.sh`. Существующие режимы обоих скриптов не меняются. - [ ] **Step 1: Написать падающий тест** `tests/test-workspace.sh`: ```bash # Шаблоны workspace/role-repo и режим --workspace в adopt-project.sh / new-project.sh. T="$ROOT/project-template" assert_file "workspace/CLAUDE.md" "$T/workspace/CLAUDE.md" assert_max_bytes "workspace/CLAUDE.md ≤ 5 КБ" "$T/workspace/CLAUDE.md" 5120 assert_grep "workspace/CLAUDE.md: таблица репозиториев" '^\| Каталог \| Репозиторий \| Роль \|' "$T/workspace/CLAUDE.md" assert_grep "workspace/CLAUDE.md: роли → /role" '/role' "$T/workspace/CLAUDE.md" assert_file "workspace/roles/dev.md" "$T/workspace/roles/dev.md" assert_grep "workspace/roles/dev.md: строка 1" '^# Роль: dev ' "$T/workspace/roles/dev.md" assert_file "role-repo/CLAUDE.md" "$T/role-repo/CLAUDE.md" assert_file "role-repo/ROLE.md" "$T/role-repo/ROLE.md" assert_file "role-repo/notes/README.md" "$T/role-repo/notes/README.md" # new-project.sh --workspace W="$TMP/ws" out=$(bash "$ROOT/new-project.sh" --workspace "$W" --role admin --role qa 2>&1); rc=$? assert_eq "new-project --workspace: exit 0" "$rc" "0" assert_file "ws CLAUDE.md" "$W/CLAUDE.md" assert_grep "ws CLAUDE.md: имя подставлено" "$(basename "$W")" "$W/CLAUDE.md" assert_grep "ws CLAUDE.md: строка admin в таблице" '^\| admin/ \| .* \| admin \|' "$W/CLAUDE.md" assert_file "ws .claude/roles/dev.md" "$W/.claude/roles/dev.md" assert_eq "admin/ — git на main" "$(git -C "$W/admin" branch --show-current)" "main" assert_eq "admin/.claude/role" "$(cat "$W/admin/.claude/role")" "admin" assert_grep "admin/.claude/roles/admin.md: строка 1" '^# Роль: admin ' "$W/admin/.claude/roles/admin.md" assert_file "admin/notes/README.md" "$W/admin/notes/README.md" assert_eq "admin: коммит есть" "$(git -C "$W/admin" log --oneline | wc -l | tr -d ' ')" "1" assert_symlink "ws .claude/roles/admin.md слинкован" "$W/.claude/roles/admin.md" assert_symlink "ws .claude/roles/qa.md слинкован" "$W/.claude/roles/qa.md" assert_eq "workspace сам не git" "$([ -d "$W/.git" ] && echo git || echo no)" "no" # adopt-project.sh --workspace на существующем каталоге без CLAUDE.md W2="$TMP/ws2"; mkdir -p "$W2/qa/.claude/roles"; echo '# Роль: qa — x' > "$W2/qa/.claude/roles/qa.md" out=$(bash "$ROOT/adopt-project.sh" --workspace "$W2" 2>&1); rc=$? assert_eq "adopt --workspace: exit 0" "$rc" "0" assert_file "adopt: CLAUDE.md создан" "$W2/CLAUDE.md" assert_file "adopt: .claude/roles/dev.md создан" "$W2/.claude/roles/dev.md" assert_grep "adopt: печатает allow-правило" 'git push origin --delete' <(printf '%s' "$out") echo 'моё' > "$W2/.claude/roles/dev.md" bash "$ROOT/adopt-project.sh" --workspace "$W2" >/dev/null 2>&1 assert_eq "adopt повторно: dev.md не затёрт" "$(cat "$W2/.claude/roles/dev.md")" "моё" ``` - [ ] **Step 2: Запустить, убедиться, что падает** Run: `bash tests/run.sh workspace` Expected: FAIL по шаблонам и режимам. - [ ] **Step 3: Написать шаблоны** `project-template/workspace/CLAUDE.md`: ```markdown # {{PROJECT_NAME}} ## Что это {{Одним абзацем: что за проект, для кого, что сейчас делается.}} ## Репозитории Сессии запускаются из этого каталога. Роль сессии выбирается через `/role ` (подсказка хука по заголовку сессии `claude -n "<роль> …"`); правила роли — в `~/.claude/roles/.md` (общие) и `.claude/roles/.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 для кода; Jira <проект> для заказчика}}. ## Ограничения для всех ролей - Хуки guard-main и require-plan действуют в каждом git-репозитории здесь; workspace сам не git. - {{Стенд, порты, что нельзя трогать.}} ``` `project-template/workspace/roles/dev.md`: ```markdown # Роль: dev — разработчик (проектный слой {{PROJECT_NAME}}) Эта роль заменяет предыдущую роль сессии целиком. ## Процесс - {{Репозитории кода и их CLAUDE.md; worktree `.wt/<задача>/` и что в него линкуется; `.bounded`; venv/сборка; стенд.}} - {{Источники истины для рулингов: модель данных, доменные правила.}} ## Читать на старте - {{путь к инвариантам / ARCHITECTURE.md}} - brief: `tea issues ls --state open` ## Не делать - {{Инварианты проекта: что не мутируется, кто единственный писатель, чего не хранить.}} ## Делегировать - SDD-контроллер → исполнение плана. ``` `project-template/role-repo/CLAUDE.md`: ```markdown # {{DIR}} — репозиторий направления {{ROLE}} Кухня роли `{{ROLE}}` проекта {{PROJECT_NAME}}: рабочие материалы, `notes/` (уроки, решения владельца, рецепты), `.claude/roles/{{ROLE}}.md` (проектный слой роли), `.claude/agents/` (агенты роли), `.claude/agent-memory/` (их память, коммитится). Сессии запускаются из корня workspace; этот файл загружается при чтении файлов отсюда. Публикация в витрину `docs/` — через PR. ``` `project-template/role-repo/ROLE.md` (ставится как `.claude/roles/.md`): ```markdown # Роль: {{ROLE}} — проектный слой {{PROJECT_NAME}} Эта роль заменяет предыдущую роль сессии целиком. ## Процесс - {{Инструменты роли в этом проекте, трекер, где лежат артефакты.}} ## Читать на старте - {{DIR}}/README.md - brief: {{DIR}}/notes/ ## Не делать - {{Операционные ограничения проекта для этой роли.}} ## Делегировать - {{агент → для чего, если есть}} ``` `project-template/role-repo/notes/README.md`: ```markdown # notes/ Уроки, решения владельца (с датой) и рецепты роли. Коммитятся в той же ветке, что и работа. Состояние задач сюда не пишется — оно в трекере. ``` - [ ] **Step 4: Добавить режим `--workspace` в `adopt-project.sh`** Вставить после строки `HERE="$(cd "$(dirname "$0")" && pwd)"` (до разбора аргументов): ```bash # Режим workspace (каталог не git, рядом репозитории направлений): bash adopt-project.sh --workspace [папка] if [ "${1:-}" = "--workspace" ]; then cd "${2:-.}"; NAME="$(basename "$(pwd)")" if [ -f CLAUDE.md ]; then echo "CLAUDE.md: есть, не трогаю" else sed "s/{{PROJECT_NAME}}/$NAME/g" "$HERE/project-template/workspace/CLAUDE.md" > CLAUDE.md; echo "CLAUDE.md: создан из шаблона workspace — заполни {{...}} и таблицу репозиториев"; fi mkdir -p .claude/roles if [ -e .claude/roles/dev.md ]; then echo ".claude/roles/dev.md: есть, не трогаю" else sed "s/{{PROJECT_NAME}}/$NAME/g" "$HERE/project-template/workspace/roles/dev.md" > .claude/roles/dev.md; echo ".claude/roles/dev.md: заготовка"; fi bash "$HERE/link-roles.sh" "$(pwd)" cat < [--role admin] [--role qa] [--role analyst] if [ "${1:-}" = "--workspace" ]; then DIR="${2:?usage: new-project.sh --workspace [--role ...]}"; 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-}} на имя каталога, если заготовка есть sed -i.bak "s#| $d/ | {{owner/repo-[a-z]*}} | $r |#| $d/ | /$NAME-$d | $r |#" CLAUDE.md && rm -f CLAUDE.md.bak echo "$d/: репозиторий направления $r создан" done bash "$HERE/link-roles.sh" "$(pwd)" cat <.md. 2. Заведи репозитории направлений в Gitea и добавь remote (tea repos create …; git remote add origin …). 3. cd $(pwd) && claude -n "dev …" MSG exit 0 fi ``` Примечание для implementer: строка таблицы для `analysis/` в шаблоне — `| analysis/ | {{owner/repo-analysis}} | analyst |`, `sed` выше подставляет её по `$d` и `$r`; для `admin` и `qa` имена каталога и роли совпадают. - [ ] **Step 6: Запустить тест** Run: `bash tests/run.sh workspace` Expected: все `ok`. Затем `bash tests/run.sh` — всё зелёное. - [ ] **Step 7: Коммит** ```bash git add project-template/workspace project-template/role-repo adopt-project.sh new-project.sh tests/test-workspace.sh git commit -m "feat: шаблоны workspace и role-repo, режим --workspace в adopt/new-project Refs #2 Co-Authored-By: Claude Fable 5.1 " ``` --- ### Task 8: `doctor.sh` — роли, role-hint, workspace, размеры **Files:** - Modify: `skills/harness-doctor/scripts/doctor.sh` - Modify: `skills/harness-doctor/SKILL.md` (чеклист прочтения — новые строки) - Create: `tests/test-doctor.sh` **Interfaces:** - Consumes: установленные пути из Task 9 (`$CC/roles/`, `$CC/skills/role/`, `$CC/agents/briefing.md`, `$CC/hooks/role-hint.sh`, `$CC/harness/link-roles.sh`); формат таблицы репозиториев. - Produces: в разделе «Машина» — строки `ok`/`MISS` по новым файлам и `WARN глобальный CLAUDE.md > 3 КБ`; в «Самотест хуков» — две проверки `role-hint`; в «Проект» для не-git каталога с таблицей репозиториев — режим workspace: симлинки `.claude/roles|agents|agent-memory` целы/битые, размер CLAUDE.md ≤ 5 КБ, размер индекса памяти `MEMORY.md` ≤ 3 КБ. - [ ] **Step 1: Написать падающий тест** `tests/test-doctor.sh` — запускает `doctor.sh` с `CLAUDE_CONFIG_DIR`, указывающим на временную установку из источников (без настоящего `install.sh`, чтобы не зависеть от Task 9): ```bash # doctor.sh: новые проверки — файлы ролей, самотест role-hint, режим workspace, размеры. D="$ROOT/skills/harness-doctor/scripts/doctor.sh" CC="$TMP/cc"; mkdir -p "$CC/hooks" "$CC/roles" "$CC/skills/role" "$CC/agents" "$CC/harness" "$CC/projects" cp "$ROOT"/hooks/*.sh "$CC/hooks/"; chmod +x "$CC/hooks/"*.sh cp "$ROOT"/roles/*.md "$CC/roles/"; cp "$ROOT/skills/role/SKILL.md" "$CC/skills/role/"; cp "$ROOT/agents/briefing.md" "$CC/agents/" cp "$ROOT/link-roles.sh" "$ROOT/adopt-project.sh" "$CC/harness/"; cp "$ROOT/global/CLAUDE.md" "$CC/CLAUDE.md" printf '{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"\\"$HOME/.claude/hooks/role-hint.sh\\""}]}]}}' > "$CC/settings.json" # workspace с одной целой и одной битой ссылкой W="$TMP/ws"; mkdir -p "$W/admin/.claude/roles" "$W/.claude/roles" "$W/.claude/agents" echo '# Роль: admin — x' > "$W/admin/.claude/roles/admin.md" printf '# p\n\n| Каталог | Репозиторий | Роль |\n|---|---|---|\n| admin/ | o/a | admin |\n' > "$W/CLAUDE.md" ln -s ../../admin/.claude/roles/admin.md "$W/.claude/roles/admin.md" ln -s ../../qa/.claude/agents/none.md "$W/.claude/agents/none.md" slug=$(printf '%s' "$W" | sed 's#[^A-Za-z0-9]#-#g'); mkdir -p "$CC/projects/$slug/memory"; head -c 4000 /dev/zero | tr '\0' 'x' > "$CC/projects/$slug/memory/MEMORY.md" out=$(CLAUDE_CONFIG_DIR="$CC" bash "$D" "$W" 2>&1) assert_grep "машина: roles" 'ok +roles/ \(dev admin analyst qa\)' <(printf '%s' "$out") assert_grep "машина: skill role" 'ok +skill role' <(printf '%s' "$out") assert_grep "машина: агент briefing" 'ok +agent briefing' <(printf '%s' "$out") assert_grep "машина: link-roles.sh" 'ok +.*link-roles.sh' <(printf '%s' "$out") assert_grep "самотест: role-hint admin" 'ok +role-hint: заголовок admin' <(printf '%s' "$out") assert_grep "самотест: role-hint тишина" 'ok +role-hint: без заголовка' <(printf '%s' "$out") assert_grep "проект: режим workspace" 'workspace' <(printf '%s' "$out") assert_grep "проект: целая ссылка" 'ok +roles/admin.md' <(printf '%s' "$out") assert_grep "проект: битая ссылка" 'WARN +agents/none.md' <(printf '%s' "$out") assert_grep "проект: индекс памяти > 3 КБ" 'WARN +индекс auto-memory' <(printf '%s' "$out") rm -f "$CC/roles/qa.md" out=$(CLAUDE_CONFIG_DIR="$CC" bash "$D" "$W" 2>&1) assert_grep "машина: MISS roles/qa.md" 'MISS +roles/qa.md' <(printf '%s' "$out") ``` - [ ] **Step 2: Запустить, убедиться, что падает** Run: `bash tests/run.sh doctor` Expected: FAIL по всем новым строкам (старые строки doctor печатаются, но новых нет). - [ ] **Step 3: Дополнить раздел «Машина»** В `doctor.sh` после строки с `[ -x "$CC/harness/adopt-project.sh" ] && ok ...` добавить: ```bash [ -x "$CC/harness/link-roles.sh" ] && ok "~/.claude/harness/link-roles.sh" || miss "~/.claude/harness/link-roles.sh" rmiss=(); for r in dev admin analyst qa; do [ -f "$CC/roles/$r.md" ] || rmiss+=("$r"); done if [ ${#rmiss[@]} -eq 0 ]; then ok "roles/ (dev admin analyst qa)"; else for r in "${rmiss[@]}"; do miss "roles/$r.md"; done; fi [ -f "$CC/skills/role/SKILL.md" ] && ok "skill role" || miss "skill role (~/.claude/skills/role/SKILL.md)" [ -f "$CC/agents/briefing.md" ] && ok "agent briefing" || miss "agent briefing (~/.claude/agents/briefing.md)" if [ -f "$CC/CLAUDE.md" ]; then n=$(wc -c < "$CC/CLAUDE.md" | tr -d ' '); [ "$n" -le 3072 ] && ok "глобальный CLAUDE.md $n Б (≤ 3 КБ)" || warn "глобальный CLAUDE.md $n Б > 3 КБ"; fi ``` И в цикле `for h in guard-main require-plan harness-check; do` добавить `role-hint`: `for h in guard-main require-plan harness-check role-hint; do`. - [ ] **Step 4: Дополнить самотест хуков** Перед `rm -rf "$T"` в блоке самотеста добавить: ```bash if [ -x "$CC/hooks/role-hint.sh" ]; then o=$(printf '{"session_title":"admin: спринт"}' | bash "$CC/hooks/role-hint.sh" 2>/dev/null) case "$o" in *"Роль сессии: admin"*) ok "role-hint: заголовок admin → подсказка" ;; *) warn "role-hint: заголовок admin → '$o' (ожидалась подсказка)" ;; esac o=$(printf '{"cwd":"/tmp","source":"resume"}' | bash "$CC/hooks/role-hint.sh" 2>/dev/null) [ -z "$o" ] && ok "role-hint: без заголовка → тишина" || warn "role-hint: без заголовка → '$o' (ожидалась тишина)" fi ``` - [ ] **Step 5: Добавить режим workspace в раздел «Проект»** Заменить последнюю строку блока `else info "не git-репозиторий"; fi` на: ```bash elif [ -f "$DIR/CLAUDE.md" ] && grep -q '^| Каталог | Репозиторий | Роль |' "$DIR/CLAUDE.md"; then ws=$(cd "$DIR" && pwd) info "workspace (не git): $ws" n=$(wc -c < "$ws/CLAUDE.md" | tr -d ' '); [ "$n" -le 5120 ] && ok "CLAUDE.md workspace $n Б (≤ 5 КБ)" || warn "CLAUDE.md workspace $n Б > 5 КБ" for kind in roles agents agent-memory; do for l in "$ws/.claude/$kind"/*; do [ -e "$l" ] || [ -L "$l" ] || continue name="$kind/$(basename "$l")" if [ -L "$l" ]; then [ -e "$l" ] && ok "$name → $(readlink "$l")" || warn "$name — битая ссылка на $(readlink "$l")" else info "$name — обычный файл (не ссылка)"; fi done done grep -E '^\|' "$ws/CLAUDE.md" | awk -F'|' '{gsub(/ /,"",$4); gsub(/^ +| +$/,"",$2); if ($4=="admin"||$4=="analyst"||$4=="qa") print $2, $4}' | while read -r d r; do [ -f "$ws/${d%/}/.claude/roles/$r.md" ] || warn "нет ${d%/}/.claude/roles/$r.md — запусти link-roles.sh после создания" done # slug каталога памяти Claude Code: все не-алфавитно-цифровые символы пути → «-» (например, /Users/a.b/x → -Users-a-b-x) slug=$(printf '%s' "$ws" | sed 's#[^A-Za-z0-9]#-#g'); mem="$CC/projects/$slug/memory/MEMORY.md" if [ -f "$mem" ]; then n=$(wc -c < "$mem" | tr -d ' '); [ "$n" -le 3072 ] && ok "индекс auto-memory $n Б (≤ 3 КБ)" || warn "индекс auto-memory $n Б > 3 КБ ($mem)"; else info "индекс auto-memory: нет ($mem)"; fi [ -x "$CC/harness/link-roles.sh" ] && info "обновить ссылки: bash $CC/harness/link-roles.sh $ws" else info "не git-репозиторий и не workspace (нет таблицы репозиториев в CLAUDE.md)"; fi ``` - [ ] **Step 6: Дополнить `SKILL.md` чеклист прочтения** В раздел «**Машина**» добавить: ```markdown - `MISS roles/.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/.md`, перенести туда. ``` В раздел «**Проект**» добавить: ```markdown - «workspace (не git)» → режим нескольких репозиториев. `WARN … битая ссылка` → репозиторий направления переименован или файл удалён: `bash ~/.claude/harness/link-roles.sh `. `WARN нет /.claude/roles/.md` → создать проектный файл роли из `~/.claude/harness/project-template/role-repo/ROLE.md`. - `WARN индекс auto-memory > 3 КБ` → индекс снова стал журналом состояния; свернуть закрытое в `registry-closed.md`, уроки — в `notes/` направления (политика памяти в глобальном CLAUDE.md). ``` В «Чинится само» добавить `link-roles.sh` в workspace. - [ ] **Step 7: Запустить тесты** Run: `bash tests/run.sh doctor` затем `bash tests/run.sh` Expected: всё `ok`. Дополнительно вручную: `bash skills/harness-doctor/scripts/doctor.sh .` в worktree печатает старые разделы без регрессий (строки `ok hook …`, самотест guard-main/require-plan). - [ ] **Step 8: Коммит** ```bash git add skills/harness-doctor tests/test-doctor.sh git commit -m "feat(doctor): проверки ролей, самотест role-hint, режим workspace, размеры Refs #2 Co-Authored-By: Claude Fable 5.1 " ``` --- ### Task 9: `install.sh` ставит роли, skill, агента, хук, link-roles **Files:** - Modify: `install.sh` - Create: `tests/test-install.sh` **Interfaces:** - Consumes: все артефакты Task 2–8. - Produces: установленное состояние в `$CC`: `roles/*.md`, `skills/role/SKILL.md`, `agents/briefing.md`, `hooks/role-hint.sh`, `harness/link-roles.sh`; `settings.json` с одной записью `role-hint.sh` в `SessionStart` после любого числа запусков; `--check` печатает `ok`/`MISS` по этим файлам. - [ ] **Step 1: Написать падающий тест** `tests/test-install.sh`: ```bash # install.sh: новые файлы ставятся, повторный запуск не дублирует хуки, --check их видит. export HOME="$TMP/home"; export CLAUDE_CONFIG_DIR="$TMP/home/.claude"; mkdir -p "$CLAUDE_CONFIG_DIR" echo '{"hooks":{"SessionStart":[{"matcher":"startup","hooks":[{"type":"command","command":"\"$HOME/.claude/hooks/harness-check.sh\""}]}]}}' > "$CLAUDE_CONFIG_DIR/settings.json" bash "$ROOT/install.sh" >/dev/null 2>&1; bash "$ROOT/install.sh" >/dev/null 2>&1 CC="$CLAUDE_CONFIG_DIR" for r in dev admin analyst qa; do assert_file "roles/$r.md установлен" "$CC/roles/$r.md"; done assert_file "skills/role/SKILL.md установлен" "$CC/skills/role/SKILL.md" assert_file "agents/briefing.md установлен" "$CC/agents/briefing.md" assert_file "hooks/role-hint.sh установлен" "$CC/hooks/role-hint.sh" assert_eq "hooks/role-hint.sh исполняемый" "$([ -x "$CC/hooks/role-hint.sh" ] && echo x)" "x" assert_file "harness/link-roles.sh установлен" "$CC/harness/link-roles.sh" assert_file "harness/project-template/workspace" "$CC/harness/project-template/workspace/CLAUDE.md" assert_file "harness/project-template/role-repo" "$CC/harness/project-template/role-repo/ROLE.md" cnt=$(python3 -c "import json,sys; s=json.load(open(sys.argv[1])); print(sum(1 for e in s['hooks']['SessionStart'] for h in e.get('hooks',[]) if 'role-hint.sh' in h['command']))" "$CC/settings.json") assert_eq "role-hint.sh в settings.json один раз после двух установок" "$cnt" "1" cnt=$(python3 -c "import json,sys; s=json.load(open(sys.argv[1])); print(sum(1 for e in s['hooks']['SessionStart'] for h in e.get('hooks',[]) if 'harness-check.sh' in h['command']))" "$CC/settings.json") assert_eq "harness-check.sh в settings.json один раз" "$cnt" "1" out=$(bash "$ROOT/install.sh" --check 2>&1) assert_grep "--check: roles" 'roles/' <(printf '%s' "$out") assert_grep "--check: skill role" 'skills/role' <(printf '%s' "$out") assert_grep "--check: agent briefing" 'agents/briefing' <(printf '%s' "$out") rm "$CC/agents/briefing.md" out=$(bash "$ROOT/install.sh" --check 2>&1) assert_grep "--check: MISS briefing" 'MISS +.*briefing' <(printf '%s' "$out") ``` - [ ] **Step 2: Запустить, убедиться, что падает** Run: `bash tests/run.sh install` Expected: FAIL по `roles/`, `skills/role`, `agents/briefing.md`, `role-hint.sh`, `link-roles.sh`, `--check`. - [ ] **Step 3: Дополнить `install.sh`** В `check()` после цикла по бинарникам добавить: ```bash echo "== Установленная часть обвязки ($CC)" for f in CLAUDE.md hooks/guard-main.sh hooks/require-plan.sh hooks/harness-check.sh hooks/role-hint.sh \ roles/dev.md roles/admin.md roles/analyst.md roles/qa.md skills/role/SKILL.md agents/briefing.md \ harness/adopt-project.sh harness/new-project.sh harness/link-roles.sh skills/harness-doctor/SKILL.md; do [ -f "$CC/$f" ] && echo " ok $f" || { echo " MISS $f (bash install.sh)"; ok=1; } done ``` В разделе установки заменить строку `cp "$HERE/hooks/guard-main.sh" "$HERE/hooks/require-plan.sh" "$HERE/hooks/harness-check.sh" "$CC/hooks/"` на: ```bash cp "$HERE/hooks/guard-main.sh" "$HERE/hooks/require-plan.sh" "$HERE/hooks/harness-check.sh" "$HERE/hooks/role-hint.sh" "$CC/hooks/" ``` Строку `cp -R "$HERE/adopt-project.sh" "$HERE/new-project.sh" "$HERE/project-template" "$CC/harness/"` заменить на: ```bash rm -rf "$CC/harness/project-template" cp -R "$HERE/adopt-project.sh" "$HERE/new-project.sh" "$HERE/link-roles.sh" "$HERE/project-template" "$CC/harness/" ``` После блока «2b. Skill диагностики» добавить: ```bash # 2c. Роли, skill /role, агент briefing mkdir -p "$CC/roles" "$CC/skills/role" "$CC/agents" cp "$HERE/roles/"*.md "$CC/roles/" cp "$HERE/skills/role/SKILL.md" "$CC/skills/role/SKILL.md" cp "$HERE/agents/briefing.md" "$CC/agents/briefing.md" ``` В Python-блоке слияния `settings.json` заменить строку ```python ss[:] = [e for e in ss if not any("harness-check.sh" in x.get("command","") for x in e.get("hooks",[]))] ``` на ```python ours_ss = ("harness-check.sh", "role-hint.sh") ss[:] = [e for e in ss if not any(any(n in x.get("command","") for n in ours_ss) for x in e.get("hooks",[]))] ``` В финальном `echo "done: ..."` добавить `$CC/roles/, $CC/skills/role, $CC/agents/briefing.md, $CC/hooks/role-hint.sh`. - [ ] **Step 4: Запустить тест** Run: `bash tests/run.sh install` затем `bash tests/run.sh` Expected: всё `ok`, `failed 0`. - [ ] **Step 5: Коммит** ```bash git add install.sh tests/test-install.sh git commit -m "feat(install): роли, skill /role, briefing, role-hint, link-roles; --check видит их Refs #2 Co-Authored-By: Claude Fable 5.1 " ``` --- ### Task 10: Документация — HARNESS.md «Роли», README, CHANGELOG 0.3.0, CLAUDE.md репозитория **Files:** - Modify: `docs/HARNESS.md` (новый раздел «Роли» после «Дисциплина между сессиями») - Modify: `README.md` (таблица «Состав», «Файлы», «Установка», новый раздел «Роли») - Modify: `CHANGELOG.md` (запись 0.3.0) - Modify: `CLAUDE.md` (разделы «Архитектура», «Команды», «Тесты» — новые файлы и `tests/run.sh`) - Modify: `docs/superpowers/specs/2026-10-11-roles-and-memory-design.md` (раздел «Decisions made without discussion» — решения плана) **Interfaces:** - Consumes: всё выше. - Produces: документация, по которой владелец переносит роли в awp-ca (часть 2). - [ ] **Step 1: HARNESS.md — раздел «Роли»** Вставить перед `## Стоимость`: ```markdown ## Роли Одна сессия — одна роль: `dev`, `admin`, `analyst`, `qa`. Роль даёт сессии только свои правила, свои файлы на старте и свой раздел памяти; процесс разработки (superpowers → Gitea → OpenSpec, SDD, чеклист закрытия) живёт в роли dev и не загружается в сессии admin. Два слоя, оба в git: общий — `~/.claude/roles/.md` (из пакета, `install.sh`), проектный — `/.claude/roles/.md` (для dev — файл, для остальных — симлинк в репозиторий направления `admin/`, `qa/`, `analysis/`). Формат одинаковый: четыре раздела «Процесс», «Читать на старте», «Не делать», «Делегировать»; при конфликте побеждает проектный слой. Выбор роли, от автоматического к ручному: заголовок сессии (`claude -n "admin …"`; хук `role-hint.sh` печатает подсказку `[claude-harness] Роль сессии: admin`), вывод из первого сообщения (правило в глобальном CLAUDE.md: объявить «Роль: admin; если не так, скажи»), явно — `/role `. Skill `/role` читает оба слоя, раздел памяти роли, строки «Читать на старте», а строки с префиксом `brief:` отдаёт фоновому агенту `briefing`, который возвращает выжимку: активные задачи, решения, открытые вопросы, ловушки. Workspace из нескольких репозиториев (не git, cwd всех сессий) описывается таблицей `| Каталог | Репозиторий | Роль |` в его CLAUDE.md; `link-roles.sh` по ней создаёт симлинки на проектные файлы ролей, агентов направлений и их память (`memory: project` → `/.claude/agent-memory//`, коммитится). `adopt-project.sh --workspace` и `new-project.sh --workspace --role …` собирают это из шаблонов; `doctor.sh` проверяет ссылки и размеры. Политика памяти (глобальный CLAUDE.md): состояние задачи — в трекере; уроки, решения владельца и рецепты — в `notes/` направления и памяти агентов (в git); в локальной auto-memory — только ловушки машины и индекс со ссылками. При закрытии файл auto-memory задачи сворачивается в строку `registry-closed.md`. ``` Также в таблице раздела «Хуки» добавить строку: ```markdown | Подсказка роли по заголовку сессии | `role-hint.sh`, SessionStart (все источники) | читает `session_title`; если начинается с `dev|admin|analyst|qa` — одна строка-подсказка вызвать `/role`; ничего не блокирует | ``` И в «Дисциплина между сессиями» первое предложение дополнить: «Новая сессия начинается с роли (`/role`), затем `tea issues ls` и `openspec list`…». - [ ] **Step 2: README.md** В таблицу «Состав» добавить строку: ```markdown | Роль сессии | `roles/`, skill `/role`, агент `briefing`, хук `role-hint.sh` | правила, файлы и память своей роли | этот пакет | ``` В блок «Файлы» добавить строки: ``` roles/.md общий слой ролей dev/admin/analyst/qa → ~/.claude/roles/ skills/role/SKILL.md /role : загрузить роль, память, запустить briefing → ~/.claude/skills/role/ agents/briefing.md выжимка состояния для роли (sonnet, только чтение) → ~/.claude/agents/ hooks/role-hint.sh подсказка роли по заголовку сессии (SessionStart) link-roles.sh симлинки ролей/агентов/памяти из workspace в репозитории направлений → ~/.claude/harness/ project-template/workspace/ CLAUDE.md workspace с таблицей репозиториев, roles/dev.md project-template/role-repo/ репозиторий направления: CLAUDE.md, ROLE.md, notes/README.md tests/run.sh тесты пакета (bash): bash tests/run.sh ``` Новый раздел после «Существующий проект»: ```markdown ## Workspace из нескольких репозиториев и роли ``` bash ~/.claude/harness/adopt-project.sh --workspace ~/projects/foo # есть каталог с репозиториями bash ~/.claude/harness/new-project.sh --workspace ~/projects/foo --role admin --role qa # с нуля ``` Заполните таблицу репозиториев в `CLAUDE.md` workspace и перезапустите `link-roles.sh`. Сессии запускайте из корня workspace с ролью в заголовке: `claude -n "admin спринт 3"`. Удобно завести алиасы в профиле shell (обвязка их не ставит): ```bash alias cc-dev='claude -n "dev"'; alias cc-admin='claude -n "admin"'; alias cc-qa='claude -n "qa"'; alias cc-analyst='claude -n "analyst"' ``` В сессии: `/role` — текущая роль и список; `/role admin` — переключить. Подробности в `docs/HARNESS.md`, раздел «Роли». ``` В «Установка» шаг 4 дополнить: «`bash tests/run.sh` — тесты пакета должны быть зелёными». - [ ] **Step 3: CHANGELOG.md** В начало после `# Changelog`: ```markdown ## 0.3.0 — 2026-10-11 - Роли сессии `dev|admin|analyst|qa`: общий слой `roles/.md` (`~/.claude/roles/`), проектный слой `/.claude/roles/.md`; формат из четырёх разделов (спека `docs/superpowers/specs/2026-10-11-roles-and-memory-design.md`). - Skill `/role `: читает оба слоя, раздел памяти роли, «Читать на старте»; строки `brief:` — фоновому агенту `briefing` (sonnet, только чтение). - SessionStart-хук `role-hint.sh`: подсказка роли по заголовку сессии (`claude -n "admin …"`), регистр и префикс `cc-` не важны; ничего не блокирует. - `link-roles.sh`: симлинки ролей, агентов и их памяти из workspace в репозитории направлений по таблице `| Каталог | Репозиторий | Роль |`. - Шаблоны `project-template/workspace/` и `project-template/role-repo/`; режим `--workspace` в `adopt-project.sh` и `new-project.sh`. - Глобальный CLAUDE.md сжат до ≤ 3 КБ (роль, хуки, память, маркировка, стиль); процесс разработки переехал в `roles/dev.md` — таблица адресов `docs/retro/2026-10-11-claude-md-addresses.md`. Изменения правил: архив OpenSpec всегда отдельным PR; п. 4 чеклиста закрытия — из worktree по allow-правилу; bounded обновляет спеку при смене API; новый п. 7 — память задачи при закрытии. - Политика памяти: состояние задачи в трекере, уроки в `notes/` направления и памяти агентов, в auto-memory — только ловушки машины и индекс. - `doctor.sh`: файлы ролей, самотест `role-hint`, режим workspace (симлинки, размеры CLAUDE.md и индекса памяти). - Тесты пакета `tests/run.sh` (bash, без зависимостей): хук, роли, размеры, skill/agent, link-roles, workspace, doctor, install. - Ретроспектива `docs/retro/2026-10-11.md` и скрипты `docs/retro/scripts/`. ``` - [ ] **Step 4: CLAUDE.md репозитория** В «Архитектура» добавить пункты: ```markdown - `roles/` — общий слой ролей (формат: четыре раздела, см. спеку ролей), `skills/role/` — skill `/role`, `agents/briefing.md` — агент выжимки, `link-roles.sh` — симлинки workspace → репозитории направлений. Ставятся `install.sh` в `~/.claude/{roles,skills/role,agents,harness}`. - `tests/` — тесты пакета на bash: `run.sh` + `test-*.sh`, работают с источниками, `install.sh` ставят во временный `CLAUDE_CONFIG_DIR`. ``` В «Команды» добавить первой строкой: `` - `bash tests/run.sh [имя]` — весь набор тестов. Зелёным считается только весь набор. `` В «Тесты» заменить первый пункт на: «`tests/run.sh` — автоматические тесты источников (хуки через фиктивный JSON, скрипты во временных каталогах, размеры). Самотест хуков в `doctor.sh` проверяет установленные копии; перед ним `bash install.sh`.» В «Архитектура» строку про `hooks/` дополнить: `role-hint.sh` (SessionStart, все источники): подсказка роли по `session_title`. - [ ] **Step 5: Спека — решения плана** В раздел `## Decisions made without discussion` спеки добавить: ```markdown - 11.10.2026 (план). Тесты пакета — `tests/run.sh` на bash в репозитории, в дополнение к самотесту `doctor.sh` (спека, раздел 8, упоминала только doctor). Обоснование: TDD по задачам плана невозможен против установленных копий. Цена ошибки: лишний каталог. - 11.10.2026 (план). Хук `role-hint.sh` регистрируется записью без `matcher` (документация: отсутствующий matcher = все источники), а не перечислением `startup|resume|clear|compact`. Цена ошибки: одна строка в `settings.hooks.json`. - 11.10.2026 (план). Формат таблицы репозиториев workspace зафиксирован: `| Каталог | Репозиторий | Роль |`; `link-roles.sh` и `doctor.sh` узнают workspace по заголовку этой таблицы. Цена ошибки: правка двух regex. - 11.10.2026 (план). `role-hint.sh` понимает заголовок без учёта регистра и с префиксом `cc-` (алиасы из README). Цена ошибки: нет. - 11.10.2026 (план). Часть 2 (awp-ca: проектные файлы ролей, агенты jira-admin/qa-runner, репозитории направлений, чистка памяти, песочница, перенос) — отдельный план в workspace awp-ca после merge этой ветки; каталог для роли analyst — `analysis/`. Цена ошибки: нет. - 11.10.2026 (план). Чеклист закрытия получил п. 7 (память задачи, спека 7.1) и новый п. 8 (итоговое сообщение); нумерация старых п. 1–6 сохранена. ``` - [ ] **Step 6: Проверка** Run: `bash tests/run.sh`; `grep -c '{{' global/CLAUDE.md roles/*.md` → все 0; `python3 -m json.tool hooks/settings.hooks.json >/dev/null`. Expected: тесты зелёные, плейсхолдеров в не-шаблонных файлах нет. - [ ] **Step 7: Коммит** ```bash git add docs/HARNESS.md README.md CHANGELOG.md CLAUDE.md docs/superpowers/specs/2026-10-11-roles-and-memory-design.md git commit -m "docs: роли в HARNESS.md и README, CHANGELOG 0.3.0, решения плана в спеке Refs #2 Co-Authored-By: Claude Fable 5.1 " ``` --- ## Предусловия окружения - 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, алиасы в профиле).