Merge pull request 'Ретро 11.10.2026, спека ролей и памяти, CLAUDE.md по коду' (#3) from retro/2026-10-11 into main
Reviewed-on: #3
This commit is contained in:
commit
c6ce0584fb
2
.claude/code-paths
Normal file
2
.claude/code-paths
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
hooks/
|
||||||
|
skills/
|
||||||
39
CLAUDE.md
39
CLAUDE.md
@ -1,32 +1,43 @@
|
|||||||
# claude-harness
|
# claude-harness
|
||||||
|
|
||||||
## Что это
|
## Что это
|
||||||
{{ОДНИМ АБЗАЦЕМ: что делает система, для кого, главный сценарий.}}
|
Пакет обвязки для разработки с Claude Code: глобальный `CLAUDE.md` с процессом, три блокирующих хука, скрипты подключения проекта и skill диагностики. Для одного разработчика, который ведёт фичи через superpowers, хранит задачи в Gitea и требования в OpenSpec. Главный сценарий: `bash install.sh` ставит глобальную часть в `~/.claude`, `adopt-project.sh` подключает существующий проект, `new-project.sh` создаёт новый; дальше проект живёт по `docs/HARNESS.md`. Репозиторий — источник; установленные копии в `~/.claude` по месту не правятся.
|
||||||
|
|
||||||
## Стек и ограничения
|
## Стек и ограничения
|
||||||
- {{Язык, сборка, тесты — например: Python 3.12, uv, pytest.}}
|
- Bash (`set -euo pipefail`) и markdown. JSON из stdin хуков разбирается встроенным `python3`; других зависимостей времени выполнения нет. Внешние CLI (`tea`, `openspec`, `codegraph`) проверяются, но не требуются для установки.
|
||||||
- {{Жёсткие ограничения среды исполнения — без чего код не должен собираться.}}
|
- Хук блокирует вызов только через `exit 2` с сообщением в stderr; `exit 0` — пропустить. Любой сбой разбора входа должен приводить к пропуску, не к блокировке.
|
||||||
|
- `install.sh` идемпотентен: повторный запуск не ломает `settings.json` и делает бэкапы `CLAUDE.md` и `settings.json` с меткой времени.
|
||||||
|
- Хуки работают из `~/.claude/hooks/`, пути в `settings.hooks.json` через `$HOME`; `CLAUDE_CONFIG_DIR` переопределяет `~/.claude`.
|
||||||
|
- macOS и Linux; `python3` должен быть в PATH сессии Claude Code.
|
||||||
|
|
||||||
## Архитектура
|
## Архитектура
|
||||||
- `src/core/` (или {{ваш путь}}) — чистая логика без зависимостей от фреймворка/платформы. Покрывается тестами полностью.
|
- `hooks/` — поведение обвязки, единственный «код» с логикой. `guard-main.sh` (PreToolUse на Edit/Write/MultiEdit/Bash): запрет записи на main/master/detached HEAD, исключение для openspec-only коммитов. `require-plan.sh` (PreToolUse на Edit/Write/MultiEdit): правки в каталогах кода только при плане для ветки или файле `.bounded`. `harness-check.sh` (SessionStart): проверка, что проект подключён. `settings.hooks.json` — блок, который `install.sh` сливает в `~/.claude/settings.json`.
|
||||||
- `src/{{adapter}}/` — тонкая обёртка над платформой. Тестируется руками или интеграционно.
|
- `install.sh`, `adopt-project.sh`, `new-project.sh` — установка и бутстрап. `install.sh` копирует `global/`, `hooks/`, `project-template/`, `adopt-project.sh`, `new-project.sh` и `skills/harness-doctor` в `~/.claude/{CLAUDE.md,hooks,harness,skills}`. `adopt-project.sh` работает на ветке `chore/harness-init` и вливает её ff в текущую ветку; это единственная разрешённая запись на main вне PR.
|
||||||
- {{Ключевые интерфейсы/порты между ними, если есть.}}
|
- `global/CLAUDE.md` — глобальные правила процесса, ставятся в `~/.claude/CLAUDE.md`. `project-template/` — `CLAUDE.md`, `CLAUDE.harness-section.md` (секция «OpenSpec и superpowers», по ней `harness-check` узнаёт подключённый проект), `ARCHITECTURE.md`, `gitignore`.
|
||||||
|
- `skills/harness-doctor/` — skill диагностики: `scripts/doctor.sh` снимает состояние машины и проекта и прогоняет самотест хуков; `SKILL.md` — чеклист прочтения отчёта.
|
||||||
|
- `caveman/config.json` — выключенный по умолчанию caveman. `examples/` — реальный `CLAUDE.md` проекта-пилота.
|
||||||
|
- `docs/HARNESS.md` — как устроен цикл фичи, SDD, маркировка, закрытие. `docs/PILOT-2026-09-27.md` — пилот. `docs/retro/` — ретроспектива 11.10.2026 и скрипты подсчёта по транскриптам. `docs/superpowers/specs/` — спеки фич обвязки, `docs/superpowers/plans/` — планы.
|
||||||
|
- Связь слоёв: хуки читают JSON вызова из stdin и состояние git проекта; скрипты бутстрапа читают `project-template/`; `doctor.sh` читает установленные копии в `~/.claude`, не репозиторий.
|
||||||
|
|
||||||
## Команды
|
## Команды
|
||||||
- `{{npm test / pytest}}` — весь набор тестов. Зелёным считается только весь набор.
|
- `bash install.sh --check` — проверка окружения: бинарники, плагины, конфликты.
|
||||||
- `{{npm run build / ...}}` — сборка.
|
- `bash install.sh` — установка глобальной части, идемпотентно.
|
||||||
- `{{npm run dev / ...}}` — локальный запуск.
|
- `bash skills/harness-doctor/scripts/doctor.sh [проект]` — отчёт и самотест хуков. Только чтение.
|
||||||
|
- `bash adopt-project.sh <папка> [--no-merge] [--push]` — подключить существующий проект. Из терминала, не из Claude Code.
|
||||||
|
- `bash new-project.sh <папка> [ssh-url]` — каркас нового проекта.
|
||||||
|
- `python3 docs/retro/scripts/retro_stats.py <каталог транскриптов>` и `retro_blocks.py` — статистика сессий и блокировок хуков для ретро.
|
||||||
|
|
||||||
## Тесты
|
## Тесты
|
||||||
- Golden-тесты: `tests/fixtures/<name>.*` + эталон `<name>.expected.*`. Внешние сервисы в тестах мокаются.
|
- Отдельного набора тестов нет. Единственная автоматическая проверка — самотест хуков в `doctor.sh`: фиктивные вызовы Write/Bash на main и на ветке, `src/` с планом и без, `.bounded`. Прогоняется по установленным копиям, поэтому перед проверкой изменений в `hooks/` сначала `bash install.sh`.
|
||||||
- {{Где лежит тестовое окружение (vault, БД, песочница).}}
|
- Ручная проверка хука: на main в любом репозитории попросить агента создать файл, ожидается `BLOCKED`.
|
||||||
|
- Регрессионные тесты хуков на реальных командах из транскриптов — бэклог ретро B2, пока не сделаны.
|
||||||
|
|
||||||
## Первая подсистема
|
## Текущая работа
|
||||||
{{Что делается первым и почему. Тестовая инфраструктура — Task 1 первого плана.}}
|
Бэклог обвязки — `docs/retro/2026-10-11.md`, раздел 4 (B1–B12), решения владельца — раздел 5. Порядок: B1+B2 (guard-main по цели записи, тесты хуков), затем B5+B6 (роли и память, спека `docs/superpowers/specs/2026-10-11-roles-and-memory-design.md`), затем B3/B4/B10. Находки ретро маркируются `RT-n`.
|
||||||
|
|
||||||
## OpenSpec и superpowers
|
## OpenSpec и superpowers
|
||||||
- Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать.
|
- Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать.
|
||||||
- Когда brainstorming классифицировал задачу как architectural, структуру change создать через `openspec new change <id>` (или `/opsx:propose <id>`), а содержимое proposal.md, specs/ и design.md написать по итогам brainstorming. tasks.md из OpenSpec не заполнять — задачи живут в плане superpowers.
|
- Когда brainstorming классифицировал задачу как architectural, структуру change создать через `openspec new change <id>` (или `/opsx:propose <id>`), а содержимое proposal.md, specs/ и design.md написать по итогам brainstorming. tasks.md из OpenSpec не заполнять — задачи живут в плане superpowers.
|
||||||
- `/opsx:apply` не использовать: исполнение идёт через writing-plans и SDD/executing-plans.
|
- `/opsx:apply` не использовать: исполнение идёт через writing-plans и SDD/executing-plans.
|
||||||
- `openspec archive <id>` — после merge PR, как часть чеклиста закрытия.
|
- `openspec archive <id>` — после merge PR, как часть чеклиста закрытия.
|
||||||
- Хук require-plan защищает любой каталог `src/` на любой глубине. Если код лежит иначе — перечислить каталоги (префиксы от корня, по одному в строке) в `.claude/code-paths`.
|
- Хук require-plan защищает любой каталог `src/` на любой глубине. Если код лежит иначе — перечислить каталоги (префиксы от корня, по одному в строке) в `.claude/code-paths`. В этом репозитории код — `hooks/` и `skills/`, они перечислены в `.claude/code-paths`.
|
||||||
|
|||||||
189
docs/retro/2026-10-11.md
Normal file
189
docs/retro/2026-10-11.md
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# Ретроспектива обвязки claude-harness — 11.10.2026
|
||||||
|
|
||||||
|
Период: 02.10–11.10.2026, полигон — workspace `awp-ca` (АРМ КА: пять git-репозиториев рядом, workspace сам не git).
|
||||||
|
Предыдущая точка сравнения — `PILOT-2026-09-27.md` (один репозиторий, одна фича).
|
||||||
|
|
||||||
|
Источники: 47 транскриптов сессий Claude Code (`~/.claude/projects/-Users-andrey-epifancev-projects-awp-ca/*.jsonl`, 841 МБ),
|
||||||
|
47 файлов auto-memory того же проекта (179 КБ), код хуков, `HARNESS.md`, `PILOT-2026-09-27.md`, `CHANGELOG.md`.
|
||||||
|
Количественная часть считалась скриптами `scripts/retro_stats.py` и `scripts/retro_blocks.py` (только чтение транскриптов).
|
||||||
|
Качественная часть — прочтение всей памяти целиком.
|
||||||
|
|
||||||
|
Маркировка находок ретро: `RT-n`. Приоритет: P1 — теряет время каждую неделю или ломает процесс; P2 — трение; P3 — наблюдение.
|
||||||
|
Статус находки проставляется при закрытии бэклога (fixed / deferred / rejected), как и для любых других маркеров обвязки.
|
||||||
|
|
||||||
|
## 1. Цифры
|
||||||
|
|
||||||
|
| Показатель | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Сессий (непустых) | 44 за 10 дней; 3 сессии больше 20 МБ транскрипта |
|
||||||
|
| Промптов владельца | 613 |
|
||||||
|
| Ходов модели / вызовов инструментов | 12 121 / 6 583 (Bash 4 302, Playwright ≈1 100, Read 346, Write 271, Edit 233) |
|
||||||
|
| Запусков сабагентов | 61 (sonnet 47, opus 9, модель не указана 5) |
|
||||||
|
| Вызовов skills | brainstorming 19, writing-plans 14, systematic-debugging 8, SDD 4, executing-plans 3, using-git-worktrees 3, TDD 1, finishing-a-development-branch 1, verification-before-completion 0, requesting/receiving-code-review 0 |
|
||||||
|
| Токены | вывод 18,3 М; чтение из кэша 3,82 Г; запись в кэш 145 М |
|
||||||
|
| Компакций контекста | 4 (в двух сессиях) |
|
||||||
|
| Стартовый контекст до первого промпта | медиана 56 К токенов (мин 52 К, макс 61 К); наши инструкции ≈39 КБ текста ≈ 13–16 К токенов, остальное — системный промпт и схемы инструментов |
|
||||||
|
| Блокировок хуков | 103 (guard-main Bash 91, guard-main Edit/Write 8, require-plan 3, прочие 1) |
|
||||||
|
| Отказов классификатора auto mode | 6 (Git Destructive 2, Auto-Mode Bypass 2, Interfere With Workloads 1, safety-check 1); отказов владельца — 6 |
|
||||||
|
| `codegraph init` в командах / вызовов `codegraph_explore` | 43 / 15 |
|
||||||
|
| `cd` в основной checkout на main (не `.wt/`) | 84 |
|
||||||
|
| Промптов «перечитай память/контекст» | 9 из 613 |
|
||||||
|
|
||||||
|
Сессии по ролям (эвристика по инструментам и первым промптам): разработка — 32 сессии, 377 промптов; администрирование
|
||||||
|
(Jira, Гант, спринт) — 7 сессий, 122 промпта, все за 10–11.10; QA/фронт (Playwright > 30 вызовов) — 5 сессий, 81 промпт.
|
||||||
|
Администрирование и QA — треть промптов, при этом вся обвязка и 44 из 47 файлов памяти написаны под разработчика.
|
||||||
|
|
||||||
|
## 2. Находки
|
||||||
|
|
||||||
|
### Хуки
|
||||||
|
|
||||||
|
**RT-1 (P1). Три четверти блокировок guard-main — ложные.** Из 91 блокировки Bash:
|
||||||
|
36 — команда только чтения с `2>/dev/null` (регулярное выражение хука считает `N>` записью);
|
||||||
|
27 — команда без единого паттерна записи в репозиторий (чтение памяти, `grep`, `cat`), заблокирована потому, что cwd оболочки
|
||||||
|
стоял на main-checkout; 8 блокировок Edit/Write пришлись на файлы вне любого репозитория (scratchpad сессии, файлы памяти) —
|
||||||
|
хук смотрит на cwd, а не на путь файла. Ещё около 20 — шаги самого процесса, которые хук не узнал: `git -C <repo> worktree add`
|
||||||
|
(исключение есть только для литерала `git worktree`), `mkdir -p .wt/<задача>` в не-git workspace, архивный
|
||||||
|
`git add openspec && git commit` (проверка staged идёт до `add`). Реальных перехватов записи в main за 10 дней — единицы
|
||||||
|
(`sed -i` по docs на main в сессии d60ad490). Нарушений main при этом не было ни одного: хук защищает, но ценой шума.
|
||||||
|
Корень: решение принимается по cwd оболочки, а не по цели записи. То же даёт RT-2.
|
||||||
|
Источники: `retro_blocks.py`, память `workspace-hooks-and-worktrees`, `q1-2025-run`, `osv-inn-mapping-rework`, `console-files-state`.
|
||||||
|
|
||||||
|
**RT-2 (P1). Общий cwd оболочки с фоновым SDD-контроллером.** Один `cd` в основной checkout из главной сессии переводит
|
||||||
|
контроллер и всех его implementer-ов в состояние `BLOCKED: shell write on main`, и они останавливаются. Правило «не cd, пока
|
||||||
|
контроллер работает» записано в шести файлах памяти, то есть повторяется, а не устранено. Устраняется вместе с RT-1: хук,
|
||||||
|
не зависящий от cwd, не ломается от чужого `cd`. Источники: память `slice-2a/2c/3a/3b-state`, `auto-confirm-64`, `arm-web-demo-prototype`.
|
||||||
|
|
||||||
|
**RT-3 (P1). Пункт 4 чеклиста закрытия (удалить ветку на сервере) стабильно не выполняется.** `git push origin --delete`
|
||||||
|
блокируется guard-main при cwd на main и отклоняется классификатором auto mode как Git Destructive / Auto-Mode Bypass
|
||||||
|
(4 из 6 отказов классификатора за период). Висящие серверные ветки упомянуты в 10 файлах памяти; владелец удаляет их руками.
|
||||||
|
Найденный опытным путём обход (push из worktree на не-main ветке) нигде в обвязке не описан. Один раз агент обошёл хук
|
||||||
|
через Gitea API, за что получил замечание (`feedback-no-harness-bypass`).
|
||||||
|
|
||||||
|
**RT-4 (P1). Правило «архив OpenSpec прямо в main» не работает в многорепозиторном workspace.** Глобальный guard-main
|
||||||
|
пропускает openspec-only коммиты, workspace-хук awp-ca запрещает любой commit/push в main без исключений. Итог: около 15
|
||||||
|
отдельных архивных PR, а поведение «то проходит, то нет» описано в памяти как урок. CLAUDE.md и фактическая практика расходятся.
|
||||||
|
|
||||||
|
**RT-5 (P2). Workspace-хук срабатывает на слово `commit`/`push` в любом месте команды**, включая `echo`, и на `reference-old-arm`
|
||||||
|
в командах удаления симлинков при сносе worktree (владелец удаляет симлинки сам: `slice-3a-state`, `hotfolder-direct-write-state`,
|
||||||
|
`core-33-golden-tests`).
|
||||||
|
|
||||||
|
**RT-6 (P2). У хуков нет регрессионных тестов.** `doctor.sh` делает самотест на нескольких случаях, но ни один из 103 реальных
|
||||||
|
инцидентов не стал тестом. Транскрипты содержат готовый набор: команда + вердикт хука + факт, была ли это запись в main.
|
||||||
|
|
||||||
|
### SDD, сабагенты, классификатор
|
||||||
|
|
||||||
|
**RT-7 (P2). После одного отказа хука классификатор auto mode отклоняет у сабагента последующие коммиты как «Auto-Mode Bypass».**
|
||||||
|
Владелец добавил allow-правила `git -C .wt/*`, `git add`, `git commit` вручную. Следствие RT-1: меньше ложных отказов — меньше каскадов.
|
||||||
|
|
||||||
|
**RT-8 (P2). Поднятый стенд ломает integration/e2e-тесты сабагентов** (общая queue group NATS на `recognition.result`).
|
||||||
|
Повторилось минимум 5 раз, каждый раз оформлено как deferred-находка, системно не починено. Агенту гасить стенд нельзя
|
||||||
|
(Interfere With Workloads), владелец делает через `!`. Это проектная находка, но обвязка должна давать место для «предусловий
|
||||||
|
окружения перед SDD» (один раз проверить, а не пять раз откладывать).
|
||||||
|
|
||||||
|
**RT-9 (P3). haiku-implementer подписывает коммиты своим trailer вместо указанного в плане** (пилот, R-5/R-7 в `console-files-state`,
|
||||||
|
O-5 в `slice-3a-state`); переписать нельзя из-за запрета force-push. Третий повтор одной находки.
|
||||||
|
|
||||||
|
**RT-10 (P3). Остановленный владельцем контроллер не возобновляется**, нужен новый контроллер «на добивку» (`slice-3b-state`).
|
||||||
|
Ledger при этом работает: возобновление без потерь подтверждено и на пилоте, и в срезах.
|
||||||
|
|
||||||
|
**RT-11 (P3). Агент один раз спросил режим исполнения вопреки CLAUDE.md** (`feedback-execution-mode-from-claude-md`), и один раз
|
||||||
|
начал задачу «не по харнесу» (сессия e08f3f0b, владелец перезапустил задачу). Процессные правила в 9 КБ глобального CLAUDE.md
|
||||||
|
плюс 16 КБ проектного читаются не всегда; это аргумент за короткие ролевые файлы (RT-14).
|
||||||
|
|
||||||
|
### codegraph
|
||||||
|
|
||||||
|
**RT-12 (P2). 43 `codegraph init` против 15 вызовов `codegraph_explore` за 10 дней**; Bash — 4 302. В памяти нет ни одного
|
||||||
|
свидетельства, что codegraph ускорил задачу; есть только «init сделан» как ритуал на каждый worktree и пустой ответ на пилоте.
|
||||||
|
При этом правило «implementer вызывает codegraph_explore вместо grep/Read» стоит в глобальном CLAUDE.md. Сервер отстаёт по версии
|
||||||
|
(1.6.0 при доступной 1.6.2).
|
||||||
|
|
||||||
|
### Память и контекст
|
||||||
|
|
||||||
|
**RT-13 (P1). Auto-memory стала журналом состояния срезов и дублирует Gitea.** 30 из 47 файлов имеют статус «ЗАКРЫТ» и
|
||||||
|
фиксируют «issue #N закрыта, PR #M слит, ветка удалена» — то, что по замыслу обвязки помнит Gitea. Факты внутри файлов
|
||||||
|
расходятся с индексом (`slice-4-state`: в индексе «ЗАКРЫТ», в файле «ждут мержа»; `logger-diagnose-state`: ветки «не удалены»
|
||||||
|
в тексте, «удалены» в индексе). Один файл (`jira-backlog-actualization`, 15 КБ) — журнал версий артефакта v27…v37.
|
||||||
|
Уникальные знания, ради которых память нужна, занимают малую долю: уроки по хукам, три файла фидбека владельца, рецепты QA
|
||||||
|
(стенд, Playwright, скриншоты), структура Jira и ловушки REST, решения владельца по OQ.
|
||||||
|
|
||||||
|
**RT-14 (P1). Одна обвязка на четыре роли.** Разработчик, администратор проекта, аналитик требований и QA работают в одном
|
||||||
|
проекте с одним CLAUDE.md, одним индексом памяти и одним процессом. В админ-сессии грузятся инварианты DBML и поток
|
||||||
|
superpowers→Gitea→OpenSpec; в дев-сессии — Jira-память. Специализированных агентов в обвязке нет вовсе (ни `~/.claude/agents`,
|
||||||
|
ни `.claude/agents`), SDD-контроллер описан текстом. Дизайн решения согласован отдельно (skill `/role`, файлы ролей, агенты
|
||||||
|
с собственной памятью, реорганизация индекса); здесь фиксируется как находка.
|
||||||
|
|
||||||
|
**RT-15 (P3). Стартовый контекст 56 К токенов, из них наши инструкции 13–16 К.** Роли срежут 8–10 К в не-дев сессиях, но
|
||||||
|
главный выигрыш от ролей — не токены, а отсутствие чужих правил. Платформенная часть (≈40 К) не наша.
|
||||||
|
|
||||||
|
### Процесс и чеклисты
|
||||||
|
|
||||||
|
**RT-16 (P2). Bounded-путь не требует обновления спек.** Bounded-PR arm-core #52/#53 изменили поведение API без правки
|
||||||
|
`openspec/specs`; понадобился отдельный аудит (#60, четыре PR). Для bounded нужен один пункт: «меняется поведение API/схемы —
|
||||||
|
спека обновляется в том же PR».
|
||||||
|
|
||||||
|
**RT-17 (P2). Сборка многорепозиторного worktree делается руками каждый раз**: симлинки fixtures/CLAUDE.md/.claude/reference-old-arm,
|
||||||
|
`.bounded`, venv, симлинк `node_modules`, `codegraph init`. Ловушки: `worktree remove` отказывает из-за симлинка `node_modules`;
|
||||||
|
относительный путь в `git worktree add` считается от репозитория, не от cwd. Около 25 bounded-задач и 7 срезов — столько же ручных сборок.
|
||||||
|
|
||||||
|
**RT-18 (P3). Skills завершения не используются**: finishing-a-development-branch 1 раз, verification-before-completion и
|
||||||
|
requesting-code-review 0 раз. Закрытие и ревью идут по чеклисту CLAUDE.md и через SDD. Это не обязательно плохо, но значит,
|
||||||
|
что превентивные проверки (verification) для bounded-задач не выполняются формально.
|
||||||
|
|
||||||
|
**RT-19 (P3). harness-check и adopt-project не оставили следов в awp-ca**: workspace не git, хук молчит. Для режима
|
||||||
|
«workspace из нескольких репозиториев» у обвязки нет ни проверки, ни бутстрапа; `doctor.sh` его тоже не знает.
|
||||||
|
|
||||||
|
**RT-20 (P3). Follow-up issues растут быстрее, чем закрываются**: за период создано около 40 follow-up issue по чеклисту п. 6
|
||||||
|
(core, tp, sg, docs, web). Механизм работает, но нет ритуала их разбора. Это кандидат на роль администратора.
|
||||||
|
|
||||||
|
## 3. Что сработало (сохранить)
|
||||||
|
|
||||||
|
- **guard-main защитил main**: за 10 дней ни одного коммита в main мимо PR. Все потери — ложные срабатывания, не пропуски.
|
||||||
|
- **Ledger SDD** переживает обрыв контроллера; возобновление без потерь подтверждено повторно.
|
||||||
|
- **Маркировка находок и follow-up issue**: находки не теряются между сессиями, каждая имеет статус.
|
||||||
|
- **`design.md` «Decisions made without discussion»**: десятки решений на срез, прослеживаемы после удаления ledger и чата.
|
||||||
|
- **Финальное ревью ветки на opus** реально находит дефекты (I-1…I-4 в #42, волна C-1/I-1/M-1…M-8 в 3b).
|
||||||
|
- **Golden-тесты** как гарант парсеров: 138/138, 113 снимков, порог `MIN_SNAPSHOTS`.
|
||||||
|
- **Чеклист закрытия** выполняется целиком в большинстве случаев; слабое место — только п. 4 (RT-3).
|
||||||
|
- **Фидбек владельца, записанный в память** (три файла), изменил поведение: сверка с макетом, запрет обхода хуков, режим исполнения.
|
||||||
|
- **Спека как источник истины** при рулингах (F-1 на пилоте, решения №15/№16 в 3a).
|
||||||
|
|
||||||
|
## 4. Бэклог улучшений (по приоритету)
|
||||||
|
|
||||||
|
| # | Что | Находки | Оценка |
|
||||||
|
|---|---|---|---|
|
||||||
|
| B1 | guard-main: решать по цели записи, не по cwd. Игнорировать `N>`/`N>&`; для Edit/Write брать ветку репозитория файла; для Bash — `git -C <dir>` или пути в команде; записи вне git-репозиториев не блокировать; исключения для `git -C … worktree add`, `mkdir -p .wt/`; архивная проверка по путям в команде | RT-1, RT-2, RT-7 | 1 день + тесты |
|
||||||
|
| B2 | Регрессионные тесты хуков на реальных командах из транскриптов (103 случая → фикстуры с ожидаемым вердиктом); запуск из `doctor.sh` | RT-6 | 0,5 дня |
|
||||||
|
| B3 | Закрытие п. 4: явный рецепт в CLAUDE.md и HARNESS.md (push --delete из worktree на ветке фичи) либо allow-правило в проектных настройках по решению владельца; перестать перекладывать на владельца | RT-3 | 0,5 дня |
|
||||||
|
| B4 | Политика архива OpenSpec: либо «всегда отдельным PR» (как фактически), либо workspace-хук учится исключению openspec-only. Привести CLAUDE.md к выбранному | RT-4 | 0,5 дня |
|
||||||
|
| B5 | Роли: skill `/role`, файлы ролей (общие в обвязке, проектные в `.claude/roles/`), агенты с собственной памятью (`jira-admin`, `qa-runner`, `briefing`), подсказка роли по `session_title` | RT-14, RT-11, RT-15 | 2–3 дня |
|
||||||
|
| B6 | Политика памяти: состояние задачи живёт в Gitea (комментарий к issue при закрытии), в память — только уроки, фидбек, знания роли; при «закрывай» файл задачи сворачивается в строку реестра; индекс по ролям; одноразовая чистка 47 файлов | RT-13 | 1 день + чистка |
|
||||||
|
| B7 | Скрипт сборки/сноса многорепозиторного worktree (`wt-new`, `wt-rm`): симлинки, `.bounded`, venv, `node_modules`, (codegraph по флагу); режим «workspace из репозиториев» в `doctor.sh` и `harness-check` | RT-17, RT-19 | 1 день |
|
||||||
|
| B8 | codegraph: вывести из обязательного потока (без `init` на worktree и без правила «вместо grep»), оставить опциональным; решение владельца, нужны ли замеры | RT-12 | 0,5 дня |
|
||||||
|
| B9 | Bounded-чеклист: пункт про спеки при изменении API/схемы; verification-before-completion перед PR | RT-16, RT-18 | 0,5 дня |
|
||||||
|
| B10 | Workspace-хук: искать `git … commit/push` как команду, а не как слово; `reference-old-arm` только для `rm`-целей, не для симлинков | RT-5 | 0,5 дня |
|
||||||
|
| B11 | SDD: раздел «предусловия окружения» в плане (стенд погашен, порты свободны); trailer коммита проверяется ревьюером задачи | RT-8, RT-9 | 0,5 дня |
|
||||||
|
| B12 | Регулярное ретро: skill `/harness-retro` на этих скриптах, метрики: доля ложных блокировок, отказы классификатора, стартовый контекст, невыполненные пункты закрытия, размер памяти; триггер — по команде после среза | все | 1 день |
|
||||||
|
|
||||||
|
Порядок, который предлагается: B1+B2 (убирают главный источник трения и каскад RT-7), затем B5+B6 (роли и память — одно
|
||||||
|
изменение, так как индекс памяти перестраивается под роли), затем B3/B4/B10 (мелкие правила), B12, остальное по мере надобности.
|
||||||
|
|
||||||
|
## 5. Решения владельца (11.10.2026)
|
||||||
|
|
||||||
|
1. **Архив OpenSpec — всегда отдельным PR.** Исключение openspec-only из guard-main и из CLAUDE.md убирается; правило
|
||||||
|
«в main только через PR» становится без исключений. (B4 → переформулирован.)
|
||||||
|
2. **Удаление серверных веток — allow-правило в настройках проекта.** Строку правила владелец добавляет сам; обвязка даёт
|
||||||
|
готовую строку в `adopt-project.sh` и в отчёте `doctor.sh`. Чеклист закрытия п. 4 остаётся за агентом. (B3.)
|
||||||
|
3. **codegraph — оставить и замерить.** Один срез с обязательным codegraph и подсчётом вызовов `codegraph_explore` против
|
||||||
|
Bash/Read в тех же задачах; решение после замера. Метрика входит в `/harness-retro`. (B8 → замер, не вывод из потока.)
|
||||||
|
4. **Память — политика «состояние задачи в Gitea, в память только уроки, фидбек и знания роли» и одноразовая чистка
|
||||||
|
47 файлов по ролям.** Закрытые срезы сворачиваются в реестр (строка на задачу: PR, follow-up); чистка на копии, с бэкапом,
|
||||||
|
дифф показывается до замены. (B6.)
|
||||||
|
5. Роли: состав агентов на первом шаге решается в спеке ролей (предложено: `jira-admin`, `qa-runner`, `briefing`;
|
||||||
|
аналитику — файл роли без агента).
|
||||||
|
|
||||||
|
## 6. Что не удалось измерить
|
||||||
|
|
||||||
|
- Время, потерянное на каждую ложную блокировку: транскрипт не содержит длительности исправления, только факт.
|
||||||
|
- Польза codegraph: нет ни одного замера «с ним / без него».
|
||||||
|
- Доля решений в `design.md`, которые владелец потом пересмотрел: нужен разбор архивов OpenSpec, не делался.
|
||||||
|
- Поведение памяти вне git (slug по cwd) не описано в документации Claude Code; проверено только наблюдением.
|
||||||
135
docs/retro/scripts/retro_blocks.py
Normal file
135
docs/retro/scripts/retro_blocks.py
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Классификация блокировок хуков, отказов auto mode, стартового контекста и ручных напоминаний."""
|
||||||
|
import json, sys, glob, os, re, collections
|
||||||
|
|
||||||
|
d = sys.argv[1]
|
||||||
|
files = sorted(glob.glob(os.path.join(d, "*.jsonl")))
|
||||||
|
|
||||||
|
RE_REAL = re.compile(r'(^|[^<\d])>{1,2}[^&]|\btee\b|\bsed\s+-i|\bmv\b|\bcp\b|\brm\b|\btouch\b|\bmkdir\b|\bgit\s+(add|commit|push|apply|stash\s+pop)\b|\bnpm\s+(i|install|init)\b|\bpip\s+install\b')
|
||||||
|
RE_STDERR = re.compile(r'\d>{1,2}\s*/dev/null|\d>&\d')
|
||||||
|
RE_HEREDOC_PY = re.compile(r"<<\s*'?(PY|EOF)")
|
||||||
|
|
||||||
|
blocks = collections.Counter()
|
||||||
|
by_hook = collections.Counter()
|
||||||
|
by_session = collections.Counter()
|
||||||
|
examples = collections.defaultdict(list)
|
||||||
|
denials = collections.Counter()
|
||||||
|
denial_ex = []
|
||||||
|
start_ctx = []
|
||||||
|
recall_prompts = []
|
||||||
|
codegraph_init = 0
|
||||||
|
cd_main = 0
|
||||||
|
n_prompts_total = 0
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
sid = os.path.basename(f)[:8]
|
||||||
|
pending = {} # tool_use_id -> (name, input)
|
||||||
|
first_usage = None
|
||||||
|
title = ""
|
||||||
|
for line in open(f, encoding="utf-8", errors="replace"):
|
||||||
|
try:
|
||||||
|
j = json.loads(line)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
t = j.get("type")
|
||||||
|
if t == "ai-title" and not title:
|
||||||
|
title = j.get("aiTitle", "")
|
||||||
|
if t == "assistant":
|
||||||
|
m = j.get("message", {})
|
||||||
|
u = m.get("usage") or {}
|
||||||
|
if first_usage is None and u:
|
||||||
|
first_usage = (u.get("input_tokens", 0), u.get("cache_creation_input_tokens", 0), u.get("cache_read_input_tokens", 0))
|
||||||
|
for b in m.get("content", []) or []:
|
||||||
|
if isinstance(b, dict) and b.get("type") == "tool_use":
|
||||||
|
pending[b.get("id")] = (b.get("name"), b.get("input") or {})
|
||||||
|
if b.get("name") == "Bash":
|
||||||
|
cmd = (b.get("input") or {}).get("command", "")
|
||||||
|
if "codegraph init" in cmd:
|
||||||
|
codegraph_init += 1
|
||||||
|
if re.search(r"\bcd\s+\S*(arm-core|docs|tabular-parser|scan-gateway|arm-web)/?\s*($|;|&&)", cmd) and "/.wt/" not in cmd:
|
||||||
|
cd_main += 1
|
||||||
|
if t == "user":
|
||||||
|
m = j.get("message", {})
|
||||||
|
cont = m.get("content", "")
|
||||||
|
if isinstance(cont, str) and not cont.startswith("<") and not j.get("isMeta"):
|
||||||
|
n_prompts_total += 1
|
||||||
|
if re.search(r"памят|перечитай|сверься|контекст|вспомни|прочитай .*md", cont, re.I):
|
||||||
|
recall_prompts.append((sid, cont[:140].replace("\n", " ")))
|
||||||
|
if isinstance(cont, list):
|
||||||
|
for b in cont:
|
||||||
|
if not (isinstance(b, dict) and b.get("type") == "tool_result"):
|
||||||
|
continue
|
||||||
|
txt = b.get("content", "")
|
||||||
|
if isinstance(txt, list):
|
||||||
|
txt = "\n".join(x.get("text", "") for x in txt if isinstance(x, dict))
|
||||||
|
name, inp = pending.get(b.get("tool_use_id"), ("?", {}))
|
||||||
|
if "BLOCKED" in txt and ("hook" in txt.lower() or "BLOCKED:" in txt[:400]):
|
||||||
|
if name not in ("Bash", "Edit", "Write", "MultiEdit"):
|
||||||
|
continue
|
||||||
|
if "shell write" in txt:
|
||||||
|
hook = "guard-main(Bash)"
|
||||||
|
elif "you are on" in txt:
|
||||||
|
hook = "guard-main(Edit/Write)"
|
||||||
|
elif "without a plan" in txt:
|
||||||
|
hook = "require-plan"
|
||||||
|
elif "commit/push в main" in txt or "ветке main" in txt or "reference-old-arm" in txt or "docs/source" in txt:
|
||||||
|
hook = "workspace-guard"
|
||||||
|
else:
|
||||||
|
hook = "other"
|
||||||
|
by_hook[hook] += 1
|
||||||
|
by_session[sid] += 1
|
||||||
|
cmd = inp.get("command", "") if name == "Bash" else inp.get("file_path", "")
|
||||||
|
if name == "Bash":
|
||||||
|
if RE_STDERR.search(cmd) and not RE_REAL.sub("", cmd) != cmd:
|
||||||
|
cls = "stderr-redirect-only"
|
||||||
|
else:
|
||||||
|
stripped = RE_STDERR.sub("", cmd)
|
||||||
|
if RE_REAL.search(stripped):
|
||||||
|
mm = RE_REAL.search(stripped).group(0)
|
||||||
|
if re.match(r"^\s*(cat|python3|grep|ls|find|sed -n|head|git (log|status|diff|show|branch|worktree list))", cmd) and ("mkdir" not in cmd and "git commit" not in cmd and "git push" not in cmd and "git add" not in cmd and "rm " not in cmd):
|
||||||
|
cls = "read-only-but-matched:" + mm.strip()[:12]
|
||||||
|
else:
|
||||||
|
cls = "real-write:" + mm.strip()[:12]
|
||||||
|
else:
|
||||||
|
cls = "no-pattern(workspace or cwd)"
|
||||||
|
else:
|
||||||
|
cls = name
|
||||||
|
blocks[(hook, cls)] += 1
|
||||||
|
if len(examples[(hook, cls)]) < 3:
|
||||||
|
examples[(hook, cls)].append(f"[{sid}] {cmd[:150]!r}")
|
||||||
|
if "denied by the Claude Code auto mode classifier" in txt or "denied by a built-in Claude Code safety check" in txt or "The user doesn't want to proceed" in txt:
|
||||||
|
r = re.search(r"Reason: \[([^\]]+)\]", txt)
|
||||||
|
key = r.group(1) if r else ("safety-check" if "safety check" in txt else "user-rejected")
|
||||||
|
denials[key] += 1
|
||||||
|
cmd = inp.get("command", "") if name == "Bash" else name
|
||||||
|
denial_ex.append(f"[{sid}] {key}: {cmd[:140]!r}")
|
||||||
|
if first_usage:
|
||||||
|
start_ctx.append((sid, title[:30], sum(first_usage), first_usage))
|
||||||
|
|
||||||
|
print("== Блокировки по хукам")
|
||||||
|
for k, v in by_hook.most_common():
|
||||||
|
print(f"{k}: {v}")
|
||||||
|
print("\n== Блокировки по классу")
|
||||||
|
for k, v in sorted(blocks.items(), key=lambda x: -x[1]):
|
||||||
|
print(f"{v:3d} {k[0]} / {k[1]}")
|
||||||
|
for e in examples[k]:
|
||||||
|
print(" ", e)
|
||||||
|
print("\n== Сессии с наибольшим числом блокировок")
|
||||||
|
for k, v in by_session.most_common(8):
|
||||||
|
print(f"{k}: {v}")
|
||||||
|
print("\n== Отказы auto mode / safety / пользователь")
|
||||||
|
for k, v in denials.most_common():
|
||||||
|
print(f"{k}: {v}")
|
||||||
|
for e in denial_ex:
|
||||||
|
print(" ", e)
|
||||||
|
print("\n== Стартовый контекст (первый ответ модели): input+cache_create+cache_read")
|
||||||
|
start_ctx.sort(key=lambda x: -x[2])
|
||||||
|
for s, t, tot, fu in start_ctx:
|
||||||
|
print(f"{s} {tot:7d} (in={fu[0]} create={fu[1]} read={fu[2]}) {t}")
|
||||||
|
vals = [x[2] for x in start_ctx if x[2] > 0]
|
||||||
|
vals.sort()
|
||||||
|
print(f"median={vals[len(vals)//2]} min={vals[0]} max={vals[-1]} n={len(vals)}")
|
||||||
|
print(f"\n== codegraph init в командах: {codegraph_init}; cd в основной checkout (не .wt): {cd_main}")
|
||||||
|
print(f"\n== Промпты владельца с просьбой перечитать память/контекст: {len(recall_prompts)} из {n_prompts_total}")
|
||||||
|
for s, p in recall_prompts:
|
||||||
|
print(f" [{s}] {p}")
|
||||||
159
docs/retro/scripts/retro_stats.py
Normal file
159
docs/retro/scripts/retro_stats.py
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Статистика по транскриптам сессий Claude Code для ретро обвязки.
|
||||||
|
Только чтение. Вывод: таблица по сессиям + агрегаты + примеры блокировок."""
|
||||||
|
import json, sys, glob, os, re, collections, datetime
|
||||||
|
|
||||||
|
d = sys.argv[1]
|
||||||
|
files = sorted(glob.glob(os.path.join(d, "*.jsonl")))
|
||||||
|
rows = []
|
||||||
|
agg = collections.Counter()
|
||||||
|
blocked_examples = collections.defaultdict(list)
|
||||||
|
denied_examples = []
|
||||||
|
system_subtypes = collections.Counter()
|
||||||
|
hook_names = collections.Counter()
|
||||||
|
skills = collections.Counter()
|
||||||
|
agent_models = collections.Counter()
|
||||||
|
agent_types = collections.Counter()
|
||||||
|
first_prompts = {}
|
||||||
|
|
||||||
|
PAT_BLOCK = re.compile(r"BLOCKED[^\n]{0,160}")
|
||||||
|
PAT_DENY = re.compile(r"(Auto-Mode Bypass|permission denied|user denied|denied by|The user doesn't want|rejected this tool)", re.I)
|
||||||
|
|
||||||
|
def text_of(content):
|
||||||
|
if isinstance(content, str):
|
||||||
|
return content
|
||||||
|
out = []
|
||||||
|
if isinstance(content, list):
|
||||||
|
for b in content:
|
||||||
|
if isinstance(b, dict):
|
||||||
|
if b.get("type") == "text":
|
||||||
|
out.append(b.get("text", ""))
|
||||||
|
elif b.get("type") == "tool_result":
|
||||||
|
out.append(text_of(b.get("content", "")))
|
||||||
|
return "\n".join(out)
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
sid = os.path.basename(f)[:8]
|
||||||
|
c = collections.Counter()
|
||||||
|
title = ""
|
||||||
|
pm = ""
|
||||||
|
t0 = t1 = None
|
||||||
|
usage_in = usage_out = cache_r = cache_c = 0
|
||||||
|
human_prompts = 0
|
||||||
|
first_prompt = ""
|
||||||
|
for line in open(f, encoding="utf-8", errors="replace"):
|
||||||
|
try:
|
||||||
|
j = json.loads(line)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
t = j.get("type")
|
||||||
|
if t == "ai-title" and not title:
|
||||||
|
title = j.get("aiTitle", "")
|
||||||
|
if t == "permission-mode":
|
||||||
|
pm = j.get("permissionMode", "")
|
||||||
|
ts = j.get("timestamp")
|
||||||
|
if ts:
|
||||||
|
try:
|
||||||
|
tt = datetime.datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
||||||
|
t0 = tt if t0 is None or tt < t0 else t0
|
||||||
|
t1 = tt if t1 is None or tt > t1 else t1
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if t == "system":
|
||||||
|
system_subtypes[j.get("subtype", "?")] += 1
|
||||||
|
if j.get("subtype") == "compact_boundary" or "compact" in str(j.get("subtype", "")):
|
||||||
|
c["compact"] += 1
|
||||||
|
hn = j.get("hookName") or j.get("hook_name")
|
||||||
|
if hn:
|
||||||
|
hook_names[hn] += 1
|
||||||
|
if t == "user":
|
||||||
|
m = j.get("message", {})
|
||||||
|
cont = m.get("content", "")
|
||||||
|
if isinstance(cont, str):
|
||||||
|
if not cont.startswith("<") and j.get("userType", "external") == "external" and not j.get("isMeta"):
|
||||||
|
human_prompts += 1
|
||||||
|
if not first_prompt:
|
||||||
|
first_prompt = cont[:120].replace("\n", " ")
|
||||||
|
elif isinstance(cont, list):
|
||||||
|
has_tr = any(isinstance(b, dict) and b.get("type") == "tool_result" for b in cont)
|
||||||
|
if not has_tr and not j.get("isMeta"):
|
||||||
|
txt = text_of(cont)
|
||||||
|
if txt and not txt.startswith("<"):
|
||||||
|
human_prompts += 1
|
||||||
|
if not first_prompt:
|
||||||
|
first_prompt = txt[:120].replace("\n", " ")
|
||||||
|
for b in cont:
|
||||||
|
if isinstance(b, dict) and b.get("type") == "tool_result":
|
||||||
|
txt = text_of(b.get("content", ""))
|
||||||
|
for mm in PAT_BLOCK.findall(txt):
|
||||||
|
c["blocked"] += 1
|
||||||
|
key = re.sub(r"\s+", " ", mm)[:90]
|
||||||
|
if len(blocked_examples[key]) < 2:
|
||||||
|
blocked_examples[key].append(sid)
|
||||||
|
if PAT_DENY.search(txt):
|
||||||
|
c["denied"] += 1
|
||||||
|
if len(denied_examples) < 15:
|
||||||
|
denied_examples.append((sid, re.sub(r"\s+", " ", txt)[:160]))
|
||||||
|
# hook feedback in attachments / text
|
||||||
|
txt = text_of(cont)
|
||||||
|
if "BLOCKED" in txt and isinstance(cont, str):
|
||||||
|
c["blocked_text"] += 1
|
||||||
|
if t == "assistant":
|
||||||
|
m = j.get("message", {})
|
||||||
|
u = m.get("usage", {}) or {}
|
||||||
|
usage_in += u.get("input_tokens", 0) or 0
|
||||||
|
usage_out += u.get("output_tokens", 0) or 0
|
||||||
|
cache_r += u.get("cache_read_input_tokens", 0) or 0
|
||||||
|
cache_c += u.get("cache_creation_input_tokens", 0) or 0
|
||||||
|
c["assistant"] += 1
|
||||||
|
for b in m.get("content", []) or []:
|
||||||
|
if isinstance(b, dict) and b.get("type") == "tool_use":
|
||||||
|
n = b.get("name", "")
|
||||||
|
c["tool"] += 1
|
||||||
|
c["t:" + n] += 1
|
||||||
|
agg["t:" + n] += 1
|
||||||
|
inp = b.get("input", {}) or {}
|
||||||
|
if n in ("Agent", "Task"):
|
||||||
|
agent_models[inp.get("model", "default")] += 1
|
||||||
|
agent_types[inp.get("subagent_type", "?")] += 1
|
||||||
|
if n == "Skill":
|
||||||
|
skills[inp.get("skill", "?")] += 1
|
||||||
|
dur = (t1 - t0).total_seconds() / 3600 if t0 and t1 else 0
|
||||||
|
rows.append(dict(sid=sid, date=t0.strftime("%m-%d") if t0 else "?", hours=round(dur, 1), title=title[:38], pm=pm,
|
||||||
|
prompts=human_prompts, asst=c["assistant"], tools=c["tool"], agents=c["t:Agent"] + c["t:Task"],
|
||||||
|
blocked=c["blocked"], denied=c["denied"], compact=c["compact"],
|
||||||
|
out_k=usage_out // 1000, cache_r_m=round(cache_r / 1e6, 1), first=first_prompt[:70]))
|
||||||
|
for k in ("assistant", "tool", "blocked", "denied", "compact"):
|
||||||
|
agg[k] += c[k]
|
||||||
|
agg["prompts"] += human_prompts
|
||||||
|
agg["out"] += usage_out
|
||||||
|
agg["cache_r"] += cache_r
|
||||||
|
agg["cache_c"] += cache_c
|
||||||
|
agg["hours"] += dur
|
||||||
|
|
||||||
|
rows.sort(key=lambda r: r["date"])
|
||||||
|
hdr = ["sid", "date", "hours", "pm", "prompts", "asst", "tools", "agents", "blocked", "denied", "compact", "out_k", "cache_r_m", "title"]
|
||||||
|
print("\t".join(hdr))
|
||||||
|
for r in rows:
|
||||||
|
print("\t".join(str(r[h]) for h in hdr))
|
||||||
|
print("\n== Первые промпты сессий")
|
||||||
|
for r in rows:
|
||||||
|
print(f"{r['date']} {r['sid']} | {r['first']}")
|
||||||
|
print("\n== Агрегаты")
|
||||||
|
for k in ("prompts", "assistant", "tool", "blocked", "denied", "compact"):
|
||||||
|
print(f"{k}: {agg[k]}")
|
||||||
|
print(f"hours: {agg['hours']:.0f}; output tokens: {agg['out']/1e6:.1f}M; cache read: {agg['cache_r']/1e9:.2f}G; cache create: {agg['cache_c']/1e6:.0f}M")
|
||||||
|
print("\n== Инструменты (top 25)")
|
||||||
|
for k, v in sorted(((k, v) for k, v in agg.items() if k.startswith("t:")), key=lambda x: -x[1])[:25]:
|
||||||
|
print(f"{k[2:]}: {v}")
|
||||||
|
print("\n== Skills"); [print(f"{k}: {v}") for k, v in skills.most_common()]
|
||||||
|
print("\n== Модели сабагентов"); [print(f"{k}: {v}") for k, v in agent_models.most_common()]
|
||||||
|
print("\n== Типы сабагентов"); [print(f"{k}: {v}") for k, v in agent_types.most_common()]
|
||||||
|
print("\n== system subtypes"); [print(f"{k}: {v}") for k, v in system_subtypes.most_common(15)]
|
||||||
|
print("\n== hookName в system"); [print(f"{k}: {v}") for k, v in hook_names.most_common(15)]
|
||||||
|
print("\n== Блокировки хуков (уникальные тексты, top 40)")
|
||||||
|
for k, v in sorted(blocked_examples.items(), key=lambda x: -len(x[1]))[:40]:
|
||||||
|
print(f"[{','.join(v)}] {k}")
|
||||||
|
print("\n== Отказы (примеры)")
|
||||||
|
for s, e in denied_examples:
|
||||||
|
print(f"[{s}] {e}")
|
||||||
287
docs/superpowers/specs/2026-10-11-roles-and-memory-design.md
Normal file
287
docs/superpowers/specs/2026-10-11-roles-and-memory-design.md
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
# Роли и память: дизайн
|
||||||
|
|
||||||
|
Дата: 11.10.2026. Источник: ретроспектива `docs/retro/2026-10-11.md`, находки RT-11, RT-13, RT-14, RT-15, бэклог B5+B6.
|
||||||
|
Полигон: workspace `awp-ca`. Решение должно подходить и для следующих проектов.
|
||||||
|
|
||||||
|
## 1. Цель
|
||||||
|
|
||||||
|
Одна сессия Claude Code работает в одной из четырёх ролей: разработчик (`dev`), руководитель/администратор проекта (`admin`),
|
||||||
|
аналитик требований (`analyst`), QA (`qa`). Сессия получает только контекст, правила и память своей роли, без ручных
|
||||||
|
просьб «перечитай». Знания роли живут в git-репозитории направления, а не в локальной auto-memory, и переживают смену машины.
|
||||||
|
|
||||||
|
Критерии успеха:
|
||||||
|
- владелец начинает сессию одной фразой или заголовком, роль определяется и объявляется без вопроса, когда она очевидна;
|
||||||
|
- в сессии admin не загружаются инварианты DBML и процесс superpowers→Gitea→OpenSpec; в сессии dev не загружается Jira;
|
||||||
|
- ни одно правило из нынешних `~/.claude/CLAUDE.md` и `awp-ca/CLAUDE.md` не потеряно: у каждого абзаца есть новый адрес;
|
||||||
|
- глобальный CLAUDE.md ≤ 3 КБ, проектный ≤ 5 КБ, индекс auto-memory ≤ 3 КБ;
|
||||||
|
- стартовый контекст не-dev сессий меньше текущей медианы 56 К токенов (замер скриптом ретро);
|
||||||
|
- хуки guard-main, require-plan, harness-check не меняются.
|
||||||
|
|
||||||
|
## 2. Модель workspace
|
||||||
|
|
||||||
|
Все сессии запускаются из корня workspace (не git). Рядом лежат репозитории направлений.
|
||||||
|
|
||||||
|
```
|
||||||
|
<project>/ workspace, не git, cwd всех сессий
|
||||||
|
CLAUDE.md общий минимум: что за проект, таблица репозиториев с ролью-владельцем, источники истины, «роли → /role»
|
||||||
|
.claude/
|
||||||
|
settings.json как сейчас (хуки workspace, разрешения)
|
||||||
|
roles/<r>.md проектный слой ролей (dev — файл; admin/analyst/qa — симлинки в репозитории направлений)
|
||||||
|
agents/<name>.md симлинки на агентов из репозиториев направлений
|
||||||
|
agent-memory/<name>/ симлинки на память агентов в репозиториях направлений
|
||||||
|
<code repos>/ роль dev
|
||||||
|
docs/ витрина: контекст, модель, контракты, гайды, инструкции. Читают все, пишут через PR
|
||||||
|
admin/ → arm-ka-admin кухня admin: Jira, Гант, спринты, документы заказчику, notes/, .claude/
|
||||||
|
qa/ → arm-ka-qa кухня qa: сценарии, прогоны, скриншоты, находки, notes/, .claude/
|
||||||
|
analysis/ → arm-ka-analysis кухня analyst: черновики OQ, протоколы, сверки ТЗ с кодом, notes/, .claude/
|
||||||
|
```
|
||||||
|
|
||||||
|
Правило: **docs/ публикует, репозиторий направления работает.** У каждого раздела docs/ есть роль-владелец:
|
||||||
|
`context/`, `model/` — analyst; `dev/`, `contracts/` — dev; `user/`, `dev/qa-guide.md` — qa; `source/` — никто (экспорт).
|
||||||
|
Рабочий материал (журналы прогонов, скриншоты, выгрузки Jira, черновики решений) в витрину не попадает или попадает после обработки.
|
||||||
|
|
||||||
|
Репозиторий направления содержит:
|
||||||
|
```
|
||||||
|
<dir>/
|
||||||
|
CLAUDE.md что это за репозиторий, 5–10 строк (загружается лениво при чтении файлов из него)
|
||||||
|
.claude/role имя роли по умолчанию (одна строка) — для будущего режима запуска из репозитория
|
||||||
|
.claude/roles/<r>.md проектный слой роли
|
||||||
|
.claude/agents/<name>.md агенты роли
|
||||||
|
.claude/agent-memory/<name>/MEMORY.md память агентов (коммитится)
|
||||||
|
notes/ уроки, решения владельца, рецепты роли (markdown, коммитится)
|
||||||
|
<рабочие каталоги>
|
||||||
|
```
|
||||||
|
|
||||||
|
Симлинки из `<workspace>/.claude/` в репозитории направлений создаёт скрипт `link-roles.sh` (раздел 8), проверяет `doctor.sh`.
|
||||||
|
Claude Code находит агентов и skills только в `<cwd>/.claude/`, поэтому без ссылок конфигурация направлений из корня не видна.
|
||||||
|
|
||||||
|
## 3. Слои ролей
|
||||||
|
|
||||||
|
Два слоя, оба в git:
|
||||||
|
|
||||||
|
| Слой | Где в источнике | Куда ставится | Что содержит |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Общий | `claude-harness/roles/<r>.md` | `~/.claude/roles/<r>.md` (install.sh) | процесс роли, не зависящий от проекта |
|
||||||
|
| Проектный | `<repo направления>/.claude/roles/<r>.md`, для dev — `<workspace>/.claude/roles/dev.md` | симлинк в `<workspace>/.claude/roles/` | пути, инструменты, люди, ограничения проекта |
|
||||||
|
|
||||||
|
При конфликте побеждает проектный слой. Общий слой `roles/dev.md` получает целиком нынешний процесс разработки из
|
||||||
|
`global/CLAUDE.md` (маршрутизация, исполнение, завершение, маркировка, стиль). Проектный `roles/dev.md` в awp-ca получает
|
||||||
|
из `awp-ca/CLAUDE.md` инварианты, стек, зеркальный worktree `.wt/`, DBML, `05-domain-rules`, стенд, «чего не делать».
|
||||||
|
|
||||||
|
### 3.1 Формат файла роли
|
||||||
|
|
||||||
|
Одинаков для обоих слоёв. Четыре раздела, заголовки фиксированы, чтобы `/role` мог их склеить:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Роль: admin — руководитель/администратор проекта
|
||||||
|
Эта роль заменяет предыдущую роль сессии целиком.
|
||||||
|
|
||||||
|
## Процесс
|
||||||
|
- какие skills и чеклисты действуют; что по умолчанию bounded; где трекер задач этой роли
|
||||||
|
|
||||||
|
## Читать на старте
|
||||||
|
- пути (относительно workspace) и команды; строки с префиксом `brief:` уходят агенту briefing, остальные читаются в контекст
|
||||||
|
|
||||||
|
## Не делать
|
||||||
|
- явные запреты роли
|
||||||
|
|
||||||
|
## Делегировать
|
||||||
|
- агент → для чего
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Содержание общих файлов ролей (обвязка)
|
||||||
|
|
||||||
|
- **dev** — нынешний глобальный CLAUDE.md без изменений по смыслу, плюс уроки из ретро, не зависящие от проекта:
|
||||||
|
не `cd` в основной checkout, пока работает фоновый контроллер; абсолютные пути worktree в планах; trailer коммита
|
||||||
|
проверяет ревьюер задачи; архив OpenSpec всегда отдельным PR (решение владельца 11.10.2026); пункт 4 чеклиста закрытия —
|
||||||
|
через allow-правило проекта. Bounded-чеклист дополняется: «если меняется поведение API или схемы, спека обновляется в том же PR».
|
||||||
|
- **admin** — задачи ведутся в трекере заказчика (Jira) или в репозитории направления, Gitea-issue и OpenSpec не заводятся;
|
||||||
|
brainstorming остаётся входом, задачи по умолчанию bounded; код только в репозитории направления (скрипты, TDD);
|
||||||
|
тексты для внешних людей самодостаточны (без внутренних маркеров, номеров PR и путей); артефакты для передачи — в `deliverables/`.
|
||||||
|
- **analyst** — работа с требованиями: спеки, открытые вопросы, решения владельца, сверка ТЗ с кодом; код не пишется;
|
||||||
|
публикация в docs/ через PR; противоречия документов не решаются молча, а фиксируются вопросом.
|
||||||
|
- **qa** — стенд, прогоны сценариев, сверка каждого экрана с макетом, гайды и инструкции; код не меняется;
|
||||||
|
находки — списком со скриншотами, issue в кодовый репозиторий заводит dev по решению владельца.
|
||||||
|
|
||||||
|
### 3.3 Проектные файлы ролей awp-ca
|
||||||
|
|
||||||
|
- `dev` (workspace): репозитории и их CLAUDE.md, зеркальный worktree `.wt/<задача>/` с симлинками, `.bounded`, venv через
|
||||||
|
`uv venv --python 3.11`, стенд `:8000/:3000`, DBML v5.18 и `05-domain-rules` как источники истины, инварианты (первичка не
|
||||||
|
мутируется, производные не хранятся, VERIFIED read-only, единственный писатель arm-core, права — данные, один файл = N
|
||||||
|
документов, парсеры не по координатам, деньги numeric, append-only аудит), «чего не делать», синхронизация `arm-core/web`
|
||||||
|
скриптом `sync-web.sh`, codegraph обязателен на период замера (решение владельца 11.10.2026).
|
||||||
|
- `admin` (в `admin/`): `admin/README.md`, пакет `jira-sync/` и его README, Jira AURORA (доска 4216, правила текстов задач,
|
||||||
|
переходы статусов), Гант (`source/*.mpp`, читать mpxj, Ган не трогать), просмотрщик `sprint-2-links.html`, команда и зоны
|
||||||
|
ответственности, текущие операционные ограничения: `apply` в реальную Jira выполняет только владелец на виртуалке
|
||||||
|
(ограничение снимается вместе с мостом из `jira-sync/next-session-bridge.md`). `brief:` — `admin/notes/`, последний `jira-sync/runs/<дата>/`.
|
||||||
|
- `analyst` (в `analysis/`): `docs/context/05-domain-rules.md`, `07-open-questions.md`, `04-data-model-guide.md`, DBML,
|
||||||
|
`docs/source/tz/` (имена v4, алгоритмы рабочие), `08-source-docs-index.md`; как оформлять OQ и решения; `brief:` — `analysis/notes/decisions.md`, открытые OQ.
|
||||||
|
- `qa` (в `qa/`): `docs/dev/qa-guide.md` (§8 чек-лист UI), стенд и его ловушки, Playwright (окно 1600×1000, макет через
|
||||||
|
`http.server`, запасной runner), `docs/mockup/screens/*.png`, `docs/user/user-guide.md` и рецепт скриншотов и docx,
|
||||||
|
контрольные цифры прогонов; `brief:` — `qa/notes/`, последний `qa/runs/<дата>/`.
|
||||||
|
|
||||||
|
## 4. Выбор роли
|
||||||
|
|
||||||
|
Три входа, от автоматического к ручному. Запуск всегда из корня workspace, git-корень для определения роли не используется.
|
||||||
|
|
||||||
|
1. **Заголовок сессии.** `claude -n "admin …"` (алиасы `cc-admin`, `cc-dev`, `cc-qa`, `cc-analyst` в профиле shell —
|
||||||
|
рекомендация в README, не часть install.sh). Хук `hooks/role-hint.sh` на SessionStart (все источники: startup, resume,
|
||||||
|
clear, compact) читает `session_title`; если он начинается с имени роли, печатает в stdout одну строку:
|
||||||
|
`[claude-harness] Роль сессии: admin. Вызови /role admin до первого ответа.` Без заголовка молчит. Ничего не блокирует.
|
||||||
|
2. **Вывод из первого сообщения.** Правило в глобальном CLAUDE.md: если роль очевидна (Jira, Гант, спринт → admin; стенд,
|
||||||
|
прогон, скриншот, макет → qa; ТЗ, OQ, спека, методика → analyst; срез, issue, PR, тест, миграция → dev), сессия объявляет
|
||||||
|
её одной строкой «Роль: admin; если не так, скажи» и вызывает `/role`. Если неочевидна, один вопрос с четырьмя вариантами.
|
||||||
|
3. **Явно.** `/role <r>` в любой момент. Новая роль заменяет предыдущую, первая строка файла роли это говорит.
|
||||||
|
|
||||||
|
## 5. Skill `/role`
|
||||||
|
|
||||||
|
Источник: `claude-harness/skills/role/SKILL.md`, ставится в `~/.claude/skills/role/`. Frontmatter: `name: role`,
|
||||||
|
`description`, `argument-hint: dev|admin|analyst|qa`, `user-invocable: true`. Шаги:
|
||||||
|
|
||||||
|
1. Прочитать `~/.claude/roles/<r>.md` и `<workspace>/.claude/roles/<r>.md` (если проектного нет — сказать об этом одной строкой и работать по общему).
|
||||||
|
2. Прочитать раздел роли из индекса auto-memory (`## <Роль>`) и перечисленные в нём файлы.
|
||||||
|
3. Собрать строки `brief:` из раздела «Читать на старте» обоих слоёв и, если они есть, запустить агента `briefing`
|
||||||
|
в фоне (`Agent`, `subagent_type: briefing`, `run_in_background`), передав роль и список. Остальные строки прочитать в контекст.
|
||||||
|
4. Ответить пятью строками: роль; действующий процесс; что прочитано; чего в этой роли не делать; что ждёт решения владельца
|
||||||
|
(из памяти роли). Когда придёт выжимка briefing, добавить её одним сообщением без пересказа.
|
||||||
|
|
||||||
|
`/role` без аргумента печатает текущую роль и список доступных.
|
||||||
|
|
||||||
|
## 6. Агенты
|
||||||
|
|
||||||
|
Все агенты — markdown с frontmatter Claude Code (`name`, `description`, `model`, `tools`, `memory`). Модель указывается явно.
|
||||||
|
|
||||||
|
### 6.1 `briefing` (общий, обвязка)
|
||||||
|
|
||||||
|
`claude-harness/agents/briefing.md` → `~/.claude/agents/briefing.md`. `model: sonnet`, `tools: Read, Glob, Grep, Bash`,
|
||||||
|
без `memory`. Промпт: получает роль и список путей/команд; команды выполняет только читающие (`tea issues ls`, `openspec list`,
|
||||||
|
`git log`, `ls`); возвращает выжимку до 60 строк в фиксированной форме: **Активные задачи** / **Решения за последние дни** /
|
||||||
|
**Открытые вопросы** / **Ловушки**. Не предлагает действий, не редактирует файлы.
|
||||||
|
|
||||||
|
### 6.2 `jira-admin` (в `admin/.claude/agents/`)
|
||||||
|
|
||||||
|
`model: sonnet`, `memory: project`, `tools: Read, Write, Edit, Bash, Glob, Grep`. Промпт содержит только инварианты,
|
||||||
|
не зависящие от инструмента:
|
||||||
|
- CSV в `admin/jira-sync/` — источник истины и единственное место ключей созданных задач; ключи пишутся обратно сразу;
|
||||||
|
- задания декларативные (что сделать), не произвольные команды; пароль Jira нигде, кроме `config.json` виртуалки;
|
||||||
|
- тексты задач по правилам README пакета (самодостаточные, структура h3 Контекст / Что сделать / Критерии готовности / Источники);
|
||||||
|
- инструментарий берётся из `admin/jira-sync/README.md` и доступных MCP-инструментов; что можно выполнять против реальной Jira — из проектного файла роли admin.
|
||||||
|
Задачи: `discover`/`plan` в сухом режиме, перегенерация CSV, сверка Ганта с выгрузкой, черновики описаний, разбор спринта по CSV.
|
||||||
|
Память: customfield'ы, ловушки REST (направление `issueLink`), люди и зоны, история актуализаций.
|
||||||
|
|
||||||
|
### 6.3 `qa-runner` (в `qa/.claude/agents/`)
|
||||||
|
|
||||||
|
`model: sonnet`, `memory: project`, инструменты: Read, Bash, Glob, Grep и Playwright MCP (`mcp__plugin_playwright_playwright__*`),
|
||||||
|
без Write/Edit в репозиториях кода (запись только в `qa/runs/`). Задачи: прогон сценария на локальном стенде, сверка каждого экрана
|
||||||
|
с PNG макета при окне 1600×1000, список расхождений со скриншотами в `qa/runs/<дата>/`. Не меняет код; находки отдаёт списком.
|
||||||
|
Память: ловушки стенда, порты, токены (`sessionStorage['armka.token']`), запасной runner playwright-core, контрольные цифры.
|
||||||
|
|
||||||
|
### 6.4 Что не меняется
|
||||||
|
|
||||||
|
SDD-контроллер и implementer-ы остаются описанием в `roles/dev.md` (superpowers запускает их сам). Аналитик без агента.
|
||||||
|
|
||||||
|
### 6.5 Память агентов через симлинк
|
||||||
|
|
||||||
|
`memory: project` пишет в `<cwd-проект>/.claude/agent-memory/<name>/`. Чтобы память оказалась в git репозитория направления,
|
||||||
|
`<workspace>/.claude/agent-memory/<name>` — симлинк в `<dir>/.claude/agent-memory/<name>`. Проверяется первой (раздел 9).
|
||||||
|
Запасной вариант: поле `memory` не используется, промпт агента велит читать `<dir>/notes/agent-memory.md` при старте и дописывать в конце.
|
||||||
|
|
||||||
|
## 7. Память
|
||||||
|
|
||||||
|
### 7.1 Политика (в глобальный CLAUDE.md, три строки)
|
||||||
|
|
||||||
|
- Состояние задачи живёт в трекере (Gitea issue, Jira), не в памяти.
|
||||||
|
- В репозиторий направления (`<dir>/notes/`, память агента) пишутся уроки, решения владельца, рецепты. Коммит в той же ветке, что и работа.
|
||||||
|
- В локальную auto-memory — только ловушки этой машины и обвязки и индекс со ссылками на `notes/` направлений.
|
||||||
|
|
||||||
|
Чеклист закрытия, новый пункт 8: файл auto-memory задачи, если был, сворачивается в строку `registry-closed.md`
|
||||||
|
(issue, PR, follow-up, дата); уроки из него переносятся в `notes/` направления.
|
||||||
|
|
||||||
|
### 7.2 Индекс auto-memory после чистки
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Общее — ловушки машины, обвязка, фидбек владельца по процессу
|
||||||
|
## Разработка — ссылка на .claude/roles/dev.md и docs/dev/, активные уроки
|
||||||
|
## Администрирование — ссылка на admin/notes/, память jira-admin
|
||||||
|
## Аналитика — ссылка на analysis/notes/decisions.md
|
||||||
|
## QA — ссылка на qa/notes/, память qa-runner
|
||||||
|
## Закрытое — registry-closed.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Одноразовая чистка 47 файлов awp-ca
|
||||||
|
|
||||||
|
На копии памяти в песочнице; дифф показывается владельцу до замены; оригинал сохраняется как `memory.bak-<дата>/`.
|
||||||
|
|
||||||
|
| Куда | Что |
|
||||||
|
|---|---|
|
||||||
|
| `registry-closed.md` (auto-memory) | 30 файлов «ЗАКРЫТ»: одна строка на задачу |
|
||||||
|
| `admin/notes/jira.md` + память `jira-admin` | `jira-backlog-actualization`, трудозатраты из `slice-0-proposals-state`, рецепт Word из `user-guide-85-state` |
|
||||||
|
| `qa/notes/` + память `qa-runner` | `qa-local-stand`, `q1-2025-run`, `pdfjs-legacy-yandex`, рецепт скриншотов, `feedback-compare-with-mockup` (как правило роли qa) |
|
||||||
|
| `analysis/notes/decisions.md` | решения владельца по OQ из `oq-decisions-62`, `slice-1/2c/3a/3b/4-state`, `tech-debt-slice-1`, `osv-inn-mapping-rework`, `auto-confirm-64`, `statement-template-lines-state` |
|
||||||
|
| `<workspace>/.claude/roles/dev.md` или `docs/dev/` | `workspace-hooks-and-worktrees` (уроки), `nats-platform-fileworker`, `arm-web-repo`, `sync-web-script-state` |
|
||||||
|
| `claude-harness/roles/dev.md` | уроки без привязки к проекту (cwd и контроллер, пути в планах, trailer) |
|
||||||
|
| остаются в auto-memory | `feedback-no-harness-bypass`, `feedback-execution-mode-from-claude-md`, `harness-retro-and-roles`, `jetstream-check-later` |
|
||||||
|
|
||||||
|
`admin/jira-sync/next-session-bridge.md` после переноса ссылается на `admin/notes/jira.md` вместо памяти `jira-backlog-actualization`.
|
||||||
|
|
||||||
|
## 8. Изменения в обвязке (claude-harness)
|
||||||
|
|
||||||
|
| Файл | Изменение |
|
||||||
|
|---|---|
|
||||||
|
| `global/CLAUDE.md` | сжать до ≤ 3 КБ: язык, хуки, worktree, маркировка, политика памяти (7.1), правило выбора роли (раздел 4), «процесс роли — в /role» |
|
||||||
|
| `roles/{dev,admin,analyst,qa}.md` | новые, формат 3.1 |
|
||||||
|
| `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`; идемпотентно; печатает, что сделано |
|
||||||
|
| `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`; существующий режим не меняется |
|
||||||
|
| `skills/harness-doctor/scripts/doctor.sh` | проверки: симлинки ролей целы, `role-hint.sh` отвечает на тестовый JSON, размеры CLAUDE.md и индекса памяти |
|
||||||
|
| `docs/HARNESS.md`, `README.md`, `CHANGELOG.md` | раздел «Роли», версия 0.3.0 |
|
||||||
|
|
||||||
|
Хуки `guard-main.sh`, `require-plan.sh`, `harness-check.sh` не меняются (правки хуков — отдельная работа B1+B2).
|
||||||
|
|
||||||
|
## 9. Песочница, проверка, перенос
|
||||||
|
|
||||||
|
Песочница `~/projects/awp-ca-roles/`: симлинки на все репозитории awp-ca, копии `CLAUDE.md`, `.claude/`, копия памяти
|
||||||
|
(своя auto-memory из-за другого cwd). Репозитории `admin/`, `qa/`, `analysis/` создаются настоящими git-репозиториями
|
||||||
|
в `awp-ca/` (git init не трогает файлы; репозитории в Gitea заводятся по команде владельца). Ветка обвязки ставится
|
||||||
|
в песочницу не через `~/.claude/`, а через `CLAUDE_CONFIG_DIR` только если это понадобится; по умолчанию общий слой
|
||||||
|
проверяется копированием файлов ролей в `.claude/` песочницы, а `install.sh` выполняется один раз при переносе.
|
||||||
|
|
||||||
|
Проверки (каждая — задача плана с ожидаемым результатом):
|
||||||
|
|
||||||
|
1. Симлинки агентов и памяти: агент виден в списке Agent, запись в память попадает в `admin/.claude/agent-memory/`. Иначе — запасной вариант 6.5.
|
||||||
|
2. `role-hint.sh`: JSON с `session_title: "admin …"` даёт строку подсказки; без заголовка — пустой вывод; тест в `doctor.sh`.
|
||||||
|
3. Четыре сессии (`claude -n admin|qa|analyst|dev`): `/context` показывает файлы своей роли и не показывает чужие; стартовый контекст не-dev сессий меньше 56 К (скрипт `docs/retro/scripts/retro_blocks.py` по транскрипту песочницы).
|
||||||
|
4. `/role admin` в dev-сессии переключает правила; `briefing` возвращает выжимку.
|
||||||
|
5. Размеры: глобальный CLAUDE.md ≤ 3 КБ, проектный ≤ 5 КБ, индекс ≤ 3 КБ. Сверка «каждый абзац старых CLAUDE.md имеет адрес» — таблицей в PR.
|
||||||
|
6. Чистка памяти: дифф на копии, таблица 7.3 выполнена, `next-session-bridge.md` обновлён.
|
||||||
|
|
||||||
|
Перенос по команде владельца: дифф `~/.claude/CLAUDE.md` и памяти с состоянием на момент форка (11.10.2026) → `install.sh`
|
||||||
|
из ветки обвязки → копирование `.claude/` и `CLAUDE.md` из песочницы в `awp-ca/` → замена памяти с бэкапом → удаление песочницы.
|
||||||
|
|
||||||
|
## 10. Вне объёма
|
||||||
|
|
||||||
|
- Правки хуков guard-main/workspace (B1, B2, B10) — отдельная ветка после ролей.
|
||||||
|
- Мост jira-sync (`admin/jira-sync/next-session-bridge.md`) — brainstorming в роли admin после переноса.
|
||||||
|
- Skill `/harness-retro` (B12) — третий подпроект; сюда входит только размещение скриптов ретро в `docs/retro/scripts/`.
|
||||||
|
- Запуск сессий из репозитория направления (`.claude/role` по git-корню) — файл создаётся, механизм не реализуется.
|
||||||
|
|
||||||
|
## 11. Открытые вопросы
|
||||||
|
|
||||||
|
- Работает ли `memory: project` через симлинк каталога (проверка 1). Пока не проверено, в плане первой задачей.
|
||||||
|
- Имена репозиториев в Gitea: предложено `RSHB/arm-ka-admin`, `RSHB/arm-ka-qa`, `RSHB/arm-ka-analysis`; подтверждает владелец при создании.
|
||||||
|
- Алиасы `cc-<роль>`: в профиле shell владельца, обвязка только документирует.
|
||||||
|
|
||||||
|
## Decisions made without discussion
|
||||||
|
|
||||||
|
- 11.10.2026. Файлы ролей общего слоя ставятся в `~/.claude/roles/`, а не внутрь skill `role`. Обоснование: проектный слой
|
||||||
|
лежит в `.claude/roles/`, одинаковый путь в обоих слоях проще объяснить и проверить в doctor. Цена ошибки: перенос каталога.
|
||||||
|
- 11.10.2026. Агенты направлений объявляются в репозитории направления и подключаются симлинками, а не копируются в workspace.
|
||||||
|
Обоснование: один источник в git, видимость из корня. Цена ошибки: если симлинки не работают, копирование скриптом `link-roles.sh`.
|
||||||
|
- 11.10.2026. `briefing` запускается в фоне, а не блокирует `/role`. Обоснование: владелец может начать говорить сразу.
|
||||||
|
Цена ошибки: выжимка приходит после первого ответа; если это мешает, флаг `--wait`.
|
||||||
|
- 11.10.2026. Спека лежит в `docs/superpowers/specs/` репозитория обвязки, а не в `openspec/`: обвязка к самой себе не
|
||||||
|
подключена, OpenSpec в ней нет. Вопрос «подключать ли обвязку к обвязке» открыт, владельцу не задавался. Цена ошибки: перенос файла.
|
||||||
Loading…
x
Reference in New Issue
Block a user