diff --git a/docs/superpowers/specs/2026-05-19-brain-governance-design.md b/docs/superpowers/specs/2026-05-19-brain-governance-design.md index 77bb5d93..68c34c00 100644 --- a/docs/superpowers/specs/2026-05-19-brain-governance-design.md +++ b/docs/superpowers/specs/2026-05-19-brain-governance-design.md @@ -1,7 +1,8 @@ --- title: Brain governance design — router-only + observer scope B + 4 контролёра date: 2026-05-19 -status: design (approved by user — awaiting written-spec user review per brainstorming flow) +version: 1.1 +status: design (approved by user; v1.1 amendment 2026-05-19 — structured routing_decision + primary_rationale for factor analysis per user request before subagent execution) author: Дмитрий (заказчик) + Claude (Opus 4.7) via superpowers:brainstorming related: - docs/discovery/2026-05-18-system-audit-brain.md @@ -188,7 +189,7 @@ Observer **только пишет evidence, не вмешивается**. Ни **Action**: append одной JSONL-строки в `docs/observer/episodes-YYYY-MM.jsonl` (rotation помесячно — простое именование, никакой инфраструктуры). -**4 обязательных поля**: +**5 обязательных полей** (v1.1 — factor analysis amendment 2026-05-19): | Поле | Тип | Описание | |---|---|---| @@ -196,18 +197,66 @@ Observer **только пишет evidence, не вмешивается**. Ни | `timestamps` | `{started_at, ended_at}` ISO-8601 | Начало и конец сессии (для time-burn анализа) | | `path_type` | enum: `regulated` / `improvised` / `alternative` / `mixed` | Пошёл ли роутер по канонической связке L1–L12, импровизировал, ушёл альтернативой или смешанно | | `outcome` | enum: `success` / `partial` / `failure` / `aborted` | Итог сессии | +| `primary_rationale` | structured object (см. §5.2.1) | **Главное решение роутера на эпизод** — структурированные факторы выбора первичного узла. Дублирует первое `routing_decision` событие в `events[]` для быстрых агрегатов без чтения массива. | **Структурированные события** (опционально, массив `events[]`): | Тип | Описание | |---|---| +| `routing_decision` | **Структурированный факт выбора узла** — 7 полей (см. §5.2.1). Записывается один раз на каждое решение роутера в сессии (если цепочка из N узлов — N событий с возрастающим `step`). Это основа факторного анализа. | | `hook_fired` | Сработал хук (skill-marker, skill-check, state-guard, postcompact, verifier, economy-mode, security-guidance, ruflo-* — текущая 6+ компонентная архитектура) | -| `chain_divergence` | Роутер пошёл не по канонической связке, хотя совпадение триггеров было | -| `skill_invoked` | Инвокирован skill — записать `skill_id` + `reason` | +| `chain_divergence` | Роутер пошёл не по канонической связке, хотя совпадение триггеров было. Поля: `expected_chain` (L1–L12 ID), `chosen_nodes[]`, `reason_summary`. Дополняет (не подменяет) `routing_decision` на этом же шаге. | +| `skill_invoked` | Инвокирован skill — поля `skill_id` + `reason` (free-text). Для факторного анализа выбора skill'а — используется отдельный `routing_decision` события с тем же `step`. | | `error` | Возникла ошибка — записать `class` (например `quirk:72`) + `recovery_action` | | `confusion_marker` | Заказчик/Claude пометил место как «запутано» (вручную через TODO-метку в промпте) | | `time_burn` | Большой блок времени потрачен на одну операцию (порог: >5 минут на single tool-call) | +#### §5.2.1. Структура `routing_decision` и `primary_rationale` + +Оба объекта используют **одинаковую схему 7 полей** — `primary_rationale` это **копия первого `routing_decision`** в эпизоде, поднятая на уровень эпизода для дешёвой агрегации. + +| Поле | Тип | Обязательное | Описание | +|---|---|---|---| +| `step` | int | yes (для events) / нет в primary_rationale | Номер решения в эпизоде (1, 2, 3 ...). Для primary_rationale — всегда 1. | +| `node_chosen` | string (`#NN` или ``) | yes | Выбранный узел из Tooling Прил. Н §4.X. | +| `triggers_matched` | array of string | yes | Список триггеров из реестра, которые сработали (например `["discovery", "интервью", "JTBD"]`). Empty `[]` допустим если решение принято исключительно по hard-floor. | +| `candidates_considered` | array of `{node_id, dropped_because}` | yes | Узлы, которые роутер рассмотрел и отбросил. Каждый элемент содержит ID отброшенного узла и **строку причины** (ADR-ref / specificity / hard-floor / manual). Empty `[]` если альтернатив не было. | +| `boundaries_applied` | array of string | yes | ADR-ссылки или PSR_v1 R-ссылки, применённые для разруливания (например `["ADR-009", "PSR_v1 R15.3"]`). Empty `[]` если границ не применялось. | +| `hard_floor` | `{invoked: bool, rules: string[]}` | yes | Информация о hard-floor §12/§14/§15. Если применилось — `invoked: true` и в `rules` перечислены сработавшие правила (например `["Pravila §12"]`). | +| `task_classification` | string | yes | Категория задачи на момент решения (`discovery` / `brainstorm` / `writing-plan` / `TDD` / `debug` / `arch-decision` / `refactor` / `docs` / `sync` / `migration` / `commit` / `review` / `other`). | + +**Пример** (запись эпизода с цепочкой из 2 узлов): + +```json +{ + "task_id": "2026-05-19-brain-governance-plan", + "timestamps": {"started_at": "2026-05-19T04:00:00+03:00", "ended_at": "2026-05-19T04:35:00+03:00"}, + "path_type": "regulated", + "outcome": "success", + "primary_rationale": { + "step": 1, + "node_chosen": "superpowers:brainstorming", + "triggers_matched": ["brainstorm", "design"], + "candidates_considered": [ + {"node_id": "superpowers:writing-plans", "dropped_because": "specificity: brainstorm предшествует writing-plans"} + ], + "boundaries_applied": ["PSR_v1 R15.3"], + "hard_floor": {"invoked": true, "rules": ["Pravila §12"]}, + "task_classification": "brainstorm" + }, + "events": [ + {"kind": "routing_decision", "step": 1, "node_chosen": "superpowers:brainstorming", "triggers_matched": ["brainstorm", "design"], "candidates_considered": [...], "boundaries_applied": ["PSR_v1 R15.3"], "hard_floor": {"invoked": true, "rules": ["Pravila §12"]}, "task_classification": "brainstorm"}, + {"kind": "routing_decision", "step": 2, "node_chosen": "superpowers:writing-plans", "triggers_matched": ["writing-plans"], "candidates_considered": [], "boundaries_applied": [], "hard_floor": {"invoked": true, "rules": ["Pravila §12"]}, "task_classification": "writing-plan"}, + {"kind": "skill_invoked", "skill_id": "superpowers:writing-plans", "reason": "terminal skill brainstorming-flow"} + ] +} +``` + +**Граница**: `primary_rationale` НЕ дублирует все шаги цепочки — только **первое** решение. Полная цепочка читается через `events[].kind=="routing_decision"`. Это даёт два уровня агрегации: + +1. **Дешёвая верхнеуровневая** (читай только `primary_rationale` каждого эпизода): «какой узел чаще всего открывает сессию», «какие факторы доминируют в первом решении». +2. **Глубокая факторная** (читай все `routing_decision` событий): «какие пары факторов чаще всего разруливают конкретные конфликты», «какие ADR-границы выходят на первый план». + ### 5.3. Опциональные MD-заметки **Путь**: `docs/observer/notes/YYYY-MM-DD-.md`. Создаются **только если у сессии есть качественная история, которая не помещается в JSONL**: длинный narrative разбора, скриншот ошибки, цепочка из 5+ узлов с границами и т.д. @@ -239,6 +288,13 @@ Observer **только пишет evidence, не вмешивается**. Ни - Топ-связки L1–L12 (использованы) + новые связки (path_type=improvised, повторившиеся ≥2 раз). - Top `error` classes + recovery-патерны. - `chain_divergence` cases — где роутер ушёл с канонической связки. + - **Факторная матрица** (v1.1+ amendment): по каждому узле в `routing_decision` событиях — частоты по 5 осям факторов: + - `triggers_matched` → какие триггеры чаще всего ведут к этому узлу + - `candidates_considered.dropped_because` → какие альтернативы чаще всего отбрасываются и по какой причине + - `boundaries_applied` → какие ADR-границы / R-rules чаще всего разруливают + - `hard_floor.rules` → как часто узел вынуждается hard-floor §12/§14/§15 + - `task_classification` → в каких классах задач узел доминирует + Output — таблица «узел × фактор × частота» + cross-tab «фактор × фактор» для каждой пары факторов (например «ADR-009 ↔ triggers_matched=['discovery']» — 8 раз). 3. Возвращает **кандидатов на корректировку нормативки**: - Новая связка L13+ (если improvised повторился ≥2 раз с success). - Граница X ↔ Y нуждается в ADR-уточнении (если chain_divergence повторился).