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