From 0733ffd9c9f8d043132ec91b25a0c5311778f51d Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 11 Oct 2026 16:51:34 +0400 Subject: [PATCH 1/4] =?UTF-8?q?docs:=20=D1=80=D0=B5=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D1=81=D0=BF=D0=B5=D0=BA=D1=82=D0=B8=D0=B2=D0=B0=20=D0=BE=D0=B1?= =?UTF-8?q?=D0=B2=D1=8F=D0=B7=D0=BA=D0=B8=2011.10.2026=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=B8=D0=B3=D0=BE=D0=BD=D1=83=20awp-ca?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Находки RT-1…RT-20, бэклог B1…B12, решения владельцу; скрипты подсчёта по транскриптам (retro_stats.py, retro_blocks.py) как основа регулярного ретро. Co-Authored-By: Claude Fable 5.1 --- docs/retro/2026-10-11.md | 183 +++++++++++++++++++++++++++++ docs/retro/scripts/retro_blocks.py | 135 +++++++++++++++++++++ docs/retro/scripts/retro_stats.py | 159 +++++++++++++++++++++++++ 3 files changed, 477 insertions(+) create mode 100644 docs/retro/2026-10-11.md create mode 100644 docs/retro/scripts/retro_blocks.py create mode 100644 docs/retro/scripts/retro_stats.py diff --git a/docs/retro/2026-10-11.md b/docs/retro/2026-10-11.md new file mode 100644 index 0000000..6a9d133 --- /dev/null +++ b/docs/retro/2026-10-11.md @@ -0,0 +1,183 @@ +# Ретроспектива обвязки 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 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 ` или пути в команде; записи вне 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. Решения, которые нужны от владельца + +1. Архив OpenSpec: отдельный PR всегда (рекомендуется, так уже делается) или исключение в workspace-хуке. +2. Удаление серверных веток при закрытии: allow-правило `git push origin --delete` в проектных настройках или рецепт «из worktree». +3. codegraph: оставить опциональным (рекомендуется) или провести замер пользы на одном срезе. +4. Память: согласие на политику «состояние — в Gitea, в память — уроки и знания роли» и на одноразовую чистку 47 файлов. +5. Роли: состав агентов на первом шаге (предложено: `jira-admin`, `qa-runner`, `briefing`; аналитику — файл роли без агента). + +## 6. Что не удалось измерить + +- Время, потерянное на каждую ложную блокировку: транскрипт не содержит длительности исправления, только факт. +- Польза codegraph: нет ни одного замера «с ним / без него». +- Доля решений в `design.md`, которые владелец потом пересмотрел: нужен разбор архивов OpenSpec, не делался. +- Поведение памяти вне git (slug по cwd) не описано в документации Claude Code; проверено только наблюдением. diff --git a/docs/retro/scripts/retro_blocks.py b/docs/retro/scripts/retro_blocks.py new file mode 100644 index 0000000..21481a8 --- /dev/null +++ b/docs/retro/scripts/retro_blocks.py @@ -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}") diff --git a/docs/retro/scripts/retro_stats.py b/docs/retro/scripts/retro_stats.py new file mode 100644 index 0000000..b9989c1 --- /dev/null +++ b/docs/retro/scripts/retro_stats.py @@ -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}") From f8b77ed8e3e997ef5dc3204aa161dd5b27b56f14 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 11 Oct 2026 17:24:26 +0400 Subject: [PATCH 2/4] =?UTF-8?q?docs(retro):=20=D1=80=D0=B5=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B2=D0=BB=D0=B0=D0=B4=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D1=86=D0=B0=20=D0=BF=D0=BE=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D1=83=205?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- docs/retro/2026-10-11.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/retro/2026-10-11.md b/docs/retro/2026-10-11.md index 6a9d133..392db16 100644 --- a/docs/retro/2026-10-11.md +++ b/docs/retro/2026-10-11.md @@ -167,13 +167,19 @@ requesting-code-review 0 раз. Закрытие и ревью идут по ч Порядок, который предлагается: B1+B2 (убирают главный источник трения и каскад RT-7), затем B5+B6 (роли и память — одно изменение, так как индекс памяти перестраивается под роли), затем B3/B4/B10 (мелкие правила), B12, остальное по мере надобности. -## 5. Решения, которые нужны от владельца +## 5. Решения владельца (11.10.2026) -1. Архив OpenSpec: отдельный PR всегда (рекомендуется, так уже делается) или исключение в workspace-хуке. -2. Удаление серверных веток при закрытии: allow-правило `git push origin --delete` в проектных настройках или рецепт «из worktree». -3. codegraph: оставить опциональным (рекомендуется) или провести замер пользы на одном срезе. -4. Память: согласие на политику «состояние — в Gitea, в память — уроки и знания роли» и на одноразовую чистку 47 файлов. -5. Роли: состав агентов на первом шаге (предложено: `jira-admin`, `qa-runner`, `briefing`; аналитику — файл роли без агента). +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. Что не удалось измерить From 58ffbef9570ebf260402bb623b704301506c695e Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 11 Oct 2026 20:44:11 +0400 Subject: [PATCH 3/4] =?UTF-8?q?docs:=20=D1=81=D0=BF=D0=B5=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=80=D0=BE=D0=BB=D0=B5=D0=B9=20=D0=B8=20=D0=BF=D0=B0=D0=BC?= =?UTF-8?q?=D1=8F=D1=82=D0=B8=20(B5+B6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- .../2026-10-11-roles-and-memory-design.md | 287 ++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 docs/superpowers/specs/2026-10-11-roles-and-memory-design.md 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 new file mode 100644 index 0000000..a50cd78 --- /dev/null +++ b/docs/superpowers/specs/2026-10-11-roles-and-memory-design.md @@ -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). Рядом лежат репозитории направлений. + +``` +/ workspace, не git, cwd всех сессий + CLAUDE.md общий минимум: что за проект, таблица репозиториев с ролью-владельцем, источники истины, «роли → /role» + .claude/ + settings.json как сейчас (хуки workspace, разрешения) + roles/.md проектный слой ролей (dev — файл; admin/analyst/qa — симлинки в репозитории направлений) + agents/.md симлинки на агентов из репозиториев направлений + agent-memory// симлинки на память агентов в репозиториях направлений + / роль 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, черновики решений) в витрину не попадает или попадает после обработки. + +Репозиторий направления содержит: +``` +/ + CLAUDE.md что это за репозиторий, 5–10 строк (загружается лениво при чтении файлов из него) + .claude/role имя роли по умолчанию (одна строка) — для будущего режима запуска из репозитория + .claude/roles/.md проектный слой роли + .claude/agents/.md агенты роли + .claude/agent-memory//MEMORY.md память агентов (коммитится) + notes/ уроки, решения владельца, рецепты роли (markdown, коммитится) + <рабочие каталоги> +``` + +Симлинки из `/.claude/` в репозитории направлений создаёт скрипт `link-roles.sh` (раздел 8), проверяет `doctor.sh`. +Claude Code находит агентов и skills только в `/.claude/`, поэтому без ссылок конфигурация направлений из корня не видна. + +## 3. Слои ролей + +Два слоя, оба в git: + +| Слой | Где в источнике | Куда ставится | Что содержит | +|---|---|---|---| +| Общий | `claude-harness/roles/.md` | `~/.claude/roles/.md` (install.sh) | процесс роли, не зависящий от проекта | +| Проектный | `/.claude/roles/.md`, для dev — `/.claude/roles/dev.md` | симлинк в `/.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 ` в любой момент. Новая роль заменяет предыдущую, первая строка файла роли это говорит. + +## 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/.md` и `/.claude/roles/.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` пишет в `/.claude/agent-memory//`. Чтобы память оказалась в git репозитория направления, +`/.claude/agent-memory/` — симлинк в `/.claude/agent-memory/`. Проверяется первой (раздел 9). +Запасной вариант: поле `memory` не используется, промпт агента велит читать `/notes/agent-memory.md` при старте и дописывать в конце. + +## 7. Память + +### 7.1 Политика (в глобальный CLAUDE.md, три строки) + +- Состояние задачи живёт в трекере (Gitea issue, Jira), не в памяти. +- В репозиторий направления (`/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` | +| `/.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 в ней нет. Вопрос «подключать ли обвязку к обвязке» открыт, владельцу не задавался. Цена ошибки: перенос файла. From ccf6a0b5e64d26b1e1ee5b36b9a63a3532b6ade1 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 11 Oct 2026 21:14:19 +0400 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20CLAUDE.md=20=D0=BF=D0=BE=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4=D1=83=20=D1=80=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D1=8F,=20code-paths=20=D0=B4=D0=BB=D1=8F=20r?= =?UTF-8?q?equire-plan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Разделы «Что это», «Стек», «Архитектура», «Команды», «Тесты», «Текущая работа» заполнены по hooks/, скриптам установки и skill harness-doctor. Шаблонные {{...}} убраны. .claude/code-paths: hooks/ и skills/ вместо src/. Refs #1 Co-Authored-By: Claude Fable 5.1 --- .claude/code-paths | 2 ++ CLAUDE.md | 39 +++++++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 .claude/code-paths diff --git a/.claude/code-paths b/.claude/code-paths new file mode 100644 index 0000000..3c11b1a --- /dev/null +++ b/.claude/code-paths @@ -0,0 +1,2 @@ +hooks/ +skills/ diff --git a/CLAUDE.md b/CLAUDE.md index 79e1404..4a3443f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,32 +1,43 @@ # 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/` (или {{ваш путь}}) — чистая логика без зависимостей от фреймворка/платформы. Покрывается тестами полностью. -- `src/{{adapter}}/` — тонкая обёртка над платформой. Тестируется руками или интеграционно. -- {{Ключевые интерфейсы/порты между ними, если есть.}} +- `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`. +- `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}}` — весь набор тестов. Зелёным считается только весь набор. -- `{{npm run build / ...}}` — сборка. -- `{{npm run dev / ...}}` — локальный запуск. +- `bash install.sh --check` — проверка окружения: бинарники, плагины, конфликты. +- `bash install.sh` — установка глобальной части, идемпотентно. +- `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/.*` + эталон `.expected.*`. Внешние сервисы в тестах мокаются. -- {{Где лежит тестовое окружение (vault, БД, песочница).}} +- Отдельного набора тестов нет. Единственная автоматическая проверка — самотест хуков в `doctor.sh`: фиктивные вызовы Write/Bash на main и на ветке, `src/` с планом и без, `.bounded`. Прогоняется по установленным копиям, поэтому перед проверкой изменений в `hooks/` сначала `bash install.sh`. +- Ручная проверка хука: на 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 - Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать. - Когда brainstorming классифицировал задачу как architectural, структуру change создать через `openspec new change ` (или `/opsx:propose `), а содержимое proposal.md, specs/ и design.md написать по итогам brainstorming. tasks.md из OpenSpec не заполнять — задачи живут в плане superpowers. - `/opsx:apply` не использовать: исполнение идёт через writing-plans и SDD/executing-plans. - `openspec archive ` — после merge PR, как часть чеклиста закрытия. -- Хук require-plan защищает любой каталог `src/` на любой глубине. Если код лежит иначе — перечислить каталоги (префиксы от корня, по одному в строке) в `.claude/code-paths`. +- Хук require-plan защищает любой каталог `src/` на любой глубине. Если код лежит иначе — перечислить каталоги (префиксы от корня, по одному в строке) в `.claude/code-paths`. В этом репозитории код — `hooks/` и `skills/`, они перечислены в `.claude/code-paths`.