f4602b4aa5
aggregation-template.md gets two new sections (Hook script breakdown, Recommended-node candidates) + paragraph in Missed Activations. factor-analysis spec gets a v3 amendment cross-ref to the 2026-05-23 spec. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
383 lines
35 KiB
Markdown
383 lines
35 KiB
Markdown
# Observer factor-analysis extension — design
|
||
|
||
**Версия:** 1.2
|
||
**Дата:** 2026-05-20
|
||
**Статус:** accepted — фаза 1 реализована (`f7f37fb`); фаза 1.1 (см. §11) реализована (commits `7f379bd..dc6d2dd`); **фаза 1.2 «instrument expansion» (см. §12) — в реализации**
|
||
**Связано:** ADR-011 (brain governance), spec `2026-05-19-brain-governance-design.md`, Pravila §16, PSR_v1 R16, `docs/observer/`, `tools/observer-stop-hook.mjs`, `tools/observer-transcript-parser.mjs`
|
||
|
||
---
|
||
|
||
## 1. Контекст
|
||
|
||
Регламент «мозга» (ADR-011) включает наблюдателя — Stop-хук пишет эпизоды в `docs/observer/episodes-YYYY-MM.jsonl`, `/brain-retro` раз в спринт строит факторный анализ. После follow-up'а 19.05.2026 (`99c7bac`) наблюдатель наполняется реальными данными из транскрипта.
|
||
|
||
Но при разборе с заказчиком вскрылось: данных для **полноценного факторного анализа** не хватает. Факторный анализ объясняет разброс **исходов** через **факторы**. Текущий эпизод:
|
||
|
||
- **Исход фиктивен** — `outcome` хардкод `"success"`. Объяснять нечего, если зависимая переменная константа. Настоящий исход часто известен только в следующем ходе (заказчик говорит «не то»).
|
||
- **Нет провенанса решения** — эпизод пишет `node_chosen`, но не «кто решил». Заказчик ставит задачи и навязывает методы; Claude исполняет. Если заказчик навязал неверный узел → эпизод покажет `node_chosen: X, outcome: error`, и анализ обвинит роутинг, хотя причина — навязанная инструкция.
|
||
- **Нет факторов среды** — уровень экономии, модель, пост-компакция, параллельные сессии, длина сессии радикально влияют на исход, но не записываются → анализ искажается смешиванием факторов.
|
||
- **Эпизод ≠ задача** — одна задача = N ходов = N эпизодов; rework невидим как rework.
|
||
- **Нет нормировки размера** — «узел X коррелирует с ошибками» может значить «узел X применяют на больших задачах».
|
||
- **Процессные события не наполняются** — `hook_fired` / `chain_divergence` / `confusion_marker` / `time_burn` заявлены в spec v1.1, не генерируются; прерывания и ретраи не фиксируются.
|
||
- **Наблюдатель может тихо пропускать** — Stop-хук при ошибке делает молчаливый `exit 0`; пропуск невидим.
|
||
|
||
**Цель:** расширить эпизод и наблюдателя так, чтобы факторный анализ стал возможен; сделать дисциплину ведения наблюдателя принудительной (которую Claude не может обойти) и самоконтролируемой (пропуски наблюдателя видимы).
|
||
|
||
**Граница (вне scope):** независимый LLM-agent-судья («какой узел был правильным»), `confusion_marker` как настоящее суждение, real-time in-session флаги friction. Это отдельная фаза 2 — требует agent + $/сессию + дополнительной нормативки.
|
||
|
||
---
|
||
|
||
## 2. Архитектура — 4 слоя
|
||
|
||
```
|
||
Слой 1 Схема эпизода v2 — что хранится
|
||
Слой 2 Захват (parser + 1 тег) — детерминированный парсинг + routing-тег
|
||
Слой 3 Принуждение (двустороннее)— 3a routing-gate, 3b самодисциплина наблюдателя
|
||
Слой 4 Анализ (/brain-retro) — исход, группировка, цепочки, факторная матрица
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Слой 1 — схема эпизода v2
|
||
|
||
Эпизод приобретает `schema_version: 2`. Новые/изменённые поля поверх v1.1 (5 mandatory + 7-полевой `primary_rationale` + `events`):
|
||
|
||
| Поле | Тип | Источник |
|
||
|---|---|---|
|
||
| `schema_version` | `2` | константа |
|
||
| `decision_provenance` | `{ kind, claude_would_have_chosen }` | routing-тег (Слой 2) |
|
||
| `environment` | `{ economy_level, model, post_compaction, session_turn, parallel_session }` | детерминированный парсинг |
|
||
| `task_size` | `{ tool_calls, files_touched }` | детерминированный парсинг |
|
||
| `task_ref` | string | sessionId + сегмент задачи |
|
||
| `outcome` | enum | провизорно при записи, уточняется Слоем 4 |
|
||
| `events[]` | расширенные виды | детерминированный парсинг + тег |
|
||
|
||
- `decision_provenance.kind` ∈ `autonomous` | `user_directed_method`. `autonomous` — дефолт (тега нет). `user_directed_method` — тег есть. `claude_would_have_chosen` — узел-контрфактуал, не-null только при `user_directed_method`. (Случай «заказчик задал задачу» — это норма, конфлируется с `autonomous`: для факторного анализа важно только «навязан ли *метод*».)
|
||
- `environment.economy_level` ∈ `0` | `5` | `100` | `null` — из маркера `=== ECONOMY MODE: N% ===` в транскрипте.
|
||
- `environment.model` — из метаданных assistant-сообщений; `null` если недоступно.
|
||
- `environment.post_compaction` — `true` если перед текущим ходом в транскрипте был маркер компакции.
|
||
- `environment.session_turn` — порядковый номер реального user-prompt в сессии.
|
||
- `environment.parallel_session` — best-effort: `true` при маркерах коллизии (foreign git index, упоминание параллельной сессии); иначе `false`. Честно: детерминированно ненадёжно.
|
||
- `task_size.tool_calls` — число `tool_use` в ходе; `files_touched` — число уникальных путей в Read/Edit/Write.
|
||
- `outcome` ∈ `success` | `partial` | `failure` | `rework` | `blocked` | `unknown`. При записи — `unknown` (честный дефолт вместо фиктивного `success`). Уточняется `/brain-retro`.
|
||
- `events[].kind` ∈ `skill_invoked` | `tool_summary` | `error` | `hook_fired` | `interrupt` | `retry` | `time_burn` | `parse_gap`. (`confusion_marker`, `chain_divergence` — зарезервированы под фазу 2 / agent.)
|
||
|
||
**Маркер-эпизод `observer_error`** — особая минимальная строка при внутреннем отказе хука: `{ schema_version: 2, observer_error: true, error_message, timestamps, task_id }`. Пишется вместо тихого пропуска.
|
||
|
||
7-полевой `primary_rationale` из v1.1 сохраняется без изменений (`triggers_matched` / `candidates_considered` / `boundaries_applied` остаются `[]` — детерминированно невосстановимы).
|
||
|
||
---
|
||
|
||
## 4. Слой 2 — захват
|
||
|
||
### 4.1. Расширение `tools/observer-transcript-parser.mjs`
|
||
|
||
Все факторы среды, размер, процессные события — детерминированно из транскрипта:
|
||
|
||
- `economy_level` — regex `=== ECONOMY MODE: (\d+)% ===` по транскрипту хода.
|
||
- `model` — из `message.model` assistant-сообщений (если поле есть); иначе `null`.
|
||
- `post_compaction` — поиск compaction-маркера (`isCompactSummary` / запись типа компакции) до начала хода.
|
||
- `session_turn` — счётчик `isRealUserPrompt` до текущего.
|
||
- `task_size` — подсчёт `tool_use` + уникальных файловых путей.
|
||
- `events`: `hook_fired` — из записей `attachment.type === "hook_success"`/`hook_error`; `interrupt` — из маркера `[Request interrupted by user]`; `retry` — эвристика «тот же инструмент по той же цели после `error`»; `time_burn` — производное от длительности хода > порога; `parse_gap` — если доля непарсибельных строк выше порога.
|
||
|
||
### 4.2. Routing-тег (подход C)
|
||
|
||
Когда заказчик навязал метод, Claude печатает в ответе одну строку:
|
||
|
||
```
|
||
<!-- routing: provenance=user_directed_method node=<выбранный> counterfactual=<что выбрал бы сам> -->
|
||
```
|
||
|
||
Парсер извлекает тег regex'ом, заполняет `decision_provenance`. Тег эмитится **только** при method-direction; на автономных ходах тега нет, провенанс по умолчанию `autonomous`. Тег — HTML-комментарий: виден заказчику (прозрачность — это сама по себе обратная связь), не шумит в рендере.
|
||
|
||
---
|
||
|
||
## 5. Слой 3 — принуждение (двустороннее)
|
||
|
||
### 5.1. 3a — routing-gate (сторона Claude)
|
||
|
||
Логика в `tools/observer-stop-hook.mjs`:
|
||
|
||
1. **Всегда сначала** — записать эпизод (как Слой 1/2), exit-0-safe.
|
||
2. **Затем** — детектор «навязан ли метод» (детерминированный, консервативный): regex по последнему user-prompt — содержит ли имя узла/скила из реестра в директивном контексте (`запусти X`, `используй X`, `через X`, `/команда`, имя скила). Список известных узлов — из Tooling Прил. Н + список скилов.
|
||
3. Если метод навязан **и** в ответе нет `<!-- routing: -->` тега → хук выводит `{ "decision": "block", "reason": "<инструкция: добавь routing-тег>" }`.
|
||
4. `decision: block` заставляет Claude продолжить ход; Claude добавляет тег; следующий Stop — хук перепроверяет, проходит.
|
||
|
||
**Необойдёмость:** Stop-хук исполняется Claude Code детерминированно в конце каждого хода. Claude не может его пропустить, рационализировать или обойти через Bash. Завершить ход без тега (когда метод навязан и детектор сработал) — нельзя.
|
||
|
||
**Честный предел:** не «обход Claude», а **покрытие детектора**. False negative (метод навязан тонко, regex не поймал) → тега нет → провенанс ложно `autonomous`. False positive (имя узла упомянуто вскользь) → лишний блок. Детектор делается консервативно-широким — false negative минимизируется ценой редких false positive.
|
||
|
||
### 5.2. 3b — самодисциплина наблюдателя
|
||
|
||
- **Видимость отказа.** `observer-stop-hook.mjs` оборачивается так: при любой внутренней ошибке — записать маркер `observer_error` и `exit 0`. Принцип «не блокировать Stop» сохранён, пропуск становится видимым в данных.
|
||
- **Видимость потери данных.** Парсер при доле непарсибельных строк выше порога пишет событие `parse_gap`.
|
||
- **Контроль покрытия.** Расширение C3 observer-of-observer (или новый контролёр): сверяет «есть git-активность за период, но 0 эпизодов» → флаг в STATUS.md. Тихая смерть наблюдателя становится заметной.
|
||
- **Целостность регистрации.** Проверка (стиль C1 L1-watcher): Stop-хук прописан в `.claude/settings.json`; `.git/hooks/post-commit` установлен. Удалили/сломали → флаг.
|
||
|
||
### 5.3. HK1 — анализ коллизии хуков (ADR-010)
|
||
|
||
Stop-событие сейчас несёт: user-level economy-verifier (agent) + project-level `observer-stop-hook` (command). routing-gate встраивается **внутрь существующего** `observer-stop-hook` — нового Stop-entry не добавляется (остаётся 2). Оба способны вернуть `decision: block` — конфликта нет: Claude Code прогоняет все Stop-хуки, любой `block` ведёт к продолжению хода. observer-gate детерминированный и дешёвый. UserPromptSubmit-напоминание (осознанность) — отдельный лёгкий компонент, не Stop. HK1: коллизии нет.
|
||
|
||
---
|
||
|
||
## 6. Слой 4 — анализ (`/brain-retro`)
|
||
|
||
`/brain-retro` остаётся read-only агрегатором (не мутирует JSONL). Расширения:
|
||
|
||
- **Вывод настоящего исхода.** Для каждого эпизода: `interrupt`-событие → `partial`; больше `error`-событий, чем `retry` (невосстановленный сбой) → `blocked`; иначе — по первому user-prompt **следующего** эпизода той же сессии: коррекция (`не то` / `не так` / `переделай` / `откати` / `сломал` / `не работает` / `revert` / … — расширенный набор) → исход прошлого = `rework`; одобрение/новая задача (`ок` / `спасибо` / `дальше` / `готово`) → `success`; нет следующего → `unknown`. `failure` детерминированно невосстановим (суждение «работа неверна И не исправлена») — отложен в фазу 2 (agent-судья). Уточнённый исход — в retro-ноте (JSONL не трогается, append-only).
|
||
- **Группировка «эпизоды → задача».** `task_ref` по sessionId; сегментация — новая задача начинается с top-level user-prompt после `success` или после паузы.
|
||
- **Каузальные цепочки.** Детерминированная корреляция: эпизоды, делящие `files_touched`; `error` в N → исправление того же файла в N+1. Surface как «кандидаты цепочек».
|
||
- **Факторная матрица.** Строки — факторы (`decision_provenance.kind`, `economy_level`, `model`, `post_compaction`, бакет `session_turn`, `parallel_session`, бакет `task_size`, `node_chosen`, `task_classification` — 9 осей); столбцы — распределение `outcome`. Пример вывода: «`user_directed_method`: 40% rework против `autonomous` 12%» — прямой ответ на вопрос заказчика «моя ли вина».
|
||
|
||
---
|
||
|
||
## 7. Нормативные изменения
|
||
|
||
- **ADR-011** — поправка: добавить решение «observer factor-analysis extension v2» (схема v2, двустороннее принуждение).
|
||
- **Pravila §16** — §16.2 расширить: схема эпизода v2; **новое правило routing-тег-дисциплины** (заказчик навязал метод → Claude обязан эмитить routing-тег; Stop-gate enforced); **правило самодисциплины наблюдателя** (каждый Stop фиксируется; внутренний отказ → `observer_error`, не замалчивается; покрытие и регистрация проверяются контролёром).
|
||
- **PSR_v1 R16** — синхронизировать (brain evidence loop — расширенные события).
|
||
- spec brain-governance — cross-ref на этот документ.
|
||
|
||
Все правки нормативки — через pre-flight sync (Pravila §15.2, 8-файловый список); CLAUDE.md — через `claude-md-management`.
|
||
|
||
---
|
||
|
||
## 8. Тест-план
|
||
|
||
- **Unit (Vitest, `tools/*.test.mjs`):** расширения парсера — извлечение `economy_level` / `model` / `post_compaction` / `session_turn` / `task_size`; события `hook_fired` / `interrupt` / `retry` / `time_burn` / `parse_gap`; парсинг routing-тега; путь `observer_error`.
|
||
- **Unit:** детектор routing-gate (regex по известным узлам), логика `decision: block`.
|
||
- **Unit:** `/brain-retro` — вывод исхода из следующего эпизода, группировка, факторная матрица.
|
||
- **Integration:** CLI smoke — хук против реального транскрипта → v2-эпизод наполнен; симуляция method-directed хода без тега → хук выводит `decision: block`.
|
||
- Полный tools Vitest GREEN; lefthook pre-commit зелёный.
|
||
|
||
---
|
||
|
||
## 9. Открытые риски
|
||
|
||
- **R1 — покрытие детектора routing-gate.** Regex по именам узлов: false negative (тонкая директива) / false positive (упоминание вскользь). Митигируется консервативной широтой; остаточный риск принят.
|
||
- **R2 — `model` / `parallel_session` детерминированно ненадёжны.** Записываются best-effort, `null`/`false` при недоступности. Не блокирует факторный анализ по остальным факторам.
|
||
- **R3 — вывод исхода эвристичен.** Next-prompt sentiment — приближение; систематический сдвиг (заказчик не всегда реагирует словом коррекции). Принято для фазы 1; точный исход — фаза 2 (agent).
|
||
- **R4 — два Stop-блокатора.** economy-agent + observer-gate. Сосуществуют (HK1 §5.3), но при одновременном блоке Claude получает два reason'а — нормально, оба отрабатываются.
|
||
- **R5 — `transcript_path` в Stop-payload.** Унаследовано из Item 1 (`99c7bac`): если Claude Code не передаёт путь — хук деградирует, эпизод беднеет. Проверяется post-deploy.
|
||
- **R6 — reconstructive bias контрфактуала.** `claude_would_have_chosen` пишет тот же Claude, что исполнял задачу. Сдвиг признан заказчиком осознанно; независимая проверка — фаза 2 (agent).
|
||
|
||
---
|
||
|
||
## 10. Что НЕ делаем (фаза 2, отдельный spec)
|
||
|
||
Независимый agent-судья «какой узел был правильным»; `confusion_marker` / `chain_divergence` как настоящие суждения; real-time in-session флаги friction; автоматические правки нормативки по результатам факторного анализа (остаётся: `/brain-retro` только предлагает кандидатов).
|
||
|
||
---
|
||
|
||
## 11. Phase 1.1 amendment — `user_chose_from_options` (2026-05-19)
|
||
|
||
### 11.1. Контекст
|
||
|
||
Live-триггер 19.05.2026 при выполнении фазы 1: при предложении пользователю A/B/C-выбора (numbered/lettered options в моём ответе) — пользователь короткими промптами выбирает один (`1 экономия 0%`, `в делаем`, `делай 2`). Routing-gate классифицирует такой ход как `user_directed_method`, но это **не** навязанный извне метод — выбор сделан из choice-space, который я сам сформулировал. Контрфактуал тоже отличается: в `user_directed_method` контрфактуал — «что бы я выбрал автономно без директивы», а здесь — «какую из *моих* опций я бы рекомендовал». Для факторного анализа эти случаи смешиваются → искажение матрицы.
|
||
|
||
Третий kind `decision_provenance` устраняет смешение.
|
||
|
||
### 11.2. Схема (расширение §3)
|
||
|
||
`decision_provenance.kind` расширяется до 3 значений: `autonomous` | `user_directed_method` | `user_chose_from_options`.
|
||
|
||
Для `user_chose_from_options` `decision_provenance` принимает форму:
|
||
|
||
```json
|
||
{
|
||
"kind": "user_chose_from_options",
|
||
"node": "<выбранный пользователем>",
|
||
"options_offered": ["<node-1>", "<node-2>", "<node-3>"],
|
||
"claude_would_have_chosen": "<моя рекомендация — первый из options_offered по конвенции>"
|
||
}
|
||
```
|
||
|
||
Поле `claude_would_have_chosen` — то же поле, что и в `user_directed_method` (semantic: «что выбрал бы Claude автономно / самостоятельно»). Для `user_chose_from_options` его значение — «моя рекомендация из предложенных опций». Tag-literal `counterfactual=` в routing-теге сохраняется (mapping `counterfactual=X` → `claude_would_have_chosen: X`).
|
||
|
||
Поле `node` — для `user_directed_method` не записывалось (только `claude_would_have_chosen` контрфактуал); для `user_chose_from_options` фиксирует фактический выбор пользователя. `options_offered` — массив, появляется только при `kind: user_chose_from_options`.
|
||
|
||
### 11.3. Детектор (новый `tools/observer-choice-detector.mjs`)
|
||
|
||
Pure-функция `detectChoiceProvenance(promptText, lastAssistantContent)` → `{ kind, options, chosen, counterfactual } | null`.
|
||
|
||
**Шаг 1 — извлечь опции из последнего assistant-сообщения** (broad detection per user-approved scope):
|
||
|
||
- `AskUserQuestion` tool_use → опции из `questions[].options[].label`.
|
||
- Numbered list: `^\d+[.\)]\s+...` (минимум 2 подряд).
|
||
- Lettered list: `^[A-Za-zА-Яа-я][.\)]\s+...` (минимум 2 подряд, латиница ИЛИ кириллица).
|
||
- Bullet list: `^[-*]\s+...` (минимум 2 подряд).
|
||
|
||
Если найдено `<2` опций — return `null`, fallback на текущую логику.
|
||
|
||
**Шаг 2 — найти ссылку в user-prompt:**
|
||
|
||
- Position-based: prompt начинается с числа/буквы (опционально после verb-префикса: `делай|выбираю|беру|хочу|вариант|option`), сопровождаемой пробелом/запятой/точкой/EOL. Поддержка кириллицы + латиницы (keyboard-layout collision: `B` ↔ `В`).
|
||
- Substring match: prompt содержит первые 2-4 слова из label одной из опций.
|
||
|
||
Если ни один сигнал не найден — return `null`.
|
||
|
||
**Шаг 3 — собрать результат:**
|
||
|
||
- `chosen` — label/node соответствующей выбранной опции.
|
||
- `claude_would_have_chosen` — первая опция из списка (моя рекомендация по конвенции).
|
||
- `options_offered` — все извлечённые опции (как массив строк).
|
||
|
||
### 11.4. Routing-gate (расширение §5.1)
|
||
|
||
Существующая логика (`routingGateDecision` в `tools/observer-stop-hook.mjs`) расширяется:
|
||
|
||
1. **Сначала** — `detectChoiceProvenance` (новый). Если вернул не-null → `decision_provenance.kind = user_chose_from_options`, **gate НЕ блокирует** (collaborative-choice; tag желателен, но не обязательный — данные восстановимы детерминированно из транскрипта).
|
||
2. **Иначе** — текущий `detectMethodDirected` (как сейчас). Если directed без тега → `decision: block`.
|
||
3. Routing-тег `<!-- routing: provenance=user_chose_from_options node=X counterfactual=Y -->` Claude может эмитить добровольно (для прозрачности); парсер его примет, но детектор работает и без него.
|
||
|
||
### 11.5. Парсер (расширение §4)
|
||
|
||
`tools/observer-transcript-parser.mjs` — добавляется вызов `detectChoiceProvenance` ДО `detectMethodDirected`. Извлечение `lastAssistantContent` — последняя assistant-message строка перед текущим user-turn в JSONL транскрипта.
|
||
|
||
Если `detectChoiceProvenance` срабатывает — `decision_provenance` собирается из его результата; routing-тег (если есть) проверяется на consistency (warn, не fail). Если нет — старая логика (тег или autonomous).
|
||
|
||
### 11.6. /brain-retro (расширение §6)
|
||
|
||
Факторная матрица: ось `decision_provenance.kind` теперь имеет 3 значения. Counterfactual-анализ: для `user_chose_from_options` отдельная строка «пользователь выбрал не первую (мою рекомендованную) опцию — N% случаев, rework-доля Y%».
|
||
|
||
### 11.7. Тест-план
|
||
|
||
- Unit (`tools/observer-choice-detector.test.mjs`): извлечение опций из AskUserQuestion / numbered / lettered / bullets; position-based references (число/буква, кириллица/латиница, verb-префиксы); substring label match; negative cases (нет опций / нет ссылки → null).
|
||
- Unit (расширение `observer-stop-hook.test.mjs`): `routingGateDecision` с `user_chose_from_options` — НЕ блокирует.
|
||
- Unit (расширение `observer-transcript-parser.test.mjs`): сборка эпизода с `kind: user_chose_from_options`, миграция чтения `claude_would_have_chosen` → `counterfactual`.
|
||
- Integration: CLI smoke — реальный транскрипт с AskUserQuestion-ходом → эпизод корректно классифицирован.
|
||
|
||
### 11.8. Out of scope (phase 1.1)
|
||
|
||
- Детектор «опции в потоке текста» (например, `«вариант A — короткий», «вариант B — длинный»` в inline-прозе без списка) — out, эвристика низкой надёжности.
|
||
- Распознавание «пользователь дополнил мою опцию» (выбрал B, но изменил параметр) — out, требует семантического понимания.
|
||
- Refine routing-gate detector (исключение `<system-reminder>` блоков из scope) — отдельная задача, см. memory `project_brain_governance_design.md`.
|
||
|
||
---
|
||
|
||
## 12. Phase 1.2 amendment — instrument expansion (2026-05-20)
|
||
|
||
Реализованы 18 рекомендаций из brain-retro 2026-05-20 (план
|
||
`docs/superpowers/plans/2026-05-20-observer-instrument-expansion.md` v1.1
|
||
REVISION). Worktree: `.claude/worktrees/observer-v2-expansion`, ветка
|
||
`feat/observer-v2-expansion`.
|
||
|
||
### 12.1. Схема v2 (расширения)
|
||
|
||
- **Опциональное поле `task_cost`** в episode (#2): захват `message.usage`
|
||
agregat по Σ всех assistant-сообщений turn'а. Поля: `input_tokens`,
|
||
`output_tokens`, `cache_read_input_tokens`, `cache_creation_input_tokens`,
|
||
`web_search_requests`, `web_fetch_requests`, `iterations` (B1-verified
|
||
shape, бонус `server_tool_use` + `iterations` поверх 4 базовых).
|
||
Backward-compat: `V2_FIELDS` validator не расширен — старые v2-эпизоды
|
||
без `task_cost` остаются валидны.
|
||
- **`outcome` enum** дополнен `soft_success` (#16): next-prompt `neutral`
|
||
интерпретируется как silent success ('no objection'). Слабее explicit
|
||
approval, отдельно labelled.
|
||
- **Новые `events[]` kinds:**
|
||
- `ask_user_question` (#4) — per AskUserQuestion question с `answer_kind`
|
||
∈ `option|custom|no_answer`. Сигнал quality предлагаемых options.
|
||
- `subagent_invoked` (#12) — per Agent tool_use с `subagent_type` /
|
||
`model` / `description` (80 chars).
|
||
- `error` enriched (#7) — теперь несёт `tool` (имя инструмента,
|
||
атрибуция через id-map) + `summary` (first 80 chars причины);
|
||
раньше bare `message: 'tool_result reported is_error'`.
|
||
|
||
### 12.2. Парсер — heuristic capture (Слой 2)
|
||
|
||
- **`primary_rationale` arrays больше не пусты** (#6): три pure-функции
|
||
`extractTriggers` / `extractCandidates` / `extractBoundaries` сканируют
|
||
assistant.text на маркеры (Pravila §N, ADR-N, PSR_v1 RX,
|
||
routing-off-phase LN, hard-floor/rule + numbered/bulleted lists ≥2).
|
||
Conservative-broad — false positives accepted; agent-judge остаётся
|
||
out-of-scope (фаза 2 §10).
|
||
- **Opt-in reasoning-tag** (#11): `<!-- reasoning: triggers="..."
|
||
candidates="..." boundaries="..." -->` HTML-комментарий в assistant.text;
|
||
semicolon-separated значения merged в heuristic arrays через Set-dedupe.
|
||
- **`<system-reminder>` strip в `promptText`** (#8): UserPromptSubmit
|
||
hook injections больше не загрязняют `classifyTask` /
|
||
`classifyPromptSignal` / routing detection.
|
||
- **`classifyTask` vocabulary +7 classes** (#1): `analysis` / `memory-sync` /
|
||
`regulatory-bump` / `release` / `cleanup` / `monitoring` / `planning`.
|
||
Closes «59% other» observation.
|
||
- **`classifyPromptSignal` vocabulary +** (#9): `correction` +'не совсем',
|
||
'другое', 'wrong direction'; `approval` +'класс', 'well done', 'nice';
|
||
`new_task` prefix +'теперь', 'далее', 'next', 'now'. Bug fix: JS `\b`
|
||
не работает с Cyrillic (Cyrillic ≠ word char) — substring match для
|
||
русских correction markers, lookahead для prefix-based new_task.
|
||
- **`parallel_session` +OR pre-flight git fetch** (#13 PIVOT): additive
|
||
signal — Bash command `git fetch && git log HEAD..origin/...` (Pravila
|
||
§15.2 pre-flight) = strong signal для parallel sessions. Не overwrite
|
||
F1 narrow-to-tool_result детектор; OR-clause.
|
||
|
||
### 12.3. Анализатор (Слой 4)
|
||
|
||
- **`session_segment_turn` axis rename** (#14): factor matrix ось
|
||
`session_turn` → `session_segment_turn` (turns-since-last-compaction —
|
||
что фактически и было per parser). Семантика не меняется, только имя
|
||
для ясности.
|
||
- **`inferOutcome` neutral → soft_success** (#16) — см. §12.1.
|
||
|
||
### 12.4. STATUS.md generator
|
||
|
||
- **Real PII counter** (#3 SIMPLIFIED): `sanitizeWithCount` в pii-filter
|
||
- persistent `docs/observer/.pii-counters.json` (per-month aggregation,
|
||
bumped on each Stop-hook write) + `countPiiMatches()` reads counter.
|
||
STATUS перестаёт врать `0 PII matches`. PII patterns themselves NOT
|
||
changed (F7 of parallel session already extended).
|
||
- **`Last /brain-retro: N day(s) ago`** (#10): метрика читается из
|
||
`docs/observer/.read-counter.json` `last_read_at`.
|
||
- **`Legacy v1 episodes (not in factor analysis)`** (#18): count of
|
||
pre-2026-05-19T08:06 episodes без schema_version=2 — visible.
|
||
|
||
### 12.5. /brain-retro skill
|
||
|
||
- **Step 4 explicit `recordRead`** (#15): replaced abstract 'bump'
|
||
instruction with `node tools/observer-of-observer.mjs record`. Atomic
|
||
read-modify-write через fs.
|
||
- **Step 8a STATUS auto-refresh** (#19): после save retro-note запускается
|
||
status-md-generator — STATUS.md становится immediately current
|
||
(`Last /brain-retro: 0 day(s) ago`, fresh episode count).
|
||
|
||
### 12.6. Ad-hoc tooling
|
||
|
||
- **`tools/glob-latency-investigator.mjs`** (#17): one-off script для
|
||
расследования Glob p50=12.7s аномалии из исходного ретро. Smoke-test
|
||
на session 553717ec: top-5 slowest все `docs/adr/**` @ 20265ms — Glob
|
||
recursive по ADR-каталогу = apparent culprit. Не production code path.
|
||
|
||
### 12.7. Infrastructure
|
||
|
||
- **`npm run test:tools`** (B3-1, applied вне scope плана): canonical
|
||
entry point для `tools/observer-*.test.mjs` runner. Корневой
|
||
`package.json` script.
|
||
|
||
### 12.8. Not done
|
||
|
||
- Phase 2 (out-of-scope, см. §10 + spec brain-governance §10):
|
||
agent-judge для true vs nominal use, `confusion_marker` /
|
||
`chain_divergence` как real judgments, real-time in-session friction
|
||
flags, automatic Pravila/Tooling edits from factor matrix.
|
||
- Task 5 (hot-file two-tier) — **SKIPPED** per REVISION v1.1: F4 of
|
||
parallel session already added full exclude of `memory/*.md`; warm-tier
|
||
для adjacent имел diminishing return.
|
||
|
||
### 12.9. Регрессия
|
||
|
||
После всех 18 task'ов: **NNN/NNN GREEN** в `npm run test:tools`
|
||
(baseline 232 → final NNN — заполнить в финальном commit Task 21).
|
||
|
||
## Amendments
|
||
|
||
### 2026-05-23 — schema v3 (parser skill/hook expand)
|
||
|
||
Spec extension: forward-only bump `schema_version` 2 → 3. Two new fields:
|
||
|
||
- `events[].hook_fired.scripts: { script_name: count, ... }` — reverse-lookup `.claude/settings.json` → имена хук-скриптов. Old `counts` (matcher level) preserved для backward-compat.
|
||
- `primary_rationale.recommended_node: "#NN" | null` — для direct-эпизодов derived из `classification-map` + dormancy. null при использованном skill / отсутствии рекомендации / всех dormant.
|
||
|
||
Analyzer фильтр `schema_version === 2` → `>= 2`; `missed-activations` фильтр `!== 2` → `< 2`. FACTOR_FNS +recommended_node_for_direct.
|
||
|
||
Полный spec: `docs/superpowers/specs/2026-05-23-observer-parser-skill-hook-expand-design.md`.
|