diff --git a/docs/superpowers/specs/2026-10-11-roles-and-memory-design.md b/docs/superpowers/specs/2026-10-11-roles-and-memory-design.md index c6d2d0b..07d1fa4 100644 --- a/docs/superpowers/specs/2026-10-11-roles-and-memory-design.md +++ b/docs/superpowers/specs/2026-10-11-roles-and-memory-design.md @@ -28,7 +28,7 @@ settings.json как сейчас (хуки workspace, разрешения) roles/.md проектный слой ролей (dev — файл; admin/analyst/qa — симлинки в репозитории направлений) agents/.md симлинки на агентов из репозиториев направлений - agent-memory// симлинки на память агентов в репозиториях направлений + agent-memory// симлинки на память агентов (не работает, см. раздел 11) / роль dev docs/ витрина: контекст, модель, контракты, гайды, инструкции. Читают все, пишут через PR admin/ → arm-ka-admin кухня admin: Jira, Гант, спринты, документы заказчику, notes/, .claude/ @@ -47,7 +47,7 @@ .claude/role имя роли по умолчанию (одна строка) — для будущего режима запуска из репозитория .claude/roles/.md проектный слой роли .claude/agents/.md агенты роли - .claude/agent-memory//MEMORY.md память агентов (коммитится) + .claude/agent-memory//MEMORY.md память агентов (коммитится; см. раздел 11: симлинк не работает, применяется запасной вариант) notes/ уроки, решения владельца, рецепты роли (markdown, коммитится) <рабочие каталоги> ``` @@ -183,7 +183,7 @@ SDD-контроллер и implementer-ы остаются описанием ### 6.5 Память агентов через симлинк `memory: project` пишет в `/.claude/agent-memory//`. Чтобы память оказалась в git репозитория направления, -`/.claude/agent-memory/` — симлинк в `/.claude/agent-memory/`. Проверяется первой (раздел 9). +`/.claude/agent-memory/` — симлинк в `/.claude/agent-memory/`. Проверяется первой (раздел 9). (см. раздел 11: симлинк не работает; применяется запасной вариант) Запасной вариант: поле `memory` не используется, промпт агента велит читать `/notes/agent-memory.md` при старте и дописывать в конце. ## 7. Память @@ -233,7 +233,7 @@ SDD-контроллер и implementer-ы остаются описанием | `skills/role/SKILL.md` | новый, раздел 5 | | `agents/briefing.md` | новый, 6.1 | | `hooks/role-hint.sh` + `hooks/settings.hooks.json` | новый SessionStart-хук; слияние блока в install.sh как у harness-check | -| `link-roles.sh` | новый: по таблице репозиториев в `CLAUDE.md` workspace (или по аргументам) создаёт симлинки `.claude/roles`, `.claude/agents`, `.claude/agent-memory`; идемпотентно; печатает, что сделано | +| `link-roles.sh` | новый: по таблице репозиториев в `CLAUDE.md` workspace (или по аргументам) создаёт симлинки `.claude/roles`, `.claude/agents` (и `.claude/agent-memory`, см. раздел 11: симлинк не работает, в реализации не создаётся); идемпотентно; печатает, что сделано | | `install.sh` | ставит `roles/`, `skills/role`, `agents/briefing.md`, `hooks/role-hint.sh`, `link-roles.sh` в `~/.claude/harness/`; `--check` проверяет их наличие | | `project-template/` | `.claude/roles/*.md` заготовки; `role-repo/` шаблон репозитория направления (CLAUDE.md, `.claude/role`, `notes/README.md`) | | `adopt-project.sh` / `new-project.sh` | режим `--workspace`: создать `CLAUDE.md` workspace, `.claude/roles/`, вызвать `link-roles.sh`; существующий режим не меняется | diff --git a/link-roles.sh b/link-roles.sh index e4b9c31..35a152e 100755 --- a/link-roles.sh +++ b/link-roles.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# claude-harness: симлинки из /.claude/ в репозитории направлений (роли, агенты, память агентов). +# claude-harness: симлинки из /.claude/ в репозитории направлений (роли и агенты; память агентов симлинком не работает, спека раздел 11). # Запуск: bash link-roles.sh [workspace] [--map = ...] # Источник пар «каталог → роль»: таблица | Каталог | Репозиторий | Роль | в /CLAUDE.md (роли admin|analyst|qa) # и/или пары --map. Идемпотентно. Обычные файлы на месте ссылок не трогает (ПРОПУЩЕН). @@ -7,7 +7,7 @@ set -euo pipefail WS="."; MAPS=() while [ $# -gt 0 ]; do case "$1" in - --map) MAPS+=("$2"); shift 2 ;; + --map) [ $# -ge 2 ] || { echo "link-roles: --map требует =" >&2; exit 2; }; MAPS+=("$2"); shift 2 ;; *) WS="$1"; shift ;; esac done @@ -29,7 +29,7 @@ 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 +mkdir -p .claude/roles .claude/agents link() { # $1 = путь ссылки (относительно WS), $2 = цель (относительно каталога ссылки) local l="$1" t="$2" @@ -55,7 +55,5 @@ for p in "${pairs[@]}"; 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 diff --git a/project-template/role-repo/CLAUDE.md b/project-template/role-repo/CLAUDE.md index caa49c1..f538a71 100644 --- a/project-template/role-repo/CLAUDE.md +++ b/project-template/role-repo/CLAUDE.md @@ -1,5 +1,5 @@ # {{DIR}} — репозиторий направления {{ROLE}} Кухня роли `{{ROLE}}` проекта {{PROJECT_NAME}}: рабочие материалы, `notes/` (уроки, решения владельца, рецепты), -`.claude/roles/{{ROLE}}.md` (проектный слой роли), `.claude/agents/` (агенты роли), `.claude/agent-memory/` (их память, коммитится). +`.claude/roles/{{ROLE}}.md` (проектный слой роли), `.claude/agents/` (агенты роли), память агентов — `notes/agent-memory.md` (спека 6.5; коммитится). Сессии запускаются из корня workspace; этот файл загружается при чтении файлов отсюда. Публикация в витрину `docs/` — через PR. diff --git a/project-template/workspace/CLAUDE.md b/project-template/workspace/CLAUDE.md index dae9959..43996ca 100644 --- a/project-template/workspace/CLAUDE.md +++ b/project-template/workspace/CLAUDE.md @@ -4,7 +4,7 @@ {{Одним абзацем: что за проект, для кого, что сейчас делается.}} ## Репозитории -Сессии запускаются из этого каталога. Роль сессии выбирается через `/role ` (подсказка хука по заголовку сессии `claude -n "<роль> …"`); правила роли — в `~/.claude/roles/.md` (общие) и `.claude/roles/.md` (проектные). Ссылки на проектные файлы ролей, агентов и их память создаёт `~/.claude/harness/link-roles.sh` по этой таблице. +Сессии запускаются из этого каталога. Роль сессии выбирается через `/role ` (подсказка хука по заголовку сессии `claude -n "<роль> …"`); правила роли — в `~/.claude/roles/.md` (общие) и `.claude/roles/.md` (проектные). Ссылки на проектные файлы ролей и агентов создаёт `~/.claude/harness/link-roles.sh` по этой таблице. | Каталог | Репозиторий | Роль | |---|---|---| diff --git a/skills/harness-doctor/scripts/doctor.sh b/skills/harness-doctor/scripts/doctor.sh index 719cd14..2c16183 100755 --- a/skills/harness-doctor/scripts/doctor.sh +++ b/skills/harness-doctor/scripts/doctor.sh @@ -90,7 +90,7 @@ elif [ -f "$DIR/CLAUDE.md" ] && grep -q '^| Каталог | Репозитор 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 kind in roles agents; do for l in "$ws/.claude/$kind"/*; do [ -e "$l" ] || [ -L "$l" ] || continue name="$kind/$(basename "$l")" diff --git a/tests/test-link-roles.sh b/tests/test-link-roles.sh index a510b1d..dcd6f88 100644 --- a/tests/test-link-roles.sh +++ b/tests/test-link-roles.sh @@ -22,8 +22,7 @@ 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 "agent-memory не создаётся (спека, раздел 11)" "$([ -e "$W/.claude/agent-memory" ] || [ -e "$W/admin/.claude/agent-memory" ] && echo есть || echo нет)" "нет" 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") @@ -31,18 +30,12 @@ out2=$(bash "$L" "$W" 2>&1); rc=$? assert_eq "второй запуск: exit 0" "$rc" "0" assert_eq "второй запуск: ничего не создано" "$(printf '%s' "$out2" | grep -c 'создан')" "0" assert_grep "второй запуск: 'есть'" 'есть' <(printf '%s' "$out2") -assert_eq "второй запуск: ссылка прежняя" "$(readlink "$W/.claude/agent-memory/jira-admin")" "../../admin/.claude/agent-memory/jira-admin" +assert_eq "второй запуск: ссылка прежняя" "$(readlink "$W/.claude/agents/jira-admin.md")" "../../admin/.claude/agents/jira-admin.md" # чужой обычный файл на месте ссылки не затирается 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") -# обычный каталог на месте ссылки памяти тоже не затирается -rm "$W/.claude/agent-memory/jira-admin"; mkdir "$W/.claude/agent-memory/jira-admin" -out5=$(bash "$L" "$W" 2>&1); rc=$? -assert_eq "каталог на месте ссылки: exit 0" "$rc" "0" -assert_eq "каталог на месте ссылки: не симлинк" "$([ -L "$W/.claude/agent-memory/jira-admin" ] && echo link || echo dir)" "dir" -assert_grep "каталог на месте ссылки → ПРОПУЩЕН" 'ПРОПУЩЕН' <(printf '%s' "$out5") # --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 @@ -56,3 +49,8 @@ assert_grep "нет файла роли: WARN" 'WARN' <(printf '%s' "$out4") # CLAUDE.md без таблицы и без --map: не падает W4="$TMP/ws4"; mkdir -p "$W4"; echo '# пусто' > "$W4/CLAUDE.md" bash "$L" "$W4" >/dev/null 2>&1; assert_eq "нет пар: exit 0" "$?" "0" +# --map без значения: русская ошибка и exit 2 +W5="$TMP/ws5"; mkdir -p "$W5" +err=$(bash "$L" "$W5" --map 2>&1 >/dev/null); rc=$? +assert_eq "--map без значения: exit 2" "$rc" "2" +assert_grep "--map без значения: сообщение" 'link-roles: --map требует =' <(printf '%s' "$err")