fix: согласовать шаблоны и link-roles со спайком памяти агентов

link-roles.sh больше не создаёт симлинки agent-memory (спека, раздел 11:
симлинк делает память read-only), doctor не проверяет их; шаблоны
workspace и role-repo описывают запасной вариант 6.5 (notes/agent-memory.md).
В спеке разделы 2, 6.5, 8 получили указатель на раздел 11. Заодно --map
без значения даёт русскую ошибку и exit 2.

Refs #2

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzkPQWjfA7RpqYWkjnqJwt
This commit is contained in:
Andrey Epifancev 2026-10-12 00:47:47 +04:00
parent ed5d1f9014
commit 6744d40fb8
6 changed files with 17 additions and 21 deletions

View File

@ -28,7 +28,7 @@
settings.json как сейчас (хуки workspace, разрешения)
roles/<r>.md проектный слой ролей (dev — файл; admin/analyst/qa — симлинки в репозитории направлений)
agents/<name>.md симлинки на агентов из репозиториев направлений
agent-memory/<name>/ симлинки на память агентов в репозиториях направлений
agent-memory/<name>/ симлинки на память агентов (не работает, см. раздел 11)
<code repos>/ роль dev
docs/ витрина: контекст, модель, контракты, гайды, инструкции. Читают все, пишут через PR
admin/ → arm-ka-admin кухня admin: Jira, Гант, спринты, документы заказчику, notes/, .claude/
@ -47,7 +47,7 @@
.claude/role имя роли по умолчанию (одна строка) — для будущего режима запуска из репозитория
.claude/roles/<r>.md проектный слой роли
.claude/agents/<name>.md агенты роли
.claude/agent-memory/<name>/MEMORY.md память агентов (коммитится)
.claude/agent-memory/<name>/MEMORY.md память агентов (коммитится; см. раздел 11: симлинк не работает, применяется запасной вариант)
notes/ уроки, решения владельца, рецепты роли (markdown, коммитится)
<рабочие каталоги>
```
@ -183,7 +183,7 @@ SDD-контроллер и implementer-ы остаются описанием
### 6.5 Память агентов через симлинк
`memory: project` пишет в `<cwd-проект>/.claude/agent-memory/<name>/`. Чтобы память оказалась в git репозитория направления,
`<workspace>/.claude/agent-memory/<name>` — симлинк в `<dir>/.claude/agent-memory/<name>`. Проверяется первой (раздел 9).
`<workspace>/.claude/agent-memory/<name>` — симлинк в `<dir>/.claude/agent-memory/<name>`. Проверяется первой (раздел 9). (см. раздел 11: симлинк не работает; применяется запасной вариант)
Запасной вариант: поле `memory` не используется, промпт агента велит читать `<dir>/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`; существующий режим не меняется |

View File

@ -1,5 +1,5 @@
#!/usr/bin/env bash
# claude-harness: симлинки из <workspace>/.claude/ в репозитории направлений (роли, агенты, память агентов).
# claude-harness: симлинки из <workspace>/.claude/ в репозитории направлений (роли и агенты; память агентов симлинком не работает, спека раздел 11).
# Запуск: bash link-roles.sh [workspace] [--map <dir>=<role> ...]
# Источник пар «каталог → роль»: таблица | Каталог | Репозиторий | Роль | в <workspace>/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 требует <dir>=<role>" >&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

View File

@ -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.

View File

@ -4,7 +4,7 @@
{{Одним абзацем: что за проект, для кого, что сейчас делается.}}
## Репозитории
Сессии запускаются из этого каталога. Роль сессии выбирается через `/role <r>` (подсказка хука по заголовку сессии `claude -n "<роль> …"`); правила роли — в `~/.claude/roles/<r>.md` (общие) и `.claude/roles/<r>.md` (проектные). Ссылки на проектные файлы ролей, агентов и их память создаёт `~/.claude/harness/link-roles.sh` по этой таблице.
Сессии запускаются из этого каталога. Роль сессии выбирается через `/role <r>` (подсказка хука по заголовку сессии `claude -n "<роль> …"`); правила роли — в `~/.claude/roles/<r>.md` (общие) и `.claude/roles/<r>.md` (проектные). Ссылки на проектные файлы ролей и агентов создаёт `~/.claude/harness/link-roles.sh` по этой таблице.
| Каталог | Репозиторий | Роль |
|---|---|---|

View File

@ -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")"

View File

@ -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 требует <dir>=<role>' <(printf '%s' "$err")