feat: шаблоны workspace и role-repo, режим --workspace в adopt/new-project
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:
parent
ceec425760
commit
a6b9fa5572
@ -4,6 +4,25 @@
|
||||
# Делает всё на ветке chore/harness-init и по умолчанию вливает её в main fast-forward.
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
# Режим workspace (каталог не git, рядом репозитории направлений): bash adopt-project.sh --workspace [папка]
|
||||
if [ "${1:-}" = "--workspace" ]; then
|
||||
cd "${2:-.}"; NAME="$(basename "$(pwd)")"
|
||||
if [ -f CLAUDE.md ]; then echo "CLAUDE.md: есть, не трогаю"
|
||||
else sed "s/{{PROJECT_NAME}}/$NAME/g" "$HERE/project-template/workspace/CLAUDE.md" > CLAUDE.md; echo "CLAUDE.md: создан из шаблона workspace — заполни {{...}} и таблицу репозиториев"; fi
|
||||
mkdir -p .claude/roles
|
||||
if [ -e .claude/roles/dev.md ]; then echo ".claude/roles/dev.md: есть, не трогаю"
|
||||
else sed "s/{{PROJECT_NAME}}/$NAME/g" "$HERE/project-template/workspace/roles/dev.md" > .claude/roles/dev.md; echo ".claude/roles/dev.md: заготовка"; fi
|
||||
bash "$HERE/link-roles.sh" "$(pwd)"
|
||||
cat <<MSG
|
||||
|
||||
Workspace $NAME подключён. Дальше:
|
||||
1. Заполни таблицу репозиториев в CLAUDE.md и перезапусти: bash $HERE/link-roles.sh $(pwd)
|
||||
2. Разреши агенту удалять ветки на сервере (чеклист закрытия, п. 4) — строка в .claude/settings.json → permissions.allow:
|
||||
"Bash(git push origin --delete:*)"
|
||||
3. Сессии: claude -n "dev …" | "admin …" | "qa …" | "analyst …" из этого каталога.
|
||||
MSG
|
||||
exit 0
|
||||
fi
|
||||
DIR="."; MERGE=1; PUSH=0
|
||||
for a in "$@"; do case "$a" in --no-merge) MERGE=0;; --push) PUSH=1;; *) DIR="$a";; esac; done
|
||||
cd "$DIR"
|
||||
|
||||
@ -3,6 +3,39 @@
|
||||
# Запуск: bash new-project.sh <папка> [ssh-url репозитория в Gitea]
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
# Режим workspace: bash new-project.sh --workspace <папка> [--role admin] [--role qa] [--role analyst]
|
||||
if [ "${1:-}" = "--workspace" ]; then
|
||||
DIR="${2:?usage: new-project.sh --workspace <dir> [--role <r> ...]}"; shift 2
|
||||
ROLES=()
|
||||
while [ $# -gt 0 ]; do case "$1" in --role) ROLES+=("$2"); shift 2 ;; *) echo "ERR: неизвестный аргумент $1"; exit 1 ;; esac; done
|
||||
mkdir -p "$DIR" && cd "$DIR"; NAME="$(basename "$(pwd)")"
|
||||
[ -f CLAUDE.md ] || sed "s/{{PROJECT_NAME}}/$NAME/g" "$HERE/project-template/workspace/CLAUDE.md" > CLAUDE.md
|
||||
mkdir -p .claude/roles
|
||||
[ -e .claude/roles/dev.md ] || sed "s/{{PROJECT_NAME}}/$NAME/g" "$HERE/project-template/workspace/roles/dev.md" > .claude/roles/dev.md
|
||||
for r in "${ROLES[@]+"${ROLES[@]}"}"; do
|
||||
case "$r" in admin|analyst|qa) ;; *) echo "ERR: роль $r не из admin|analyst|qa"; exit 1 ;; esac
|
||||
d="$r"; [ "$r" = analyst ] && d="analysis"
|
||||
if [ -d "$d/.git" ]; then echo "$d/: уже git, не трогаю"; continue; fi
|
||||
mkdir -p "$d/.claude/roles" "$d/notes"
|
||||
sed -e "s/{{PROJECT_NAME}}/$NAME/g" -e "s/{{ROLE}}/$r/g" -e "s#{{DIR}}#$d#g" "$HERE/project-template/role-repo/CLAUDE.md" > "$d/CLAUDE.md"
|
||||
sed -e "s/{{PROJECT_NAME}}/$NAME/g" -e "s/{{ROLE}}/$r/g" -e "s#{{DIR}}#$d#g" "$HERE/project-template/role-repo/ROLE.md" > "$d/.claude/roles/$r.md"
|
||||
cp "$HERE/project-template/role-repo/notes/README.md" "$d/notes/README.md"
|
||||
echo "$r" > "$d/.claude/role"
|
||||
git -C "$d" init -q -b main && git -C "$d" add -A && git -C "$d" commit -qm "chore: репозиторий направления $r (claude-harness)"
|
||||
# строка таблицы: заменить заготовку {{owner/repo-<r>}} на имя каталога, если заготовка есть
|
||||
sed -i.bak "s#| $d/ | {{owner/repo-[a-z]*}} | $r |#| $d/ | <owner>/$NAME-$d | $r |#" CLAUDE.md && rm -f CLAUDE.md.bak
|
||||
echo "$d/: репозиторий направления $r создан"
|
||||
done
|
||||
bash "$HERE/link-roles.sh" "$(pwd)"
|
||||
cat <<MSG
|
||||
|
||||
Workspace $NAME готов. Дальше:
|
||||
1. Заполни {{...}} в CLAUDE.md и в .claude/roles/dev.md; в репозиториях направлений — .claude/roles/<r>.md.
|
||||
2. Заведи репозитории направлений в Gitea и добавь remote (tea repos create …; git remote add origin …).
|
||||
3. cd $(pwd) && claude -n "dev …"
|
||||
MSG
|
||||
exit 0
|
||||
fi
|
||||
DIR="${1:?usage: new-project.sh <dir> [git-remote-url]}"
|
||||
REMOTE="${2:-}"
|
||||
NAME="$(basename "$DIR")"
|
||||
|
||||
5
project-template/role-repo/CLAUDE.md
Normal file
5
project-template/role-repo/CLAUDE.md
Normal file
@ -0,0 +1,5 @@
|
||||
# {{DIR}} — репозиторий направления {{ROLE}}
|
||||
|
||||
Кухня роли `{{ROLE}}` проекта {{PROJECT_NAME}}: рабочие материалы, `notes/` (уроки, решения владельца, рецепты),
|
||||
`.claude/roles/{{ROLE}}.md` (проектный слой роли), `.claude/agents/` (агенты роли), `.claude/agent-memory/` (их память, коммитится).
|
||||
Сессии запускаются из корня workspace; этот файл загружается при чтении файлов отсюда. Публикация в витрину `docs/` — через PR.
|
||||
15
project-template/role-repo/ROLE.md
Normal file
15
project-template/role-repo/ROLE.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Роль: {{ROLE}} — проектный слой {{PROJECT_NAME}}
|
||||
Эта роль заменяет предыдущую роль сессии целиком.
|
||||
|
||||
## Процесс
|
||||
- {{Инструменты роли в этом проекте, трекер, где лежат артефакты.}}
|
||||
|
||||
## Читать на старте
|
||||
- {{DIR}}/README.md
|
||||
- brief: {{DIR}}/notes/
|
||||
|
||||
## Не делать
|
||||
- {{Операционные ограничения проекта для этой роли.}}
|
||||
|
||||
## Делегировать
|
||||
- {{агент → для чего, если есть}}
|
||||
3
project-template/role-repo/notes/README.md
Normal file
3
project-template/role-repo/notes/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# notes/
|
||||
|
||||
Уроки, решения владельца (с датой) и рецепты роли. Коммитятся в той же ветке, что и работа. Состояние задач сюда не пишется — оно в трекере.
|
||||
25
project-template/workspace/CLAUDE.md
Normal file
25
project-template/workspace/CLAUDE.md
Normal file
@ -0,0 +1,25 @@
|
||||
# {{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.
|
||||
- {{Стенд, порты, что нельзя трогать.}}
|
||||
16
project-template/workspace/roles/dev.md
Normal file
16
project-template/workspace/roles/dev.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Роль: dev — разработчик (проектный слой {{PROJECT_NAME}})
|
||||
Эта роль заменяет предыдущую роль сессии целиком.
|
||||
|
||||
## Процесс
|
||||
- {{Репозитории кода и их CLAUDE.md; worktree `.wt/<задача>/` и что в него линкуется; `.bounded`; venv/сборка; стенд.}}
|
||||
- {{Источники истины для рулингов: модель данных, доменные правила.}}
|
||||
|
||||
## Читать на старте
|
||||
- {{путь к инвариантам / ARCHITECTURE.md}}
|
||||
- brief: `tea issues ls --state open`
|
||||
|
||||
## Не делать
|
||||
- {{Инварианты проекта: что не мутируется, кто единственный писатель, чего не хранить.}}
|
||||
|
||||
## Делегировать
|
||||
- SDD-контроллер → исполнение плана.
|
||||
45
tests/test-workspace.sh
Normal file
45
tests/test-workspace.sh
Normal file
@ -0,0 +1,45 @@
|
||||
# Шаблоны 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")" "моё"
|
||||
# старые режимы не затронуты
|
||||
P="$TMP/oldp"
|
||||
out=$(bash "$ROOT/new-project.sh" "$P" 2>&1); rc=$?
|
||||
assert_eq "new-project (старый режим): exit 0" "$rc" "0"
|
||||
assert_eq "new-project (старый режим): git" "$([ -d "$P/.git" ] && echo git || echo no)" "git"
|
||||
assert_file "new-project (старый режим): ARCHITECTURE.md" "$P/ARCHITECTURE.md"
|
||||
out=$(bash "$ROOT/adopt-project.sh" "$TMP/notgit" 2>&1); rc=$?
|
||||
assert_eq "adopt (старый режим): не git → exit 1" "$rc" "1"
|
||||
Loading…
x
Reference in New Issue
Block a user