Compare commits
275 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d1ad4e8559 | |||
| 440fd71bbf | |||
| d59b9177d4 | |||
| cc77a5817e | |||
| c8639f2fd1 | |||
| 5fa17070a9 | |||
| 068b03d946 | |||
| 4e54331ef4 | |||
| e229cf0706 | |||
| caa41e6cac | |||
| 9414699ad9 | |||
| 67a46df978 | |||
| 2c41971d88 | |||
| b92afad96c | |||
| 2bd7efbebd | |||
| 9ce6041be4 | |||
| 93e516e680 | |||
| 0f20c944ca | |||
| 1088599023 | |||
| 1857773f89 | |||
| 1478f56b51 | |||
| 4ea4806f71 | |||
| 487a6f1db9 | |||
| e8958155c4 | |||
| b48fd86152 | |||
| 50550f311e | |||
| 36545b636f | |||
| 0af6610b77 | |||
| 0c6f084f0e | |||
| cfca7ecfaa | |||
| 843097c0d6 | |||
| 44c194bc0f | |||
| 8ec7969551 | |||
| 681c2b8abc | |||
| 4c1ad03705 | |||
| a630053714 | |||
| 647879adc6 | |||
| dd63f950e2 | |||
| ff8f14d8d2 | |||
| a7f3ebd971 | |||
| d5feca2672 | |||
| f139ad5320 | |||
| 8228703320 | |||
| 2ba34dc0e2 | |||
| 45905432f8 | |||
| 987feb2e40 | |||
| 7e5db3ef91 | |||
| ad6fd73ec9 | |||
| fa07472dd0 | |||
| 5782ede3eb | |||
| 2b0c28e59f | |||
| 16e0c1db09 | |||
| 51f718ea07 | |||
| 1a3ccc2178 | |||
| 1651fdfb50 | |||
| 4a0c3a98d9 | |||
| 93fcb5e141 | |||
| 0e995970f9 | |||
| 752512d3cd | |||
| 1000abc7fc | |||
| 0eef670e54 | |||
| 2e6a6c0a7a | |||
| 6c8918dff4 | |||
| eb4b38f481 | |||
| 2900554d5f | |||
| a85d7f9d5f | |||
| 553ddea464 | |||
| cf9f803d36 | |||
| 5ae0b1359f | |||
| 84f75abb14 | |||
| cfc4e0a853 | |||
| f801593987 | |||
| 0774a41f13 | |||
| 62d3352d3e | |||
| acf1d90209 | |||
| 64fb063a22 | |||
| 65b3c57515 | |||
| 3dbd8a5cd9 | |||
| caadc92be0 | |||
| 083095174c | |||
| d48365de5e | |||
| 078e829b38 | |||
| 9dc6bb55fd | |||
| 52cea07fee | |||
| f1c245f8f6 | |||
| d1a767867a | |||
| 4d257a1c44 | |||
| 1aacb2fe66 | |||
| e2f5ba0406 | |||
| 85ffb25e2d | |||
| 7c3a495e5c | |||
| ee9e312300 | |||
| 1bc8b30563 | |||
| 84de110fee | |||
| 942f9bb8a1 | |||
| 6c53fe998a | |||
| 3ef853695b | |||
| 2d2f3fc591 | |||
| f9a4b10d1f | |||
| 9763025621 | |||
| f3f3a70aa5 | |||
| e791d33c78 | |||
| 2f39286ddf | |||
| a630c994db | |||
| a29fa9caa9 | |||
| 02e3ff7379 | |||
| 3bd3caee40 | |||
| 1d8457e671 | |||
| 521a64ed05 | |||
| 5320de8371 | |||
| 3fad5e0401 | |||
| 576e9c6079 | |||
| db3224992c | |||
| 317f8cb6fb | |||
| b6fe66e34c | |||
| 73fa2d61ff | |||
| 9148c8c6bd | |||
| f71f1abef8 | |||
| 8a9e21c280 | |||
| 91a5acc4bf | |||
| dc30c5daee | |||
| bbd66c9b61 | |||
| 06ad12cd94 | |||
| 8153f96aff | |||
| 8e56df3842 | |||
| 473fd21136 | |||
| 6556f5ca0a | |||
| 183733835f | |||
| ea83a714e4 | |||
| 7277584eaf | |||
| 89e9ca159e | |||
| a5b62fbfad | |||
| 91af38ca11 | |||
| 3847c863ca | |||
| 1c251d2592 | |||
| 15af97adae | |||
| 475d381e0c | |||
| eac1c45bbb | |||
| 8ba9a21c9c | |||
| b98b18850a | |||
| 9e7ca7ef19 | |||
| a0b98d227f | |||
| 8bfef418c2 | |||
| d221ba499d | |||
| 3ea34d42dd | |||
| d0e0bd18c9 | |||
| 28b92d90e6 | |||
| a5b99eaa7e | |||
| 6e2d485f44 | |||
| b83cfc65b9 | |||
| 4a10932cb6 | |||
| d2109ac1dc | |||
| a9e8585767 | |||
| 6b44e7afd8 | |||
| 87d84a2e3f | |||
| 4f7b1fab09 | |||
| 20c85ede09 | |||
| 3fb7a0517f | |||
| 719648cd08 | |||
| 47de8447dd | |||
| d02932b053 | |||
| 32ffab621d | |||
| 849723bc04 | |||
| 7dd3a16531 | |||
| 9c3205ad7c | |||
| 2925d063fb | |||
| 5d350b6997 | |||
| 7b2a3d32aa | |||
| a88a80ed0b | |||
| 4cfca9cc55 | |||
| ecca24f451 | |||
| 7e61b9c367 | |||
| acc9045016 | |||
| 32b688e091 | |||
| 0875cd24ab | |||
| f0f7128cc3 | |||
| 7110d0fa37 | |||
| ea0dee23e2 | |||
| cab7ffc3cf | |||
| 54f0974ba5 | |||
| 24ce7b39f1 | |||
| 8a754e9a19 | |||
| 5d96e89d5c | |||
| b6d06ede87 | |||
| 22b84fbb2e | |||
| 23f5936d0d | |||
| 15829b8eb7 | |||
| 89bc392ef4 | |||
| 89f4d1a011 | |||
| 930630955e | |||
| 5dc6785940 | |||
| 622ac4df28 | |||
| 69e20099db | |||
| e1a6f26c06 | |||
| 2b8ad760be | |||
| 1881501b81 | |||
| 0220ca5802 | |||
| 119ff1f230 | |||
| c47155ec91 | |||
| b39431d661 | |||
| fd61515d20 | |||
| 02ff19d08b | |||
| b6e59353c1 | |||
| d925c61651 | |||
| 079adfd184 | |||
| afb01219cb | |||
| 06a37cb486 | |||
| a9208a9393 | |||
| c2d6e6e130 | |||
| 0ee7874c88 | |||
| 923e8ff825 | |||
| 6dbe4374de | |||
| fe5cf99fc9 | |||
| 2ef5504710 | |||
| 7947569f38 | |||
| 522531bbd2 | |||
| 3addf4353b | |||
| 4963c1187f | |||
| 133b29df58 | |||
| 200b5b2da7 | |||
| e991027793 | |||
| 2907e3f25f | |||
| 7b578cd391 | |||
| 35a569d370 | |||
| 1d676a5616 | |||
| b94f7d244c | |||
| 92ba55bc0f | |||
| 58f3a65800 | |||
| eb3f4c4ed1 | |||
| 003bd3d86b | |||
| 14230814b0 | |||
| 0f198b6e33 | |||
| a27a848d7c | |||
| dcf772bac5 | |||
| 4cb17fc4d5 | |||
| ed89028b1d | |||
| 28b129ed9c | |||
| 3a80bdde5c | |||
| 80ebec9e82 | |||
| 8df8d05612 | |||
| 699da97dc2 | |||
| 750f406cbd | |||
| 53db0ee2b3 | |||
| a905abd1b4 | |||
| f82cefaead | |||
| 77d2b9be16 | |||
| 8e342be430 | |||
| ec4733f77a | |||
| cfbfd9c6b4 | |||
| 8d9ca65cf3 | |||
| 599dca15ec | |||
| 8a790e6f14 | |||
| de530a130d | |||
| 1cc5431b23 | |||
| b83cea2e73 | |||
| 7af68d62c8 | |||
| 56da7faba9 | |||
| 3af57e180a | |||
| 325c1f4984 | |||
| e3da14a7fc | |||
| 9bd45ce510 | |||
| d7dc03271a | |||
| 9309b3590e | |||
| d612ef2e90 | |||
| 9a090bfdac | |||
| 6a8c8494a6 | |||
| 940070685a | |||
| 3b2ffecab4 | |||
| 97a0490be1 | |||
| 9c82cb0218 | |||
| 9689a6e5b8 | |||
| 88ae0ac348 | |||
| c55e14b626 | |||
| 2f59541d4b | |||
| 618519c7e8 |
@@ -31,9 +31,14 @@ paths:
|
||||
keyset (cursor) — O(1) глубины; offset-based — backward-совместимость.
|
||||
При count_only=true возвращает только {"total": N} без строк.
|
||||
parameters:
|
||||
- name: status_in[]
|
||||
- name: status_in
|
||||
in: query
|
||||
description: Фильтр по статусам (можно несколько)
|
||||
description: >
|
||||
Фильтр по статусам (можно несколько). На проводе сериализуется
|
||||
Laravel array-binding: status_in[]=NEW&status_in[]=WON. Имя параметра
|
||||
в спецификации — без скобок: ключи свойств MCP-инструмента обязаны
|
||||
матчить ^[a-zA-Z0-9_.-]{1,64}$ (скобки запрещены, иначе Anthropic
|
||||
tools-схема падает с 400).
|
||||
required: false
|
||||
schema:
|
||||
type: array
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
# Каталог данных «мозга» Лидерры
|
||||
|
||||
Полный перечень всего, что новый «мозг» (наблюдатель + защиты router-gate v4) **способен фиксировать**: журнал эпизодов, числовые параметры/счётчики и все оси для факторного анализа.
|
||||
|
||||
**Статус проверки:** разделы A–E сверены по исходному коду `tools/` (31.05.2026). Раздел F сверен по коду хуков. Все цитаты — `file:line` от корня репозитория.
|
||||
|
||||
---
|
||||
|
||||
## A. Эпизод журнала — `docs/observer/episodes-YYYY-MM.jsonl` (схема v4.4, schema_minor 4)
|
||||
|
||||
Один эпизод = один цикл «промпт заказчика → ответ Claude». Append-only, по строке на эпизод. ПДн вырезаются до записи (`observer-pii-filter.mjs`). Сборка — `observer-transcript-parser.mjs:888` (`parseTranscript`).
|
||||
|
||||
### A.1. Идентификация и время
|
||||
| Поле | Тип / значения | Смысл |
|
||||
|---|---|---|
|
||||
| `schema_version` | `4` | версия схемы |
|
||||
| `schema_minor` | `3` | подверсия |
|
||||
| `task_id` / `task_ref` | string (sessionId) | привязка к сессии |
|
||||
| `timestamps.started_at` / `ended_at` | ISO | начало/конец хода |
|
||||
|
||||
### A.2. Кто и что выбрал
|
||||
| Поле | Значения | Смысл |
|
||||
|---|---|---|
|
||||
| `path_type` | `regulated` / `improvised` | был ли вызван навык superpowers |
|
||||
| `decision_provenance.kind` | `autonomous` / `user_directed_method` / `user_chose_from_options` | кто выбрал маршрут — Claude сам / навязан заказчиком / заказчик выбрал из предложенного |
|
||||
| `decision_provenance.claude_would_have_chosen` | string / null | контрфактуал — что выбрал бы Claude сам |
|
||||
|
||||
### A.3. Исход
|
||||
| Поле | Значения | Смысл |
|
||||
|---|---|---|
|
||||
| `outcome` | при записи `unknown` | исход (выводится позже, см. C) |
|
||||
| `outcome_reviewed` | null → метка | исход по ревью /brain-retro |
|
||||
| `outcome_reviewed_source` | null / source | кто проставил ревью |
|
||||
|
||||
### A.4. Сигнал заказчика
|
||||
| Поле | Значения | Смысл |
|
||||
|---|---|---|
|
||||
| `prompt_signal` | `correction` / `approval` / `new_task` / `neutral` | тон следующего/текущего промпта |
|
||||
| `prompt_embedding_base64` | null → вектор | смысловой вектор первого промпта (дозаполняется асинхронно) |
|
||||
|
||||
### A.5. Обстановка (`environment`)
|
||||
| Поле | Значения | Смысл |
|
||||
|---|---|---|
|
||||
| `economy_level` | 0 / 5 / 100 / null | режим экономии |
|
||||
| `model` | имя модели | модель контроллера |
|
||||
| `post_compaction` | bool | был ли сжат контекст до хода |
|
||||
| `session_turn` | int | номер хода после последнего сжатия |
|
||||
| `parallel_session` | bool | признак второй активной сессии |
|
||||
| `classifier_model` | имя / null | модель LLM-классификатора |
|
||||
|
||||
### A.6. Размер (`task_size`)
|
||||
`tool_calls` (всего вызовов инструментов) · `files_touched` (уникальных файлов) · `files[]` (список путей). — `observer-transcript-parser.mjs:423`.
|
||||
|
||||
### A.7. Стоимость и токены (`task_cost`)
|
||||
`observer-transcript-parser.mjs:472`. Базовые: `input_tokens` · `output_tokens` · `cache_read_input_tokens` · `cache_creation_input_tokens` · `web_search_requests` · `web_fetch_requests` · `iterations` (детектор extended-thinking).
|
||||
Слой LLM-агентов (дозаполняется): `classifier_input_tokens` · `classifier_output_tokens` · `self_assessment_input_tokens` · `self_assessment_output_tokens` · `reviewer_input_tokens` · `reviewer_output_tokens` · `reviewer_subagent_usd` · `reviewer_direct_fallback_usd` · `judge_spend_usd` (✅ NEW — `judge_calls × JUDGE_PER_CALL_USD`).
|
||||
|
||||
### A.8. Мета (`task_meta`)
|
||||
`prompt_length_chars` · `mcp_servers_used[]` · `file_type_distribution` по корзинам `src / test / config / spec / norm / data / other` (`classifyFilePath` — `observer-transcript-parser.mjs:358`).
|
||||
|
||||
### A.9. Классификатор (`classifier_output`) + `degraded_mode`
|
||||
`observer-state-enricher.mjs:52`. `task_type` · `recommended_node` · `recommended_chain` · `recommended_chain_id` · `no_skill_found` · `source` (llm/regex/prefilter/cache) · `reasoning` (≤600 симв.) · `confidence` · `latency_ms` · `retry_count_internal` · `llm_error` · `alternatives_considered[]` (топ-3). Отдельно `degraded_mode` (bool, LLM→regex fallback).
|
||||
|
||||
### A.10. Рассуждение маршрута (`primary_rationale`)
|
||||
`step` · `node_chosen` · `chain_ref` · `triggers_matched[]` · `candidates_considered[]` · `boundaries_applied[]` · `hard_floor{invoked, rules[]}` · `task_classification` · `recommended_node` · `recommended_chain` · `chain_progress` · `chain_completed`.
|
||||
|
||||
**`task_classification` — 12 значений** (`observer-transcript-parser.mjs:208`): `memory-sync`, `regulatory-bump`, `planning`, `release`, `refactor`, `bugfix`, `feature`, `docs`, `analysis`, `cleanup`, `monitoring`, `question`, `other`.
|
||||
|
||||
### A.11. События (`events[]`) — 11 видов
|
||||
`skill_invoked` (skill) · `tool_summary` (counts) · `error` (tool, summary) · `hook_fired` (counts, scripts, errors) · `interrupt` · `retry` · `time_burn` (ход > 15 мин) · `parse_gap` (> 10% битых строк) · `unrecovered_error` (ход кончился на ошибке) · `ask_user_question` (question_count, answer_kind: `option`/`custom`/`no_answer`) · `subagent_invoked` (subagent_type, model, description).
|
||||
|
||||
### A.12. Сигналы защит router-gate v4 (`v4_signals`) — ✅ NEW (schema_minor 4)
|
||||
Поднимаются в эпизод ридером `observer-v4-signals.mjs` по окну хода `[started_at, ended_at]`: `rationalization_flag_count` (число пойманных самооправданий в окне) · `judge_verdict` (`YES`/`NO`/`block`/`null` — последний вердикт судьи в окне) · `safe_baseline_action` (`allow`/`soft_flag`/`hard_block`/`null` — худшее действие safe-baseline в окне) · `judge_calls` (кумулятивно за сессию из бюджета судьи).
|
||||
|
||||
---
|
||||
|
||||
## B. Факторные оси — `FACTOR_FNS` (`brain-retro-analyzer.mjs:326`) — 27 осей + `chain_ref`
|
||||
|
||||
Каждая ось раскладывает эпизоды по корзинам и строит матрицу «значение фактора × распределение исходов» (`buildFactorMatrix`, `brain-retro-analyzer.mjs:384`).
|
||||
|
||||
**Pass 0 (из ядра эпизода):** `decision_provenance` · `economy_level` · `model` · `post_compaction` · `session_segment_turn` · `parallel_session` · `task_size` · `node_chosen` · `task_classification` · `recommended_node_for_direct`.
|
||||
|
||||
**Pass 1 (дешёвые оси из v4):** `prompt_signal` · `classifier_source` · `degraded_mode` · `path_type` · `retry_count` · `error_count` · `hard_floor_invoked` · `iterations_bucket`.
|
||||
|
||||
**Pass 2 (метрики классификатора):** `latency_bucket` (fast<500 / medium<2000 / slow<10000 / very_slow) · `error_type`.
|
||||
|
||||
**Pass 3 (динамика):** `prompt_length_bucket` (short<100 / medium<1000 / long<2500 / huge) · `time_of_day_bucket` (night/morning/afternoon/evening, UTC) · `day_of_week` · `inter_prompt_gap_bucket` (<1m / 1-10m / 10-60m / 60m+) · `mcp_server_used` (any/none) · `file_type_main` · `skill_invocations_bucket` (0/1/2+) · `subagent_spawns_bucket` (0/1/2+).
|
||||
|
||||
**Pass 4 (семантика):** `similar_past_outcome_majority` (исход большинства соседей по смысловому вектору; `no_neighbors` если вектора нет).
|
||||
|
||||
**Pass 5 (✅ NEW — сигналы router-gate v4):** `rationalization_flag_count` (0/1/2+) · `judge_verdict` (YES/NO/block/null) · `safe_baseline_action` (allow/soft_flag/hard_block/null) · `judge_calls_bucket` (0 / 1-9 / 10+).
|
||||
|
||||
**Отдельно:** `chain_ref` — мульти-значный (эпизод считается по разу на каждую цепочку).
|
||||
|
||||
---
|
||||
|
||||
## C. Вывод исхода — `inferOutcome` (`brain-retro-analyzer.mjs:95`) — 5 меток
|
||||
- `blocked` — ход кончился на неисправленной ошибке (`unrecovered_error`).
|
||||
- `rework` — следующий эпизод имеет `prompt_signal = correction`.
|
||||
- `success` — следующий = `approval` или `new_task`.
|
||||
- `soft_success` — следующий = `neutral` (тихий успех).
|
||||
- `unknown` — следующего эпизода ещё нет.
|
||||
|
||||
---
|
||||
|
||||
## D. Аналитические срезы (Cuts) `/brain-retro` (`brain-retro-analyzer.mjs`)
|
||||
| Срез | Функция | Что агрегирует |
|
||||
|---|---|---|
|
||||
| Факторная матрица | `buildFactorMatrix` | 27 осей × распределение исходов |
|
||||
| Cut 8 — Class × canon coverage | `buildClassCanonCoverage:448` | по классу задачи: count, канон-узлы, как часто роутер рекомендовал, что взял Claude, попало ли в канон, rework |
|
||||
| Cut 9 — Router vs Opus | `buildRouterVsOpus:498` | расхождение роутера и Opus-ревьюера (3 секции) |
|
||||
| Cut 10 — Chain-ignore breakdown | `buildChainIgnoreBreakdown:559` | % игнора цепочек + rework-rate по длине (1 / 2 / 3+) |
|
||||
| Cut 11 — Chain-hook effectiveness | `analyzeChainHookEffectiveness:36` + `buildChainHookEffectiveness:59` | исходы срабатывания chain-хука по ledger |
|
||||
| Router-gate hook effectiveness | `buildRouterGateHookEffectiveness:617` | эффективность router-gate |
|
||||
| Self-fabrication signals | `buildSelfFabricationSignals:643` | признаки фейковых результатов |
|
||||
|
||||
---
|
||||
|
||||
## E. Числовые счётчики и деньги (вне эпизода)
|
||||
|
||||
### E.1. `~/.claude/runtime/cost-daily.json` (per-date)
|
||||
`cost-aggregator.mjs:13`. Поля: `classifier_usd` · `self_assessment_usd` · `reviewer_subagent_usd` · `reviewer_direct_fallback_usd` · `self_retrospect_usd` · `total_usd` · `episode_count`.
|
||||
|
||||
### E.2. `PRICING` (`cost-pricing.mjs`)
|
||||
Sonnet — $3/Mtok вход, $15/Mtok выход. Opus — $15/Mtok вход, $75/Mtok выход.
|
||||
|
||||
### E.3. `~/.claude/runtime/hook-outcomes.jsonl`
|
||||
6 корзин исхода хука (`classifyOutcome`): `blocked` / `passed-with-skill` / `passed-inline-override` / `passed-global-override` / `passed-short-chain` / `passed-no-mutating`. Строка: rule, outcome, sessionId, timestamp.
|
||||
|
||||
### E.4. `~/.claude/runtime/override-usage.jsonl`
|
||||
Лог override-фраз. Порог 5/день на фразу (6-я блокируется `enforce-override-limit`). Байпас — фраза «лимит снят».
|
||||
|
||||
### E.5. Контролёр C5 (`observer-coverage-checker.mjs`) — warn-only
|
||||
`coverage` (хук зарегистрирован, но 0 эпизодов за месяц) · `registration` (Stop-хук + post-commit на месте) · `missed.totalMissed` (промахи активации узлов, `missed-activations.mjs`).
|
||||
|
||||
### E.6. Три счётчика в `docs/observer/`
|
||||
`.pii-counters.json` (сколько ПДн вырезано) · `.read-counter.json` (когда читали файлы наблюдателя; алерт через 54 недели тишины) · `.self-retrospect-counter.json` (`episodes_since_last`, порог 50 → предложить саморазбор).
|
||||
|
||||
---
|
||||
|
||||
## F. Сигналы новых защит router-gate v4
|
||||
|
||||
> **Обновление 31.05.2026:** F.1–F.3 ✅ **заведены в эпизод** (`v4_signals`, см. A.12) и в факторный анализ (Pass 5, раздел B) через ридер `observer-v4-signals.mjs`. F.4/F.5 — пока только на диске.
|
||||
|
||||
### F.1. Rationalization-audit (`enforce-rationalization-audit.mjs`)
|
||||
`~/.claude/runtime/rationalization-flags-<session>.jsonl`. Строка: `{kind, evidence}`. Виды (`kind`): `rationalization-phrase` (фраза-самооправдание) · `prod-edit-without-test` (правка прод-кода без теста в том же ходе) · `weak-commit-message` (commit с сообщением < 12 симв.). Блокирует на 3-м флаге за сессию (`decide:112`). Перед сопоставлением снимает цитаты/код (`stripQuotedContext:51`).
|
||||
|
||||
### F.2. Safe-baseline metering (`enforce-safe-baseline-metering.mjs`)
|
||||
`~/.claude/runtime/safe-baseline-ledger-<sess>.json` (per-task счётчики вызовов safe-инструментов Read/Grep/Glob/LS/TodoWrite/AskUser + `skill_match_within_task` + `lastKeywords`) и `safe-baseline-flags-<sess>.jsonl` (`{ts, tool, reason}`). Действия: `allow` / `soft_flag` / `hard_block` (мутирующий инструмент за порогом без навыка). Escape — любой Skill / EnterPlanMode.
|
||||
|
||||
### F.3. LLM-судья Layer 4 (`enforce-llm-judge-per-tool.mjs` + `-response-scan.mjs`)
|
||||
Вердикт по каждому мутирующему инструменту: `YES → allow`, `NO`/сомнение → `block`. Spend гейтится `resolveJudgeConfig` (флаг `ROUTER_LLM_JUDGE_ENABLED` И ключ) + per-session бюджет `JUDGE_SESSION_BUDGET` (инкремент только на реальный вызов). Исключения из проверки (scope-fix, не понижение дисциплины): `isReadonlyBashEvent` (readonly git/cat/grep/ls) и `isTestRunnerBashEvent` (vitest/pest/phpunit/artisan test/composer test/npm test без цепочки).
|
||||
|
||||
### F.4. Router-state writer (`router-prehook.mjs:156`)
|
||||
`~/.claude/runtime/router-state-<session>.json`: `classification` (вывод классификатора — task_type/recommended_node/recommended_chain/source/confidence/reasoning/…) · `chainProgress[]` · `chainCompleted` · `task_cost` (токены классификатора). Это источник, из которого `observer-state-enricher` обогащает эпизод (A.9–A.10).
|
||||
|
||||
### F.5. Прочие runtime-сигналы
|
||||
`expected-branch-<session>` (защита от угона ветки, `enforce-branch-switch`) · `verify-pass-<session>` (свежесть регрессии перед push, `enforce-verify-before-push`) · `askuser-decisions-<session>.jsonl` (одобрения git-операций) · `session-lock-<workspaceHash>.json` (замок параллельной сессии, `enforce-parallel-session-lock`) · `router-gate-mode.json` и набор `*-mode.json` (рубильники слоёв).
|
||||
|
||||
---
|
||||
|
||||
## Кандидаты на расширение факторного анализа
|
||||
|
||||
✅ **Реализовано 31.05.2026** (план `docs/superpowers/plans/2026-05-31-brain-factor-analysis-f-candidates.md`): следующие 4 оси заведены в эпизод (`v4_signals`) и в `FACTOR_FNS` (Pass 5):
|
||||
- `rationalization_flag_count` (F.1) — число самооправданий за ход/сессию.
|
||||
- `safe_baseline_action` (F.2) — allow / soft_flag / hard_block.
|
||||
- `judge_verdict` (F.3) — YES / block / disabled (когда Layer 4 активен).
|
||||
- `judge_spend_usd` (F.3) — расход судьи (добавить компонентом в `task_cost` и в `cost-daily.json`).
|
||||
@@ -0,0 +1,18 @@
|
||||
# Shadow-replay отчёт
|
||||
|
||||
## М5 пол — GREEN
|
||||
events: 954 | over-block: 0 | real-catch: 5 | allow: 949 | miss: 0
|
||||
|
||||
## М6 снимок — GREEN
|
||||
events: 951 | over-block: 0 | real-catch: 2 | allow: 949 | miss: 0
|
||||
|
||||
## М2 стена — GREEN
|
||||
events: 5 | over-block: 0 | real-catch: 2 | allow: 3 | miss: 0
|
||||
|
||||
## М4 судья — GREEN
|
||||
events: 5 | over-block: 0 | real-catch: 2 | allow: 3 | miss: 0
|
||||
|
||||
## М3 роутер — расхождение (реальные эпизоды)
|
||||
total: 884 | followed: 2 | diverged: 88 | no-rec: 794
|
||||
|
||||
> М1 журнал: вне холостого прогона (runtime read-protected); целостность — unit-тест (пин 5782ede3).
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "21st-magic",
|
||||
"kind": "external",
|
||||
"needs": ["намерение UI-шаблона (компонент / лейаут / форма)"],
|
||||
"produces": ["стартовый UI-шаблон (LLM-сгенерированный)"],
|
||||
"constraints": ["генератор шаблонов через PSR_v1 R14.4 pipeline", "Pa11y проверка обязательна после генерации", "стек-фильтр R6.0"],
|
||||
"preview-form": "mockup",
|
||||
"defaults": ["после генерации — обязательный Pa11y"],
|
||||
"key-decisions": ["какой шаблон; адаптация под Vue+Vuetify"],
|
||||
"acceptance-criteria": ["шаблон в стеке Vue+Vuetify, Pa11y 0 нарушений"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "adr-kit",
|
||||
"kind": "external",
|
||||
"needs": ["архитектурное решение для фиксации/проверки"],
|
||||
"produces": ["ADR в docs/adr/ + enforcement через adr-judge"],
|
||||
"constraints": ["ADR + adr-judge lefthook job 9 (без LLM)", "НЕ диаграммы (mermaid)", "НЕ справочник паттернов (architecture-patterns)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["adr-judge декларативно, без --llm"],
|
||||
"key-decisions": ["что фиксировать как ADR; статус решения"],
|
||||
"acceptance-criteria": ["решение зафиксировано ADR, adr-judge проходит"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "architecture-patterns",
|
||||
"kind": "external",
|
||||
"needs": ["вопрос выбора архитектурного паттерна"],
|
||||
"produces": ["описание паттернов + критерии выбора (Clean/Hexagonal/DDD/CQRS/...)"],
|
||||
"constraints": ["справочник паттернов (knowledge)", "НЕ фиксация решения (adr-kit)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["давать критерии выбора, не догму"],
|
||||
"key-decisions": ["какой паттерн под контекст"],
|
||||
"acceptance-criteria": ["паттерн обоснован под контекст задачи"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "billing-audit",
|
||||
"kind": "own",
|
||||
"needs": ["код/диф биллинга для аудита денежной корректности"],
|
||||
"produces": ["отчёт о money-инвариантах биллинга"],
|
||||
"constraints": ["self-authored; аудит кода биллинга (bcmath/идемпотентность/tier/charge_source)", "ADR-012: НЕ налоги (ru-tax), НЕ процесс (process-*), НЕ security (D3)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["проверять потерю копеек, двойное списание, tier-резолюцию, дрейф reconcile"],
|
||||
"key-decisions": ["какие денежные инварианты в scope"],
|
||||
"acceptance-criteria": ["каждый денежный путь проверен на потерю копеек и двойное списание"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "boost",
|
||||
"kind": "external",
|
||||
"needs": ["SQL/Eloquent-запрос к dev-БД или вопрос по docs Laravel/пакета"],
|
||||
"produces": ["результат SQL/Eloquent или релевантная документация Laravel"],
|
||||
"constraints": ["MCP к dev-БД через Roster auto-detect", "НЕ подключать к production DB", "заменил PostgreSQL MCP (#1)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["read-first; serverить только установленные пакеты (Roster)"],
|
||||
"key-decisions": ["источник: только dev-БД, не прод"],
|
||||
"acceptance-criteria": ["запрос/доки отданы из правильного (dev) источника"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "brand-voice",
|
||||
"kind": "external",
|
||||
"needs": ["текст/коммуникация для проверки голоса бренда"],
|
||||
"produces": ["вербальные brand guidelines / проверка тональности"],
|
||||
"constraints": ["вербальный бренд (ADR-015 MKT6) НЕ Brandbook визуальный", "взаимодополняют"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["единый тон коммуникации Лидерры"],
|
||||
"key-decisions": ["тональность под канал/аудиторию"],
|
||||
"acceptance-criteria": ["текст соответствует голосу бренда"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "ccpm",
|
||||
"kind": "external",
|
||||
"needs": ["идея фичи для управления (PRD→эпик→issue→код)"],
|
||||
"produces": ["PRD/эпики/issues в .claude/prds/ + .claude/epics/"],
|
||||
"constraints": ["вендоренный скил, 14 bash-скриптов без хуков", "GitHub-issues через GitHub MCP", "НЕ продуктовые церемонии (product-management)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["PRD → эпик → issues → код"],
|
||||
"key-decisions": ["декомпозиция эпика на issues"],
|
||||
"acceptance-criteria": ["фича прослежена PRD→issue→код"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "claude-code-setup",
|
||||
"kind": "external",
|
||||
"needs": ["паттерны использования Claude Code для рекомендаций"],
|
||||
"produces": ["рекомендации автоматизаций (hooks/permissions/settings)"],
|
||||
"constraints": ["READ-ONLY рекомендатель, не меняет конфиг"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["предлагает, не применяет"],
|
||||
"key-decisions": ["какие автоматизации предложить"],
|
||||
"acceptance-criteria": ["рекомендации релевантны паттернам использования"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "claude-md-management",
|
||||
"kind": "external",
|
||||
"needs": ["намерение правки CLAUDE.md (audit / capture learnings)"],
|
||||
"produces": ["обновлённый CLAUDE.md (через claude-md-improver / revise-claude-md)"],
|
||||
"constraints": ["единственный разрешённый канал правки CLAUDE.md (§5 п.10)", "синхронизировать Pravila + Tooling (п.7)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["claude-md-improver для структурных, revise для learnings"],
|
||||
"key-decisions": ["audit-правка vs захват learnings"],
|
||||
"acceptance-criteria": ["CLAUDE.md обновлён через плагин, нормативка синхронна"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "context7",
|
||||
"kind": "external",
|
||||
"needs": ["вопрос по API/доке конкретной библиотеки/SDK"],
|
||||
"produces": ["актуальная документация библиотеки/SDK"],
|
||||
"constraints": ["первый выбор для доков известной библиотеки", "WebFetch — fallback на URL; WebSearch — без знания библиотеки"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["resolve-library-id → query-docs"],
|
||||
"key-decisions": ["какая библиотека/тема"],
|
||||
"acceptance-criteria": ["актуальная дока по API получена"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "cspell",
|
||||
"kind": "external",
|
||||
"needs": ["Markdown-текст для проверки орфографии", "пользовательский словарь проекта"],
|
||||
"produces": ["отчёт о неизвестных словах"],
|
||||
"constraints": ["ru/en орфография .md с пользовательским словарём", "НЕ стиль (markdownlint)", "НЕ грамматика"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["учитывать cspell-words.txt"],
|
||||
"key-decisions": ["новый валидный термин → в словарь, а не подавлять находку"],
|
||||
"acceptance-criteria": ["0 неизвестных слов вне словаря"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "data-scientist",
|
||||
"kind": "external",
|
||||
"needs": ["датасет + ML-задача (классификация/регрессия/анализ)"],
|
||||
"produces": ["ML-результат: модель, метрики, визуализация"],
|
||||
"constraints": ["вендоренный скил; классический ML-воркфлоу", "требует датасета, не доменной БД портала"],
|
||||
"preview-form": "sample",
|
||||
"defaults": ["загрузка → feature engineering → обучение → оценка"],
|
||||
"key-decisions": ["алгоритм и метрики под задачу"],
|
||||
"acceptance-criteria": ["модель оценена метриками, результат воспроизводим"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "dataforseo-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["SEO-вопрос по РФ (SERP/ключевые/бэклинки)"],
|
||||
"produces": ["SEO-данные РФ: SERP-позиции, бэклинки, конкурентный анализ"],
|
||||
"constraints": ["DEFERRED — платный, pending Б-1", "комплементарен Wordstat #79"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["при платном аккаунте"],
|
||||
"key-decisions": ["какие SEO-данные нужны"],
|
||||
"acceptance-criteria": ["SEO-данные РФ получены (при доступе)"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "dependabot",
|
||||
"kind": "external",
|
||||
"needs": ["Composer/npm-зависимости"],
|
||||
"produces": ["auto-PR при обнаружении CVE в зависимости"],
|
||||
"constraints": ["авто-PR через .github/dependabot.yml", "НЕ блок install (Roave)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["настройка через .github/dependabot.yml"],
|
||||
"key-decisions": ["принять/отклонить предложенное обновление"],
|
||||
"acceptance-criteria": ["CVE-зависимости имеют открытый update-PR"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "deptrac",
|
||||
"kind": "external",
|
||||
"needs": ["PHP-слои для проверки направления зависимостей"],
|
||||
"produces": ["отчёт о нарушениях границ слоёв"],
|
||||
"constraints": ["граф слоёв по app/deptrac.yaml; lefthook job 10", "НЕ типовой анализ (Larastan)", "НЕ декларативный ADR (adr-judge)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["конфиг 13 слоёв, консервативный ruleset"],
|
||||
"key-decisions": ["допустимые направления зависимостей"],
|
||||
"acceptance-criteria": ["0 нарушений границ слоёв"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "design-plugin",
|
||||
"kind": "external",
|
||||
"needs": ["дизайн-вопрос pre-code (критика / UX-копирайт / research synthesis)"],
|
||||
"produces": ["дизайн-критика / UX-копирайт / research synthesis"],
|
||||
"constraints": ["pre-code; a11y-принципы дизайн-уровня", "технический a11y SoT — Pa11y (#9)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["до написания кода"],
|
||||
"key-decisions": ["критика, копирайт или synthesis"],
|
||||
"acceptance-criteria": ["дизайн-замечания/копирайт учтены до кода"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "discovery-interview",
|
||||
"kind": "own",
|
||||
"needs": ["задача до проектирования: фича (интервью заказчика) или ориентация в системе"],
|
||||
"produces": ["discovery-brief (FEATURE) или snapshot мета-слоя (SYSTEM)"],
|
||||
"constraints": ["self-authored; FEATURE=JTBD-интервью человека, SYSTEM=ориентация по мета-слою", "ADR-009 граница с process-analysis (#53): человек vs код"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["FEATURE → discovery-brief в brainstorming; SYSTEM → snapshot в docs/discovery/"],
|
||||
"key-decisions": ["режим FEATURE или SYSTEM"],
|
||||
"acceptance-criteria": ["проблема/контекст вскрыты до проектирования"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "eslint-prettier",
|
||||
"kind": "external",
|
||||
"needs": ["JS/Vue-код для линта+форматирования"],
|
||||
"produces": ["исправленный/проверенный JS/Vue по ESLint+Prettier"],
|
||||
"constraints": ["связка lint+format JS/Vue", "НЕ CSS (Stylelint)", "НЕ типы (vue-tsc)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["flat-config + plugin-vue; config-prettier разводит конфликты"],
|
||||
"key-decisions": ["scope: staged-файлы vs весь js/vue"],
|
||||
"acceptance-criteria": ["0 ESLint-ошибок, формат единообразен"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "figma-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["Figma-файл с дизайн-токенами/компонентами"],
|
||||
"produces": ["извлечённые дизайн-токены/компоненты/стили"],
|
||||
"constraints": ["DEFERRED — нет Figma-аккаунта; источник — статический handoff Платона", "extract-only, code-gen за Frontend Design (#30)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["на поддерживаемом источнике: extract-only"],
|
||||
"key-decisions": ["какие токены извлекать"],
|
||||
"acceptance-criteria": ["токены извлечены из Figma-источника (когда доступен)"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "finance-plugin",
|
||||
"kind": "external",
|
||||
"needs": ["финансовая задача (сверка/variance/проводки/отчётность)"],
|
||||
"produces": ["сверка / variance-анализ / проводки / финотчётность"],
|
||||
"constraints": ["US-GAAP-ориентирован, частично применим РФ; SOX not-applicable", "РФ-налоги — за ru-tax-accounting (#63); ADR-012 граница C6/C7", "warehouse-MCP DEFERRED"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["reconciliation/variance применимы; US-GAAP-скилы с осторожностью"],
|
||||
"key-decisions": ["применим ли скил для РФ-контекста"],
|
||||
"acceptance-criteria": ["финансовая операция корректна, РФ-ограничения учтены"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "frontend-design",
|
||||
"kind": "external",
|
||||
"needs": ["UI-задача: компонент / паттерн / состояние / a11y-принцип"],
|
||||
"produces": ["доменное решение UI/UX (компоненты, паттерны, a11y-принципы)"],
|
||||
"constraints": ["доменная база UI для Vue+Vuetify; обязательный стек-фильтр R6.0", "paired со #19", "технический a11y — за Pa11y, не здесь"],
|
||||
"preview-form": "mockup",
|
||||
"defaults": ["срезать React/Tailwind/shadcn → Vue 3 + Vuetify 3; палитра Forest из Brandbook"],
|
||||
"key-decisions": ["компонент/паттерн под задачу в рамках стека"],
|
||||
"acceptance-criteria": ["решение в стеке Vue+Vuetify, бренд Forest соблюдён"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "github-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["ссылка на repo/issue/PR", "намерение операции"],
|
||||
"produces": ["результат чтения или записи issue/PR/комментария"],
|
||||
"constraints": ["внешний MCP — операции через GitHub API", "НЕ локальный git-флоу (это git/PowerShell)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["read-first перед мутацией"],
|
||||
"key-decisions": ["scope операции: чтение или запись"],
|
||||
"acceptance-criteria": ["операция отражена в GitHub и подтверждена ответом API"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "gitleaks",
|
||||
"kind": "external",
|
||||
"needs": ["git diff или история репозитория"],
|
||||
"produces": ["находки утечек секретов (ключи/токены/пароли/DSN)"],
|
||||
"constraints": ["сканирует секреты в diff/истории через lefthook pre-commit/pre-push", "НЕ SAST-уязвимости кода (Semgrep)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["protect --staged на pre-commit; полная история на pre-push"],
|
||||
"key-decisions": ["реальный секрет vs тестовая фикстура (false-positive)"],
|
||||
"acceptance-criteria": ["0 утечек секретов в diff/истории"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "graphifyy",
|
||||
"kind": "external",
|
||||
"needs": ["структурный/cross-layer вопрос по проекту (docs+code)"],
|
||||
"produces": ["ответ из knowledge-graph портала (узлы/рёбра/source_location)"],
|
||||
"constraints": ["user-level CLI; backend GEMINI/GOOGLE key ИЛИ Claude subagent", "ADR-017: KG1 НЕ context7 #60 (внутренний vs внешний), KG2 НЕ Boost #10 (static vs runtime), KG3 НЕ openapi #47, KG4 НЕ Sentry #34, KG5 НЕ adr-kit/mermaid (auto vs manual)", "артефакты graphify-out*/ gitignored; только manual --update"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["query/explain/path read-only; перед открытым codebase-вопросом сначала graphify"],
|
||||
"key-decisions": ["структурный вопрос vs известный путь (Read/Grep)"],
|
||||
"acceptance-criteria": ["структурный вопрос отвечен с source_location-цитатами"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "histoire",
|
||||
"kind": "external",
|
||||
"needs": ["Vue-компонент для каталога stories"],
|
||||
"produces": ["визуальный каталог stories/variants"],
|
||||
"constraints": ["каталог компонентов (не Storybook)", "Vuetify через setupFile"],
|
||||
"preview-form": "sample",
|
||||
"defaults": ["npm run story"],
|
||||
"key-decisions": ["какие компоненты/variants в каталоге"],
|
||||
"acceptance-criteria": ["stories собираются и рендерятся"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "hookify",
|
||||
"kind": "external",
|
||||
"needs": ["явный /hookify + поведение для предотвращения"],
|
||||
"produces": ["сгенерированный Claude Code хук (PreToolUse/PostToolUse/Stop/...)"],
|
||||
"constraints": ["только по явному /hookify", "HK1 pre-check коллизий с существующими хуками (ADR-010)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["HK1 pre-check до генерации"],
|
||||
"key-decisions": ["тип события хука и условие"],
|
||||
"acceptance-criteria": ["хук генерируется без коллизий с существующей архитектурой"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "ide-helper",
|
||||
"kind": "external",
|
||||
"needs": ["Laravel-проект (facades/модели/хелперы)"],
|
||||
"produces": ["IDE-заглушки (@mixin IdeHelper*) для autocomplete/type-inference"],
|
||||
"constraints": ["генерация stubs для IDE", "НЕ влияет на рантайм"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["ide-helper:generate + models -W -M -N"],
|
||||
"key-decisions": ["когда перегенерировать (после изменения моделей)"],
|
||||
"acceptance-criteria": ["autocomplete/type-inference покрывают facades+модели"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "jupyter-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["Jupyter-ноутбук для исполнения"],
|
||||
"produces": ["результат исполнения ячеек ноутбука"],
|
||||
"constraints": ["DEFERRED — нет Python ML-окружения на native-Windows"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["на поддерживаемой среде с Python ML"],
|
||||
"key-decisions": ["какие ячейки исполнять"],
|
||||
"acceptance-criteria": ["ноутбук исполнен, результаты получены (на поддерживаемой среде)"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "larastan",
|
||||
"kind": "external",
|
||||
"needs": ["PHP-код для статанализа типов"],
|
||||
"produces": ["отчёт об ошибках типов/сигнатур/undefined-переменных"],
|
||||
"constraints": ["типовой анализ PHPStan+Laravel", "НЕ стиль (Pint)", "НЕ граф слоёв (deptrac)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["уровень + baseline проекта"],
|
||||
"key-decisions": ["новая ошибка vs baseline-долг"],
|
||||
"acceptance-criteria": ["0 новых ошибок типов сверх baseline"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "laravel-backend-patterns",
|
||||
"kind": "own",
|
||||
"needs": ["backend-задача Лидерры (controller/service/job/RLS/деньги)"],
|
||||
"produces": ["проектные backend-конвенции (слоистость, RLS-aware, bcmath, идемпотентность, partition-aware)"],
|
||||
"constraints": ["self-authored справочник проектных конвенций", "ADR-013: BT5 НЕ architecture-patterns #38 (проектные vs generic), BT6 НЕ billing-audit #62"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["controller→service→job; RLS-aware; деньги через bcmath/LedgerService"],
|
||||
"key-decisions": ["паттерн под слой задачи"],
|
||||
"acceptance-criteria": ["backend-код следует конвенциям Лидерры"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "lychee",
|
||||
"kind": "external",
|
||||
"needs": ["Markdown-файлы со ссылками"],
|
||||
"produces": ["отчёт о битых URL и якорях"],
|
||||
"constraints": ["внутренние + внешние ссылки и якоря .md", "НЕ стиль/орфография"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["проверять и внутренние якоря, и внешние URL"],
|
||||
"key-decisions": ["внешний временно недоступный vs реально битый — различать"],
|
||||
"acceptance-criteria": ["0 битых ссылок/якорей"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "markdownlint",
|
||||
"kind": "external",
|
||||
"needs": ["Markdown-файлы для линта"],
|
||||
"produces": ["отчёт о нарушениях стиля Markdown"],
|
||||
"constraints": ["внешний CLI — стиль .md (заголовки/таблицы/пробелы/переносы)", "НЕ орфография (cspell)", "НЕ ссылки (lychee)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["авто-fix где возможно (--fix), кроме корневого CLAUDE.md"],
|
||||
"key-decisions": ["scope: какие .md в проверке"],
|
||||
"acceptance-criteria": ["0 нарушений стиля по правилам markdownlint"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "marketing-plugin",
|
||||
"kind": "external",
|
||||
"needs": ["маркетинговая задача (контент/кампания/SEO/email/бриф)"],
|
||||
"produces": ["маркетинговый артефакт (контент/email-цепочка/SEO-аудит/бриф/отчёт)"],
|
||||
"constraints": ["первичный resolver C1 (8 скилов)", "ADR-015: MKT3 решатель, материал — marketingskills #75; MKT2 НЕ product-management #42"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["скил под тип задачи C1"],
|
||||
"key-decisions": ["какой маркетинг-скил под задачу"],
|
||||
"acceptance-criteria": ["маркетинговый артефакт готов и таргетирован"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "marketing-ru",
|
||||
"kind": "own",
|
||||
"needs": ["маркетинговая задача для РФ-рынка (Директ/ВК/Telegram/лендинг/152-ФЗ)"],
|
||||
"produces": ["РФ-маркетинг: каналы, конверсия лендинга, 152-ФЗ согласия"],
|
||||
"constraints": ["self-authored; РФ-специфика (ADR-015 MKT9, 152-ФЗ cross-ref #71)", "НЕ generic marketing #74"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["РФ-каналы: Яндекс.Директ/ВК/Telegram; согласия по 152-ФЗ"],
|
||||
"key-decisions": ["канал и РФ-ограничения"],
|
||||
"acceptance-criteria": ["маркетинг учитывает РФ-каналы и 152-ФЗ"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "marketingskills",
|
||||
"kind": "external",
|
||||
"needs": ["маркетинговая задача, требующая фреймворка (AIDA/PAS/CRO/...)"],
|
||||
"produces": ["маркетинговые фреймворки как материал (40 шт)"],
|
||||
"constraints": ["материал/резерв-библиотека, не решатель (ADR-015 MKT3)", "решатель — marketing #74"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["материал отбирается, решение за marketing #74"],
|
||||
"key-decisions": ["какой фреймворк под задачу"],
|
||||
"acceptance-criteria": ["фреймворк отобран как материал для решателя"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "mermaid",
|
||||
"kind": "external",
|
||||
"needs": ["требование к диаграмме (C4 / flow / sequence / ...)"],
|
||||
"produces": ["диаграмма в нотации Mermaid/C4"],
|
||||
"constraints": ["вендоренный скил; диаграммы в docs/architecture/", "НЕ фиксация решения (adr-kit)"],
|
||||
"preview-form": "diagram",
|
||||
"defaults": ["C4: context/container/component"],
|
||||
"key-decisions": ["тип и уровень диаграммы"],
|
||||
"acceptance-criteria": ["диаграмма рендерится и отражает систему"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "n8n-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["workflow для движка n8n"],
|
||||
"produces": ["автоматизация процесса через n8n"],
|
||||
"constraints": ["DEFERRED — n8n не в стеке; движок процессов = очередь Laravel", "принятие n8n — отдельное архитектурное решение"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["на среде с n8n"],
|
||||
"key-decisions": ["принятие n8n как движка — отдельный ADR"],
|
||||
"acceptance-criteria": ["workflow исполняется в n8n (когда принят)"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "nightowl",
|
||||
"kind": "external",
|
||||
"needs": ["сквозная корреляция request/job/query трассировок"],
|
||||
"produces": ["коррелированный runtime-трейс (self-hosted)"],
|
||||
"constraints": ["DEFERRED — нет pcntl/posix на native-Windows; pending Б-1/Linux", "ADR-013 BT7 НЕ Sentry #34 (трейс vs ошибки), BT8 НЕ Pail/Boost"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["на Linux-среде с pcntl/posix"],
|
||||
"key-decisions": ["scope корреляции трассировок"],
|
||||
"acceptance-criteria": ["request/job/query скоррелированы в трейс (на поддерживаемой среде)"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "normative-sync",
|
||||
"kind": "own",
|
||||
"needs": ["завершённая интеграция/ADR/brain-артефакт для синка нормативки"],
|
||||
"produces": ["синхронизированные Pravila/PSR_v1/Tooling/CLAUDE.md (version bumps, §0 cross-refs, footer, §9)"],
|
||||
"constraints": ["project-агент; звать после закрытия крупной off-phase интеграции/ADR (Pravila §2.4)", "парный с #85; не в Tooling-каноне счётчиков"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["синк 4 нормативных файлов с version bumps"],
|
||||
"key-decisions": ["какие version bumps/cross-refs нужны"],
|
||||
"acceptance-criteria": ["4 нормативных документа синхронны, cross-refs сходятся"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "nuclei",
|
||||
"kind": "external",
|
||||
"needs": ["работающий хост для скана по шаблонам"],
|
||||
"produces": ["находки известных уязвимостей (CVE/экспозиция/слабый TLS/misconfig)"],
|
||||
"constraints": ["CLI bin/nuclei.exe; цель 127.0.0.1 не localhost", "ADR-014 IS2 НЕ ZAP #68 (широта vs глубина)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["низкий rate-limit для dev"],
|
||||
"key-decisions": ["набор шаблонов"],
|
||||
"acceptance-criteria": ["0 известных уязвимостей по шаблонам"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "openapi-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["OpenAPI/REST-спецификация интеграции"],
|
||||
"produces": ["эндпоинты/схемы/параметры как MCP-ресурсы (чтение)"],
|
||||
"constraints": ["READ-ONLY интроспекция спеки", "НЕ генерация кода"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["read-only через .mcp.json"],
|
||||
"key-decisions": ["какой эндпоинт/схему смотреть"],
|
||||
"acceptance-criteria": ["структура API понята из спеки"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "operations:process-doc",
|
||||
"kind": "external",
|
||||
"needs": ["as-is process description"],
|
||||
"produces": ["structured process documentation"],
|
||||
"constraints": ["marketplace skill — outputs doc only"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["follow operations plugin process-doc template"],
|
||||
"key-decisions": ["scope of the process being documented"],
|
||||
"acceptance-criteria": ["doc covers all process steps and owners"],
|
||||
"source": { "version": "1.2.0", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "operations",
|
||||
"kind": "external",
|
||||
"needs": ["задача по бизнес-процессу (документ/runbook/риск/capacity)"],
|
||||
"produces": ["процессный артефакт (process-doc/runbook/risk/capacity/...)"],
|
||||
"constraints": ["зонтик 9 скилов, 0 lifecycle-хуков", "process-doc → Mermaid-исходник рендерит mermaid (#37)", "НЕ as-is анализ из кода (process-analysis)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["скил под тип артефакта"],
|
||||
"key-decisions": ["какой из 9 скилов под задачу"],
|
||||
"acceptance-criteria": ["процессный артефакт полон (шаги/владельцы)"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "owasp-zap",
|
||||
"kind": "external",
|
||||
"needs": ["работающий веб-портал для DAST"],
|
||||
"produces": ["DAST-отчёт (инъекции/XSS/обход входа/IDOR)"],
|
||||
"constraints": ["активное динамическое тестирование; цель 127.0.0.1", "ADR-014: IS1 НЕ Semgrep #25 (динамика vs статика), IS2 НЕ Nuclei #69 (глубина vs широта)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["цель 127.0.0.1, не localhost"],
|
||||
"key-decisions": ["scope активного скана"],
|
||||
"acceptance-criteria": ["DAST не нашёл критичных уязвимостей"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "pa11y",
|
||||
"kind": "external",
|
||||
"needs": ["отрендеренная веб-страница / URL"],
|
||||
"produces": ["отчёт о нарушениях доступности WCAG 2.1 AA"],
|
||||
"constraints": ["единственный технический SoT a11y в проекте", "НЕ через Lighthouse", "НЕ визуальный смок (Playwright)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["проверять контраст / alt / роли / фокус-порядок по WCAG 2.1 AA"],
|
||||
"key-decisions": ["какие страницы/URL в a11y-прогоне"],
|
||||
"acceptance-criteria": ["0 нарушений WCAG 2.1 AA на проверяемых страницах"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "pdn-152fz-audit",
|
||||
"kind": "own",
|
||||
"needs": ["задача аудита ПДн / 152-ФЗ"],
|
||||
"produces": ["аудит ПДн: инвентаризация, согласия, маскирование, логи доступа, pd_subject_request"],
|
||||
"constraints": ["self-authored; режим техника + закон", "ADR-014: IS4 НЕ pg_anonymizer #29 (аудит vs инструмент), IS5 НЕ D2 (техника vs юр.оформление)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["инвентаризация ПДн в схеме/коде → проверка соответствия"],
|
||||
"key-decisions": ["режим: техника или закон"],
|
||||
"acceptance-criteria": ["ПДн инвентаризированы, соответствие 152-ФЗ проверено"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "pest",
|
||||
"kind": "external",
|
||||
"needs": ["PHP-тесты (unit/feature/RLS)"],
|
||||
"produces": ["результат прогона тестов (pass/fail/assertions)"],
|
||||
"constraints": ["Pest 4: unit/feature/RLS/parallel/browser/stress/mutation", "НЕ Vue-тесты (Vitest)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["composer test; --parallel в CI"],
|
||||
"key-decisions": ["scope: какие тесты, parallel vs serial"],
|
||||
"acceptance-criteria": ["все тесты зелёные, без регрессий"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "pg-anonymizer",
|
||||
"kind": "external",
|
||||
"needs": ["дамп БД с персональными данными"],
|
||||
"produces": ["маскированный дамп (телефоны/имена/email)"],
|
||||
"constraints": ["загрузка по требованию LOAD 'anon' (не db-wide preload)", "на проде liderra.ru"],
|
||||
"preview-form": "sample",
|
||||
"defaults": ["маски по требованию для выгрузок"],
|
||||
"key-decisions": ["какие поля маскировать"],
|
||||
"acceptance-criteria": ["ПДн в выгрузке замаскированы"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "pg-audit",
|
||||
"kind": "external",
|
||||
"needs": ["DDL/DML/DCL операции БД"],
|
||||
"produces": ["аудит-журнал операций на уровне БД"],
|
||||
"constraints": ["pgaudit.log=ddl,role,write; log_parameter=off (ПДн не логируются)", "на проде liderra.ru; закрывает 152-ФЗ"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["журнал в /var/log/postgresql"],
|
||||
"key-decisions": ["scope логируемых операций"],
|
||||
"acceptance-criteria": ["аудит-журнал БД ведётся, ПДн не в логах"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "pg-formatter",
|
||||
"kind": "external",
|
||||
"needs": ["SQL-файл для форматирования"],
|
||||
"produces": ["отформатированный SQL (отступы/регистр/выравнивание)"],
|
||||
"constraints": ["форматирование SQL по хуку на db/schema.sql", "НЕ линт опасных паттернов (squawk)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["стандарт pgFormatter"],
|
||||
"key-decisions": ["scope: какой SQL форматировать"],
|
||||
"acceptance-criteria": ["SQL приведён к стандарту pgFormatter"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "pg-partman",
|
||||
"kind": "external",
|
||||
"needs": ["расписание партиционирования таблиц"],
|
||||
"produces": ["авто-создание/удаление partition-таблиц по расписанию"],
|
||||
"constraints": ["dormant — недоступно на native-Windows PG", "заменён Artisan partitions:create-months"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["на поддерживаемой среде — по расписанию; на dev заменён ручным cron'ом"],
|
||||
"key-decisions": ["окно/гранулярность партиций"],
|
||||
"acceptance-criteria": ["партиции созданы/удалены по расписанию (на поддерживаемой среде)"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "php-insights",
|
||||
"kind": "external",
|
||||
"needs": ["PHP-код для метрик качества"],
|
||||
"produces": ["метрики: сложность, архитектура, code style score"],
|
||||
"constraints": ["on-demand/CI, не блокирует; пороги 78/79/73", "ADR-013 BT4 НЕ Pint/Larastan; уникум — оси complexity+architecture"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["composer insights; baseline-пороги"],
|
||||
"key-decisions": ["какие оси/пороги важны"],
|
||||
"acceptance-criteria": ["метрики не ниже baseline-порогов"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "pint",
|
||||
"kind": "external",
|
||||
"needs": ["PHP-код для форматирования"],
|
||||
"produces": ["отформатированный PHP по PSR-12 + Laravel style"],
|
||||
"constraints": ["только форматирование стиля", "НЕ статанализ типов (Larastan)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["применять Laravel preset"],
|
||||
"key-decisions": ["scope: staged-файлы vs весь php"],
|
||||
"acceptance-criteria": ["0 расхождений со стилем Pint"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "playwright-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["URL или HTML-файл для управления", "намерение: скриншот / взаимодействие / сетевой трейс"],
|
||||
"produces": ["скриншот / результат взаимодействия / снимок console+network"],
|
||||
"constraints": ["внешний MCP — управляет headless-браузером", "НЕ a11y-проверка (это Pa11y)", "НЕ замена unit/e2e-тестам"],
|
||||
"preview-form": "sample",
|
||||
"defaults": ["read-first: снять снимок/состояние страницы до действия"],
|
||||
"key-decisions": ["что проверяем: визуал, взаимодействие или сетевой трейс"],
|
||||
"acceptance-criteria": ["ожидаемое состояние страницы подтверждено снимком/снапшотом"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "plugin-dev",
|
||||
"kind": "external",
|
||||
"needs": ["намерение разработать marketplace Claude-плагин"],
|
||||
"produces": ["scaffold плагина (plugin.json, MCP, хуки, доки, публикация)"],
|
||||
"constraints": ["8 sub-skills + 3 агента", "НЕ standalone-скилы (skill-creator)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["plugin.json + структура компонентов"],
|
||||
"key-decisions": ["состав компонентов плагина"],
|
||||
"acceptance-criteria": ["плагин валиден и публикуем"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "postgres-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["исторический SQL-запрос к dev-БД"],
|
||||
"produces": ["результат SQL (исторически)"],
|
||||
"constraints": ["historic — заменён Laravel Boost (#10); не используется"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["заменён Boost #10"],
|
||||
"key-decisions": ["использовать Boost #10 вместо этого узла"],
|
||||
"acceptance-criteria": ["SQL-запросы идут через Boost #10, не через этот узел"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "postiz",
|
||||
"kind": "external",
|
||||
"needs": ["контент-календарь / план публикаций в соцсети"],
|
||||
"produces": ["запланированные публикации в 30+ соцсетях (VK/Telegram)"],
|
||||
"constraints": ["self-host AGPL-3.0 без дистрибуции (ADR-015 MKT7)", "покрывает VK-постинг"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["контент-календарь, self-host"],
|
||||
"key-decisions": ["каналы и расписание публикаций"],
|
||||
"acceptance-criteria": ["публикации запланированы по календарю"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "process-analysis",
|
||||
"kind": "own",
|
||||
"needs": ["as-is процесс для discovery из кода Laravel"],
|
||||
"produces": ["реконструкция as-is процесса + узкие места"],
|
||||
"constraints": ["self-authored project skill; discovery из app-кода (routes/controllers/jobs)", "ADR-009 граница с discovery-interview (#55): код vs человек", "НЕ моделирование to-be (process-modeling)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["discovery из маршрутов/контроллеров/джобов/очередей"],
|
||||
"key-decisions": ["scope процесса для реконструкции"],
|
||||
"acceptance-criteria": ["as-is процесс восстановлен, узкие места выявлены"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "process-modeling",
|
||||
"kind": "own",
|
||||
"needs": ["to-be бизнес-процесс для моделирования"],
|
||||
"produces": ["BPMN 2.0 модель (swimlane/события/шлюзы) в docs/process/"],
|
||||
"constraints": ["self-authored project skill; нотация BPMN", "НЕ as-is анализ из кода (process-analysis)", "рендер диаграмм — через свой вывод/mermaid"],
|
||||
"preview-form": "diagram",
|
||||
"defaults": ["BPMN 2.0; результаты в docs/process/"],
|
||||
"key-decisions": ["границы и дорожки процесса"],
|
||||
"acceptance-criteria": ["to-be модель покрывает поток управления и роли"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "prod-deploy-validator",
|
||||
"kind": "own",
|
||||
"needs": ["намерение выката на боевой liderra.ru"],
|
||||
"produces": ["вердикт GO/NO-GO + результаты 8 SSH pre-flight проверок"],
|
||||
"constraints": ["project-агент; READ-ONLY по дизайну; звать перед любым выкатом (Pravila §2.4)", "парный с #84; не в Tooling-каноне"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["8 READ-ONLY SSH-проверок: конфиг/сервисы/БД/очереди"],
|
||||
"key-decisions": ["go/no-go по результатам проверок"],
|
||||
"acceptance-criteria": ["вердикт GO/NO-GO обоснован pre-flight проверками"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "product-management",
|
||||
"kind": "external",
|
||||
"needs": ["продуктовая церемония (спека / роадмап / метрики)"],
|
||||
"produces": ["спецификация / роадмап / анализ метрик / конкурентный бриф"],
|
||||
"constraints": ["продуктовые церемонии (write-spec/roadmap/metrics)", "НЕ dev-issues (CCPM)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["/write-spec, /roadmap-update, /metrics-review"],
|
||||
"key-decisions": ["какая церемония под запрос"],
|
||||
"acceptance-criteria": ["артефакт церемонии готов и обоснован"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "promptfoo",
|
||||
"kind": "external",
|
||||
"needs": ["LLM-промпт + тест-кейсы для eval"],
|
||||
"produces": ["результат eval/регрессии промпта (ассерты/judge/red-team)"],
|
||||
"constraints": ["только вручную/CI, никогда в хук (платные вызовы)", "НЕ SAST (Semgrep)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["запуск вручную или в CI"],
|
||||
"key-decisions": ["метрики/ассерты eval"],
|
||||
"acceptance-criteria": ["промпт прошёл регрессию/eval по критериям"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "rector",
|
||||
"kind": "external",
|
||||
"needs": ["PHP-код для авто-рефакторинга/version-upgrade"],
|
||||
"produces": ["трансформированный PHP (upgrade/dead-code/modernization)"],
|
||||
"constraints": ["вручную/CI, не блокирует коммит", "ADR-013: BT1 НЕ Pint (трансформация vs формат), BT2 комплементарен Larastan, BT3 НЕ deptrac"],
|
||||
"preview-form": "dry-run",
|
||||
"defaults": ["dry-run baseline; conservative ruleset"],
|
||||
"key-decisions": ["какие правила трансформации"],
|
||||
"acceptance-criteria": ["код трансформирован, тесты зелёные"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "redis-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["вопрос о состоянии Redis/Memurai (ключи/очереди/TTL)"],
|
||||
"produces": ["состояние Redis (чтение): ключи, очереди, TTL, паттерны"],
|
||||
"constraints": ["READ-ONLY MCP к Redis/Memurai", "НЕ prod-ошибки (Sentry MCP)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["read-only инспекция кэша/очередей"],
|
||||
"key-decisions": ["что инспектировать: кэш, очередь, race"],
|
||||
"acceptance-criteria": ["состояние кэша/очереди/race локализовано"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "roave-security",
|
||||
"kind": "external",
|
||||
"needs": ["composer install/update"],
|
||||
"produces": ["блок установки пакета с известным CVE"],
|
||||
"constraints": ["conflict-список CVE на install/update", "НЕ SAST кода (Semgrep)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["срабатывает автоматически на composer"],
|
||||
"key-decisions": ["нет ручного выбора — автоматический conflict-гейт"],
|
||||
"acceptance-criteria": ["установка с известным CVE заблокирована"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "ru-tax-accounting",
|
||||
"kind": "own",
|
||||
"needs": ["вопрос по РСБУ/НК РФ (НДС/УСН/налоговая база/проводки)"],
|
||||
"produces": ["РСБУ/налоговый контекст: расчёты, проводки ДТ/КТ, выгрузки бухгалтеру"],
|
||||
"constraints": ["self-authored; закрывает РФ-gap finance-plugin (#61)", "ADR-012: НЕ billing-audit #62 (код), НЕ D1/D2 (право)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["НДС/УСН по НК РФ; проводки по РСБУ"],
|
||||
"key-decisions": ["налоговый режим и база"],
|
||||
"acceptance-criteria": ["налоговый расчёт/проводка корректны по НК РФ/РСБУ"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "security-go-live",
|
||||
"kind": "own",
|
||||
"needs": ["портал перед выходом в интернет (go/no-go)"],
|
||||
"produces": ["вердикт GO/NO-GO + сводка проверок безопасности"],
|
||||
"constraints": ["self-authored оркестратор #68-72 + D3", "ADR-014 IS7 НЕ audit-portal (только security+go-live vs полный 14-фазный аудит)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["оркеструет ZAP/Nuclei/Ward/pdn-152fz/threat-model + Semgrep"],
|
||||
"key-decisions": ["go/no-go порог"],
|
||||
"acceptance-criteria": ["вердикт GO/NO-GO обоснован результатами проверок"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "security-guidance",
|
||||
"kind": "external",
|
||||
"needs": ["правка файла с потенциально уязвимым паттерном"],
|
||||
"produces": ["inline-предупреждение + блок правки (sys.exit 2)"],
|
||||
"constraints": ["блокирующий PreToolUse-хук, speed-bump per файл+правило", "НЕ глубокий аудит (Trail of Bits)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["одноразовый блок, retry проходит"],
|
||||
"key-decisions": ["реальная уязвимость vs false-positive"],
|
||||
"acceptance-criteria": ["уязвимый паттерн замечен до коммита"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "semgrep",
|
||||
"kind": "external",
|
||||
"needs": ["PHP/JS/Vue-код для SAST"],
|
||||
"produces": ["отчёт об уязвимостях кода (инъекции, небезопасная конфигурация, XSS)"],
|
||||
"constraints": ["SAST бинарь + MCP", "НЕ секреты в diff (gitleaks)", "НЕ глубокий on-demand аудит (Trail of Bits)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["npm run sast"],
|
||||
"key-decisions": ["scope скана; реальная уязвимость vs false-positive"],
|
||||
"acceptance-criteria": ["0 уязвимостей высокого риска"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "sentry-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["production runtime ошибка для диагностики"],
|
||||
"produces": ["события/ошибки/трассировки из Sentry (чтение)"],
|
||||
"constraints": ["READ-ONLY MCP к self-hosted Sentry", "pending активации Б-1", "НЕ Redis-состояние (Redis MCP)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["read-only диагностика"],
|
||||
"key-decisions": ["какое событие/трассировку смотреть"],
|
||||
"acceptance-criteria": ["причина prod-ошибки локализована по событию Sentry"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "skill-creator",
|
||||
"kind": "external",
|
||||
"needs": ["намерение создать standalone Claude-скил"],
|
||||
"produces": ["scaffold SKILL.md + evals.json + references/"],
|
||||
"constraints": ["конструктор скилов с eval-набором", "НЕ плагины (plugin-dev)", "НЕ хуки (hookify)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["скил с eval-набором для проверки точности"],
|
||||
"key-decisions": ["scope и триггеры скила"],
|
||||
"acceptance-criteria": ["скил оформлен, eval проходит"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "squawk",
|
||||
"kind": "external",
|
||||
"needs": ["SQL-миграция PostgreSQL"],
|
||||
"produces": ["отчёт об опасных паттернах миграции (блокировки, без CONCURRENTLY, ненадёжный DEFAULT)"],
|
||||
"constraints": ["линт миграций в pre-commit для database/migrations", "НЕ форматирование (pgFormatter)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["прогон на staged миграциях"],
|
||||
"key-decisions": ["блокирующая операция vs допустимая"],
|
||||
"acceptance-criteria": ["0 опасных паттернов в миграции"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "stylelint",
|
||||
"kind": "external",
|
||||
"needs": ["CSS-код в .vue SFC или .css-файлах"],
|
||||
"produces": ["отчёт о нарушениях стиля CSS"],
|
||||
"constraints": ["CSS в .vue SFC + css-файлы", "НЕ JS/TS (ESLint)", "НЕ a11y (Pa11y)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["порядок свойств + именование по конфигу проекта"],
|
||||
"key-decisions": ["scope: staged-файлы vs весь css"],
|
||||
"acceptance-criteria": ["0 нарушений стиля CSS"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "superpowers",
|
||||
"kind": "own",
|
||||
"needs": ["задача разработки, требующая процесса (TDD/debug/plan/review/brainstorm/worktree)"],
|
||||
"produces": ["дисциплинированный процесс: тест-первый, план, ревью, верификация"],
|
||||
"constraints": ["мета-процесс (зонтик 14 sub-skills), не доменный решатель", "hard-floor источник (ADR-011)", "paired со #30 Frontend Design"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["skill инвоцируется ПЕРВЫМ для подходящей задачи"],
|
||||
"key-decisions": ["какой sub-skill под задачу (tdd/debugging/writing-plans/brainstorming/...)"],
|
||||
"acceptance-criteria": ["процесс-скил применён ДО реализации, дисциплина соблюдена"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "telegram-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["пост/действие в Telegram-канале"],
|
||||
"produces": ["публикация/редактирование/аналитика Telegram-канала"],
|
||||
"constraints": ["выделенный аккаунт через SESSION_STRING (только .env)", "ADR-015 MKT8"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["выделенный аккаунт, SESSION_STRING в .env"],
|
||||
"key-decisions": ["канал и контент поста"],
|
||||
"acceptance-criteria": ["пост опубликован / аналитика получена"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "threat-model",
|
||||
"kind": "own",
|
||||
"needs": ["портал для моделирования угроз перед публикацией"],
|
||||
"produces": ["STRIDE-модель: attack surface + приоритеты защиты"],
|
||||
"constraints": ["self-authored; STRIDE going-public", "ADR-014 IS6 НЕ Trail of Bits #39 (портал+STRIDE vs generic deep-audit)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["карта точек входа → приоритизация по STRIDE"],
|
||||
"key-decisions": ["что защищать первым перед публикацией"],
|
||||
"acceptance-criteria": ["attack surface картирован, защита приоритизирована"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "trail-of-bits",
|
||||
"kind": "external",
|
||||
"needs": ["diff/код для глубокого security-аудита"],
|
||||
"produces": ["глубокий аудит-отчёт (diff-review / supply-chain / variant / static)"],
|
||||
"constraints": ["on-demand глубокий аудит (8 скилов)", "НЕ inline-блок (Security Guidance)", "НЕ быстрый SAST (Semgrep)"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["on-demand кампания, не в хуке"],
|
||||
"key-decisions": ["какой аудит-скил под задачу"],
|
||||
"acceptance-criteria": ["аудит покрыл diff/supply-chain/варианты"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "trivy",
|
||||
"kind": "external",
|
||||
"needs": ["Docker-образ для скана"],
|
||||
"produces": ["отчёт о CVE в OS-пакетах и зависимостях образа"],
|
||||
"constraints": ["скан Docker-образов в CI перед push в registry", "НЕ скан кода (Semgrep)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["trivy image перед push в Yandex Container Registry"],
|
||||
"key-decisions": ["порог severity для блока"],
|
||||
"acceptance-criteria": ["0 критичных CVE в образе"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "ui-ux-pro-max",
|
||||
"kind": "external",
|
||||
"needs": ["UI-задача, требующая материала (стиль / палитра / UX-гайд / график)"],
|
||||
"produces": ["UI-материалы: стили, палитры, UX-гайдлайны, паттерны графиков"],
|
||||
"constraints": ["материал, не решатель; только через PSR_v1 R14.3 pipeline", "не параллельно с Frontend Design (#30)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["активация только через R14.3 pipeline"],
|
||||
"key-decisions": ["какой материал нужен под задачу"],
|
||||
"acceptance-criteria": ["материал отобран, решение принимает Frontend Design"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "unisender-go-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["массовая email-рассылка через Unisender Go"],
|
||||
"produces": ["отправка маркетинговой email-рассылки"],
|
||||
"constraints": ["DEFERRED — нет upstream MCP, нужна своя обёртка", "ADR-015 MKT5: маркетинговые рассылки НЕ транзакционный email; 152-ФЗ согласия cross-ref #77/#71"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["при наличии обёртки"],
|
||||
"key-decisions": ["сегмент и согласия рассылки"],
|
||||
"acceptance-criteria": ["рассылка отправлена с соблюдением 152-ФЗ согласий (при наличии обёртки)"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "universal-icons-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["потребность в SVG-иконке (не-Lucide коллекция)"],
|
||||
"produces": ["SVG-иконка из коллекции (Material/Tabler/Phosphor/...)"],
|
||||
"constraints": ["только не-Lucide коллекции (ADR-006: Lucide → lucide-vue-next)", "raw-SVG, не компонент"],
|
||||
"preview-form": "sample",
|
||||
"defaults": ["для Lucide использовать lucide-vue-next, не этот MCP"],
|
||||
"key-decisions": ["коллекция и конкретная иконка"],
|
||||
"acceptance-criteria": ["иконка вставлена из не-Lucide коллекции корректно"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "vitest",
|
||||
"kind": "external",
|
||||
"needs": ["Vue-компоненты/модули для unit/component-тестов"],
|
||||
"produces": ["результат прогона Vitest (pass/fail)"],
|
||||
"constraints": ["тесты Vue (jsdom, @vue/test-utils, Pinia)", "НЕ PHP-тесты (Pest)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["npm run test:vue"],
|
||||
"key-decisions": ["scope тестов"],
|
||||
"acceptance-criteria": ["все Vue-тесты зелёные"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "volar",
|
||||
"kind": "external",
|
||||
"needs": ["открытый .vue-файл в VSCode"],
|
||||
"produces": ["IntelliSense / go-to-definition / hover / диагностика типов в редакторе"],
|
||||
"constraints": ["VSCode-расширение (редактор-only)", "НЕ CI type-check (vue-tsc)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["работает в редакторе автоматически"],
|
||||
"key-decisions": ["нет ручного выбора — редакторная служба"],
|
||||
"acceptance-criteria": ["навигация/диагностика по .vue работают в редакторе"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "vue-tsc",
|
||||
"kind": "external",
|
||||
"needs": [".vue-компоненты для проверки типов"],
|
||||
"produces": ["отчёт о несоответствиях типов в шаблонах/script"],
|
||||
"constraints": ["полный type-check только в CI", "НЕ редакторная служба (Volar)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["прогон в CI"],
|
||||
"key-decisions": ["scope проверки типов"],
|
||||
"acceptance-criteria": ["0 ошибок типов vue-tsc"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "ward",
|
||||
"kind": "external",
|
||||
"needs": ["Laravel-проект (.env/config/cookie/secrets/deps)"],
|
||||
"produces": ["аудит-отчёт безопасности настроек Laravel"],
|
||||
"constraints": ["Go CLI bin/ward.exe; заменил Enlightn (abandoned)", "ADR-014 IS3 НЕ Larastan #12/Semgrep #25"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["скан .env/config/заголовков/secrets"],
|
||||
"key-decisions": ["какие настройки критичны"],
|
||||
"acceptance-criteria": ["настройки Laravel безопасны (нет High-находок)"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"skill": "writing-plans",
|
||||
"kind": "own",
|
||||
"needs": ["spec or requirements", "file structure decisions"],
|
||||
"produces": ["implementation-plan with bite-sized TDD tasks"],
|
||||
"constraints": ["only writes the plan file", "no code, no reads beyond plan authoring"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["one action per step (2-5 min)", "test->RED->code->GREEN->commit"],
|
||||
"key-decisions": ["file structure / decomposition", "task granularity"],
|
||||
"acceptance-criteria": ["every step has concrete content (no placeholders)", "types consistent across tasks"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "yandex-metrika-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["вопрос веб-аналитики лендинга"],
|
||||
"produces": ["данные Яндекс.Метрики (визиты/источники/гео/демография/поведение)"],
|
||||
"constraints": ["READ-ONLY MCP; активен при живом лендинге (Б-1)", "НЕ подбор ключевых слов (Wordstat #79)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["read-only чтение метрик"],
|
||||
"key-decisions": ["какие метрики смотреть"],
|
||||
"acceptance-criteria": ["аналитика лендинга получена из Метрики"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"skill": "yandex-wordstat-mcp",
|
||||
"kind": "external",
|
||||
"needs": ["тема для подбора ключевых слов"],
|
||||
"produces": ["частотность запросов РФ (Wordstat): сезонность, связанные фразы"],
|
||||
"constraints": ["только Wordstat (5 read-only tools); Direct-мутации отключены (ADR-015)", "НЕ веб-аналитика (Метрика #78)"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["только Wordstat-модуль"],
|
||||
"key-decisions": ["набор ключевых фраз"],
|
||||
"acceptance-criteria": ["ключевые слова подобраны с частотностью РФ"],
|
||||
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
@@ -158,6 +158,7 @@ nodes:
|
||||
attributes:
|
||||
tooling_section: "§3.5 #10"
|
||||
install: "composer require laravel/boost --dev"
|
||||
conflicts_with: ["postgres-mcp"]
|
||||
|
||||
- id: "#11"
|
||||
name: "Laravel Pint"
|
||||
@@ -333,6 +334,7 @@ nodes:
|
||||
chain_membership: []
|
||||
attributes:
|
||||
tooling_section: "§3.1 #1 (historic)"
|
||||
conflicts_with: ["boost"]
|
||||
|
||||
- id: "#20"
|
||||
name: "Volar"
|
||||
@@ -523,6 +525,7 @@ nodes:
|
||||
attributes:
|
||||
tooling_section: "§4.4 #30"
|
||||
install: "enabledPlugins.frontend-design@anthropics-claude-plugins в ~/.claude/settings.json"
|
||||
conflicts_with: ["ui-ux-pro-max", "21st-magic"]
|
||||
|
||||
- id: "#31"
|
||||
name: "UI UX Pro Max"
|
||||
@@ -543,6 +546,7 @@ nodes:
|
||||
chain_membership: []
|
||||
attributes:
|
||||
tooling_section: "§4.5 #31"
|
||||
conflicts_with: ["frontend-design", "21st-magic"]
|
||||
|
||||
- id: "#32"
|
||||
name: "21st.dev Magic MCP"
|
||||
@@ -559,6 +563,7 @@ nodes:
|
||||
chain_membership: []
|
||||
attributes:
|
||||
tooling_section: "§4.6 #32"
|
||||
conflicts_with: ["frontend-design", "ui-ux-pro-max"]
|
||||
|
||||
- id: "#33"
|
||||
name: "claude-md-management"
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
# Аудит готовности М7 Фазы 8 «Растворение зоопарка + переезд» — находки
|
||||
|
||||
**Дата:** 2026-06-09 · **Ветка:** `worktree-brainrepo` · **HEAD на старте:** `4ea4806f`
|
||||
**Метод:** read-only, инлайн (субагенты запрещены). Скилы: `audit-context-building` (+`sharp-edges` по швам).
|
||||
**Источники:** spec `docs/superpowers/specs/2026-06-08-router-mentor-machine-7-design.md` (§5/§9/§10/§12) + handoff `2026-06-09-phase8-migration-handoff.md` + loose-ends `2026-06-08-router-mentor-loose-ends-registry.md` + код `tools/*.mjs`.
|
||||
**Принцип:** память `*_done.md` НЕ источник истины — всё верифицируется ПО КОДУ.
|
||||
**Кодовая фраза:** «роутер-наставник».
|
||||
|
||||
---
|
||||
|
||||
## Вердикт
|
||||
|
||||
Оборонный контур М1–М6 собран на **~95%**. Блокёр №1 (content-floor port, §10 V1/V1-PS) **закрыт в коде**.
|
||||
Остаток несущего кода Claude **мал**: один настоящий gap (**G1** — verify→Гейт-3 не проведён живьём) + одно
|
||||
решение с мелким кодом (**G2** — манифест 9→11). Всё прочее — owner-активация (A-блок) или осознанно отложено.
|
||||
|
||||
---
|
||||
|
||||
## Confirmed-by-code (собрано)
|
||||
|
||||
| ID | Шов / пункт §12 | Статус | Доказательство |
|
||||
|---|---|---|---|
|
||||
| **C-1** | Seam #1 content-floor port (БЛОКЁР §10 V1/V1-PS, правило 8) | ✅ ПОЛНЫЙ | `shell-content-rules.matchBashHardBlacklist` — весь P-1 набор (rm/mv/cp/chmod/chown/chgrp + node -e/-r/--import/fs.* #4 + python/bash/sh -c + eval + composer/npm/yarn/pnpm install + npx claude-* + curl -X + env-prefix #21 + --watch #22 + wget G7 + nc/ncat/netcat/socat G8 + echo-inj #34 + stderr-redirect C16). `matchPsHardBlacklist` — весь V1-PS (Remove/Move/Copy/Set/Add-Content/Out-File + redirect + iex + egress IWR/IRM/curl/wget + Start-Process + [IO.File]/[IO.Directory] + Stop-Process + service + ExecutionPolicy + ItemProperty + Get-Credential + Restart/Stop-Computer + ScheduledTask + Set-Acl/icacls + New-Item + G10 env/cloud). `floor-decide.floorDecide`: Bash (`bashIsContentBlock` whole+per-segment P-4 + `detectSubshell`) и PS (`psContentBlock` + `psProtectedWrite` P-3 forge-страж) рубят **независимо от плана**, escapable. tool-agnostic write-guard (MCP-writer) + fail-CLOSED на бросок normalize. |
|
||||
| **C-2** | `canonicalAction` PS-ветка (P-2) | ✅ | `escape-grant.canonicalAction`: `if (name==='PowerShell') return powershell:${normalizeCommand(input.command)}` — escape специфичен per PS-команда (не схлопывается в `write:`). |
|
||||
| **C-3** | Проводка пола (enforce-floor) | ✅ | `enforce-floor.decide` зовёт `floorDecide` ПЕРВЫМ, fail-CLOSED (любая ошибка → block), panic-ветка (правило 7б): если `floorDecide` бросит ДО своего escape-чека — `escapeAllowsEvent` всё равно оценивается (не кирпич мимо escape). |
|
||||
| **C-4** | Seam #2 escape-survivability (правило 7/L6) | ✅ ПОДТВЕРЖДЁН | `enforce-read-path-deny` (Read+escapeGrantOpen), `enforce-mcp-classification` (mcp+egress ветки + escaped()), `enforce-normative-content-rules` (escapeAction + escaped law-edit §6) — все читают грант. Пол тоже. `canonicalAction` тотальна. |
|
||||
| **C-5** | Seam #3 транспорт судьи М4 | ✅ ЕСТЬ (owner-активация остаётся) | `enforce-judge-gate.callJudgeModel` → реальный `callAnthropicAPI` (Sonnet за `ROUTER_LLM_KEY`); `runJudgeGate` async + degraded-allow на unavailable (VA-FIX-1, не фабрикует NO-GO); `runJudgeTurn` 3 режима inert/shadow/live-block; `decide`→`finalGate` (пол перевешивает). Рубильник `judge-gate-config.judgeActive` = флаг `ROUTER_MENTOR_JUDGE_ENABLED=1` + `resolveJudgeKey`. `judgeGateMode` = inert/shadow/live-block (`ROUTER_MENTOR_JUDGE_MODE=block`). |
|
||||
| **C-6** | Seam #4 skill-журналер (SE-K) | ✅ | `enforce-skill-journaler` (PostToolUse Skill) пишет каждый Skill в action-journal (op:'Skill') + дедуп vs wall-pre-write; мост `extractSkillCalls`→K2. |
|
||||
| **C-7** | Seam #4 доска «кто на посту» | ✅ (live-источник событий — follow-up) | `status-md-generator.computeGuardBoardBlock` рендерит манифест (`checkManifest`→registered/missing→⚠️ ПОСТ ПУСТОЙ) + режим судьи (`judgeGateMode()` inert/shadow/floor-only/live-block). recentEscapes/recentBlocks = `[]` (детализация follow-up, см. D-3). |
|
||||
| **C-8** | Seam #4 normative-канал КАРТА/ЗАКОН (SE-D) | ✅ | `enforce-normative-content-rules`: `DISCIPLINE_SOURCE_RE` (tools/enforce-/judge-/floor-/escape-grant/action-journal/receipt-/shell-content-rules/plan-lock/classify-destructive/path-normalization) + `sealedPlanCoversEdit` (build-loop: LAW под планом → CARD) + escaped owner law-edit (§6) снимает гейт. |
|
||||
| **C-9** | Манифест полный (SE-B) | ✅ | `floor-manifest-check.DEFAULT_REQUIRED_HOOKS` = все 9 хуков М1–М6 (floor/supreme-gate/normative-content-rules/read-path-deny/mcp-classification/judge-gate/snapshot/floor-escape-consume/skill-journaler). WARN-only (Δ8). |
|
||||
| **C-10** | Поглощённые coverage/todowrite → журнал-K2 (Фаза 4a) | ✅ переписаны in-place, fail-CLOSE | `enforce-coverage-verify.decide` судит `skill:`-канал по `skillTakenByJournal` (K2) ∩ turn-scope (`turnToolUses`), НЕ по строке coverage (Класс 1 закрыт). `direct/node/chain/hook/agent` приняты на этом слое (судит живой М4, §1 П3). `enforce-todowrite-skill-verifier` тоже через `extractSkillCalls`. `domain-skill-discipline` (K2). |
|
||||
| **C-11** | Стена М2 + волны R-08 | ✅ | `enforce-supreme-gate`: `verifyFrozenPlan`/`actionMatchesStep`/`treeLeafAt`/`validatePlanTree` (tree-aware волны), подписанные расписки (`receipt-sign`), журналирование op:Skill на plan-step (substrate K2). |
|
||||
|
||||
---
|
||||
|
||||
## 🔴 Остаток несущего кода (Claude)
|
||||
|
||||
### G1 — НЕСУЩИЙ: verify→Гейт-3 не проведён живьём (блокирует увольнение `verify-before-push`)
|
||||
`enforce-verify-before-push` — **старый** zoo-хук (sentinel-артефакт + override-фраза + `RULE_KEY_PUSH`), §10 его увольняет.
|
||||
Замена по §5 = «М5 подписанный GREEN + М4 Гейт-3». `judge-engine` **имеет** линзы `gate3: [goal_achieved, premortem_whole, behavior_vs_goal]` (`requiredLensesFor`),
|
||||
**но хук судьи (`enforce-judge-gate`) бьёт только Гейт-2** (`extractGate2Product` = Write плана `docs/superpowers/plans/*.md`).
|
||||
Гейт-3 ни на каком живом триггере не висит; нет PreToolUse-хука, требующего подписанный GREEN перед `git commit/push`.
|
||||
→ **Увольнять `verify-before-push` без G1 = потерять pre-push verify-дисциплину.** NO-GO на это увольнение до G1.
|
||||
**Достроить:** живой Гейт-3 / подписанный-GREEN PreToolUse на `git commit/push` (М4 Гейт-3 + М5 signer-расписка).
|
||||
|
||||
### G2 — решение + мелкий код: журнал-K2 coverage/todowrite не в манифесте
|
||||
`enforce-coverage-verify` + `enforce-todowrite-skill-verifier` переписаны в журнал-K2 fail-CLOSE (Фаза 4a),
|
||||
но **отсутствуют в `DEFAULT_REQUIRED_HOOKS` (9)** и §10 числит `coverage-verify` к увольнению.
|
||||
**Решение:** оставить+зарегистрировать журнал-K2-версии (расширить манифест 9→11) **vs** уволить (судья coverage не судит — Гейт-2 only).
|
||||
Рекомендация: **оставить+зарегистрировать** (это и есть миграция дисциплины; иначе coverage-дисциплина теряется).
|
||||
Код: расширить `DEFAULT_REQUIRED_HOOKS` + runbook-регистрация.
|
||||
|
||||
---
|
||||
|
||||
## 🟥 Owner-активация (кода Claude нет — A-блок loose-ends)
|
||||
A1-half (флаг `ROUTER_MENTOR_JUDGE_ENABLED=1` + ключ судьи keychain + регистрация хука + shadow→block) · A2 Фаза 8
|
||||
(атомарно 9 хуков + увольнение зоопарка + `sealedPlanCoversEdit` live) · A3 пол · A4 escape/снимок · A5 стена matcher `*` + HMAC ·
|
||||
A6 M1 matcher · A7 M3 живой Sonnet · A8 правила судьи в protected-файл · A9 push.
|
||||
|
||||
## ⚪ Отложено осознанно (owner-scoped / контракты)
|
||||
R-10 G6-адаптер (НЕ построен) · M4 think-layer 4 пункта · M6 FIX-5 подпись floor_escape-гранта (spec §6 «не обязательно») ·
|
||||
доска recentEscapes/Blocks live-источник (сейчас `[]`) · observer Фаза 2 (авто-правка нормативки — owner-scope).
|
||||
|
||||
## 🟨 Doc-drift (косметика, не блок)
|
||||
**D-1:** шапка `enforce-judge-gate` стр.12-14 устарела — пишет «реальный llmCall-транспорт подключит владелец», но `callJudgeModel` уже использует `callAnthropicAPI`. Транспорт проведён A1; остаётся лишь флаг/ключ/режим.
|
||||
**D-3:** доска recentEscapes/recentBlocks = `[]` (live-источник — follow-up B-блока R-09/R-30, не E).
|
||||
|
||||
---
|
||||
|
||||
## §10 GO/NO-GO на увольнение
|
||||
|
||||
| Зоопарк-хук | Вердикт | Условие |
|
||||
|---|---|---|
|
||||
| `router-gate` / `powershell-gate` | ✅ **GO** | content-floor port C-1 закрыт (блокёр снят) |
|
||||
| `runtime-write-deny` | ✅ GO | пол RUNTIME_RE + escape |
|
||||
| `verify-before-push` / `verify-record` | 🔴 **NO-GO до G1** | Гейт-3 не проведён живьём |
|
||||
| `coverage-verify` / `todowrite-skill-verifier` | 🟡 GO при G2 | оставить журнал-K2 + манифест 9→11 |
|
||||
| `branch-switch` | ✅ GO | М2 plan-step + M6 escape (маркеры удалены — подтвердить в Pass B) |
|
||||
| `llm-judge-per-tool` / `-response-scan` (L4) | ✅ GO по A1 | после активации судьи М4 |
|
||||
| `router-prehook` / `router-tool-gate` / `router-stop-gate` / `router-embedding-warmup` | ✅ GO по A7 | после живого М3 |
|
||||
| `memory-coverage` / `tdd-gate` / `tdd-real-test-verifier` / `self-debrief-detector` / `rationalization-audit` / `prompt-injection`(reminder) | ✅ GO | поглощены §4.2 (см. Pass B детально) |
|
||||
| `decomposition-detector` / `safe-baseline-metering` / `parallel-session-lock` | ✅ GO | no-op (ничего не теряют) |
|
||||
| `subagent-return-scanner` / `subagent-prompt-prefix` | ✅ GO | субагенты запрещены |
|
||||
| dead-vocab `findOverride`/`findOverrideAttempt` + врущие сообщения | ✅ GO | чистка Класса 6 |
|
||||
|
||||
---
|
||||
|
||||
## Pass B — построчный аудит ядер M1/M3/M6 (добор «ничего не пропускай»)
|
||||
|
||||
Швы M2/M4/M5 покрыты выше (C-1..C-11). Здесь — построчный добор ядер M1/M3/M6.
|
||||
|
||||
### M1 — Фундамент (журнал + ключ). Вердикт: ✅ **здоров, дефектов нет**
|
||||
- `escape-grant.canonicalAction` — **тотальна** (try/catch → `'unknown:'` на любой throw; вход всегда доходит до escape-чека, SE-I/L6). Ветки Bash/**PowerShell** (P-2)/mcp/write. `findOpenGrant` — **единый** предикат свежести (action-match + `ts∈[0,5мин]` + не-погашен) для open-проверки И consume → гасится ИМЕННО открывший грант, без дрейфа. Гранты читаются read-only из `askuser-decisions-<sess>.jsonl` (type `floor_escape`), пишет среда (`enforce-askuser-answer-parser`), контроллер канал не пишет (runtime protected).
|
||||
- `action-journal` — хеш-цепочка `sha256(prev_hash + canonicalJson({seq,ts,payload}))` (**seq+ts в хеше**, B2 — подмена метаданных задним числом ломает цепь); голова HMAC-подписана (`JOURNAL_HEAD`); `verifyChain` пересчитывает каждый хеш + linkage + подпись головы, структурно-битая запись → `{ok:false}` **значением**, не throw (N2, fail-closed). `assertSafeSessionId` — path-injection guard (N3, единый для всех строителей пути). **Неатомарность** дозаписи (append jsonl + write head — 2 fs-вызова) остаётся по конструкции, но **запинена** характеризационным тестом (`5782ede3`): обрыв → `verifyChain {ok:false, brokenAt}` (fail-closed); полная починка = иной дизайн персиста, owner-scope.
|
||||
- `receipt-sign` — `canonicalJson` (сорт ключей, стабильно); `signPayload` HMAC-SHA256(`domain + ' ' + canonicalJson`), без ключа → null; `verifyReceipt` — формат sig (64 hex) + пересчёт + `timingSafeEqual` (constant-time), без ключа/sig → false (fail-closed). **Domain-separation (R-31)** заморожен: JOURNAL_HEAD/FROZEN_PLAN/FROZEN_ARTIFACT/APPROVAL/STEP_PTR/**M5_GREEN** — подпись одного типа не принимается за другой. **NB:** домен `M5_GREEN` существует → примитив подписанного GREEN для G1 **готов**; не хватает лишь хук-триггера (см. G1).
|
||||
|
||||
### M6 — Ключ+снимок (escape + откат). Вердикт: ✅ **здоров, дефектов нет**
|
||||
- `enforce-snapshot` (PreToolUse после пола): перед разрушительным — `git stash create` + `update-ref refs/floor-snapshots/<id>` + `restore-points.jsonl`. Чистое дерево → ref=HEAD; **git-ошибка → fail-CLOSE (block)**; уникальный id (FIX-4: ts+pid+монотонный счётчик — два разрушительных в одну мс не клоббернут точку возврата). Инфра-ошибка самого хука (parse/stdin) → fail-open allow, но это не дыра: пол (`enforce-floor`) рубит разрушительное отдельно и fail-CLOSE; снимок = страховка отката, не гейт.
|
||||
- `floor-escape-consume` + `enforce-floor-escape-consume` (PostToolUse): one-shot погашение через тот же `findOpenGrant`. **Порядок верен:** снимок-блок (PreToolUse) → действие не исполнилось → PostToolUse не сработал → грант НЕ сгорел (escape переживает снимок-блок). fail-open (PostToolUse не бросает) — приемлемо (не-погашение = мелкий over-permit в 5-мин окне, не дыра; блокировать PostToolUse не может).
|
||||
- **FIX-5 (подпись floor_escape-гранта) осознанно отложена** (spec §6 «не обязательно»): целостность гранта держит **защита записи в runtime** (пол RUNTIME_RE + read-path-deny + content-floor #4 node-inline-fs рубят forge), а не подпись. Forge через `node -e fs.*` / PS-запись закрыт content-floor (C-1). Остаточный риск нулевой пока content-floor стоит; owner-noted.
|
||||
|
||||
### M3 — Роутер (каким навыком, по журналу). Вердикт: ✅ **классификатор жив; «не тот навык» по K2 — модуль готов; живой Sonnet = A7 owner**
|
||||
- `router-classifier` **жив** (выдаёт `task_type`/`recommended_chain` — виден в §17-хук-контексте каждый ход). Аудит ядра + 5 TDD-фиксов — память `project_m3_audit_and_fixes` (chosen∈candidates / managed-терминатор / token-cover / neutrality).
|
||||
- «Не тот навык по журналу» — `domain-skill-discipline` (`uncalledDomainSkills`/`domainCallDiscipline`, K2: рекомендованный доменный навык не вызван по журналу → not ok). Чистый модуль; **хук-проводка enforce — часть переезда/A7** (не отдельный enforce-хук сейчас). routing-tag-обход (`router-tool-gate`) увольняется §10.
|
||||
- Живой Sonnet вместо мока для роутинг-решений = **A7 (owner)**.
|
||||
|
||||
### M2/M4/M5 — сводка (детали C-1..C-11)
|
||||
- **M2 стена** `enforce-supreme-gate`: действие=шаг запечатанного плана, tree-aware волны (R-08), подписанные расписки, журналирование op:Skill (substrate K2). ✅
|
||||
- **M4 судья** — механический пол `judge-gate-floor` (K2/K5) + думающий `judge-engine` (линзы gate1/gate2/**gate3**) + транспорт `callAnthropicAPI`; хук бьёт **только Гейт-2** (G1). ✅ транспорт / 🔴 Гейт-3 wiring.
|
||||
- **M5 пол** `floor-decide`: content-floor (Bash+PS, P-1/V1-PS) + write-guard + критерий/мутация (прежние аудиты). ✅
|
||||
- `branch-switch` — увольняется целиком §10 (замена = M2 plan-step + M6 escape); проверять marker-removal не нужно (хук уходит).
|
||||
|
||||
---
|
||||
|
||||
## Итог аудита (Шаг 1 завершён)
|
||||
|
||||
**Контур М1–М6 собран и аудирован по коду. Криптоядра M1/M6 здоровы, швы M2/M4/M5 закрыты, блокёр №1 (content-floor port) полон.**
|
||||
**Несущий остаток Claude = G1 (Гейт-3/подписанный-GREEN wiring) + G2 (манифест 9→11, решение).** Примитив подписи для G1 (`M5_GREEN`) готов.
|
||||
Всё прочее — owner-активация (A-блок) и осознанно отложенное. §10-увольнение безопасно при G1 (для verify-before-push) и G2 (для coverage/todowrite); router-gate/powershell-gate — GO уже сейчас.
|
||||
|
||||
---
|
||||
|
||||
## G1 (Level A) — ПОСТРОЕН (2026-06-09, commit-not-push)
|
||||
|
||||
Цепочка скилов отработана полностью: `audit-context-building` → `sharp-edges` → `variant-analysis` → `writing-plans` → `executing-plans` (инлайн TDD, наблюдаемый RED) → `verification-before-completion`. План: `docs/superpowers/plans/2026-06-09-g1-gate3-verify-signed-green.md`.
|
||||
|
||||
**7 task-коммитов `1857773f..b92afad9`** на `worktree-brainrepo` (HEAD `b92afad9`), origin НЕ тронут:
|
||||
- `1857773f` task1 — `RECEIPT_DOMAINS.VERIFY_PASS` (receipt-sign).
|
||||
- `10885990` task2 — `verify-receipt.mjs` (`signVerifyReceipt`/`verifyReceiptSig`/`acceptVerifyReceipt`: sig + occurrence>0 + fingerprint-match).
|
||||
- `0f20c944` task3 — `verify-gate-config.mjs` рубильник (`ROUTER_MENTOR_VERIFY_ENABLED`: OFF=inert$0 / ON+key=enforce / ON+no-key=fail-CLOSE).
|
||||
- `93e516e6` task4 — producer pure `buildVerifyReceipt`.
|
||||
- `9ce6041b` task5 — producer staged-fingerprint + I/O main (сам прогоняет сюиту, SE-G1-5).
|
||||
- `2bd7efbe` task6 — consumer `enforce-verify-gate.mjs` (PreToolUse, fail-CLOSE + escape M6 + docs-only, fingerprint из реального `git diff --staged`).
|
||||
- `b92afad9` task7 — `enforce-verify-gate` в `DEFAULT_REQUIRED_HOOKS` (9→10).
|
||||
|
||||
**Верификация:** vitest tools regression **3350 passed / 2 skipped / 0 failed** (167 файлов; свежий прогон 10:25:55), выше планки §9.2 ≥2843+2skip. +27 G1-тестов к baseline 3323.
|
||||
|
||||
**Решение реализации (deviation, обосновано):** verify-receipt пишется в ЕДИНЫЙ `~/.claude/runtime/verify-receipt.json` (НЕ session-scoped) — producer-CLI не имеет session_id из stdin как consumer-хук; свежесть держит fingerprint. Escape-гранты остаются session-scoped.
|
||||
|
||||
**§10 разблокировано:** `verify-before-push`/`verify-record` теперь увольняемы (замена = `enforce-verify-gate`). **GO/NO-GO таблица выше:** verify-before-push 🔴NO-GO→✅GO.
|
||||
|
||||
**Остаётся (owner / follow-on):** G2 (манифест coverage/todowrite 10→12 — строкой в A2); owner-активация A-блок (ключ подписанта + `ROUTER_MENTOR_VERIFY_ENABLED=1` + регистрация хуков + увольнение зоопарка §10 + shadow→block + «пуш»); Level B (per-criterion + мутация P18 через готовый `runCriterionGate`).
|
||||
|
||||
@@ -0,0 +1,624 @@
|
||||
# Расширение факторного анализа «мозга» — кандидаты раздела F
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Поднять сигналы новых защит router-gate v4 (rationalization-флаги, safe-baseline action, вердикт LLM-судьи, активность судьи) в эпизод журнала наблюдателя и добавить их как новые оси факторного анализа.
|
||||
|
||||
**Architecture:** Сигналы уже пишутся хуками на диск в `~/.claude/runtime/`, но (1) часть не логируется в нужной форме — добавляем точечную инструментовку (verdict-log, action-log); (2) парсер транскрипта их не читает — добавляем чистый модуль-ридер `observer-v4-signals.mjs`, который `parseTranscript` зовёт и кладёт результат в новые поля эпизода; (3) анализатор не раскладывает их — добавляем 4 оси в `FACTOR_FNS`. Принцип: эпизод остаётся единственным входом факторной матрицы (хуки → runtime-файлы → парсер → эпизод → `FACTOR_FNS`).
|
||||
|
||||
**Tech Stack:** Node ESM (`.mjs`), vitest (tools-only). Деньги — `bcmath`-free, float USD как в `cost-aggregator.mjs`.
|
||||
|
||||
**Канонический прогон тестов tools** (per memory `feedback_vitest_sentinel_recipe`): `npm run test:tools` сломан keytar-установкой в `app/node_modules` → запускать `npx vitest run --root app --config vitest.config.tools.mjs <путь>`.
|
||||
|
||||
**Базовые факты, на которых стоит план (сверены по коду 31.05.2026):**
|
||||
- Эпизод собирается в `tools/observer-transcript-parser.mjs:888` (`parseTranscript`), окно хода — `turn = entries.slice(start)`, `started_at`/`ended_at` из `timestamps`.
|
||||
- rationalization-флаги: `tools/enforce-hook-helpers.mjs:280` пишет `{ts, kind, evidence}` в `rationalization-flags-<sess>.jsonl`; читает `:291`.
|
||||
- safe-baseline: `tools/enforce-safe-baseline-metering.mjs` пишет ledger `safe-baseline-ledger-<sess>.json` (`saveLedger:180`) и логирует ТОЛЬКО `soft_flag` в `safe-baseline-flags-<sess>.jsonl` (`logFlag:186`, строка `{ts, tool, reason}`); `hard_block` нигде не логируется. Ledger-state (`newCounterState` `safe-baseline-metering.mjs:60`) содержит `counts`/`skill_match_within_task`, но `warnings_issued`/`hard_blocks_issued` живой `runLiveDecision` НЕ заполняет.
|
||||
- LLM-судья: `tools/enforce-llm-judge-per-tool.mjs:112` `runPerTool` зовёт `decide()` → вердикт `{block, verdict, degraded}`; бюджет `tools/llm-judge.mjs:121` `readJudgeBudget`/`:127` `bumpJudgeBudget` хранит `{calls}` в `llm-judge-budget-<sess>.json`; вердикт по-инструментно НЕ логируется. `JUDGE_SESSION_BUDGET = 200` (`llm-judge.mjs:67`).
|
||||
- Цена: `tools/cost-pricing.mjs` `PRICING.sonnet = {input:3e-6, output:15e-6}`; агрегатор `tools/cost-aggregator.mjs:13` `episodeUsd` / `:40` `aggregateDay` (5 компонент + `total_usd` + `episode_count`); Stop-хук `tools/cost-stop-hook.mjs` пишет `cost-daily.json`.
|
||||
- Факторные оси: `tools/brain-retro-analyzer.mjs:326` `FACTOR_FNS`; `analyze()` `:657` штампует `episode._inferredOutcome` и строит матрицу `buildFactorMatrix:384`.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| Файл | Ответственность | Действие |
|
||||
|---|---|---|
|
||||
| `tools/enforce-llm-judge-per-tool.mjs` | вызов судьи per-tool | Modify — логировать вердикт |
|
||||
| `tools/enforce-hook-helpers.mjs` | общий I/O хуков | Modify — `logJudgeVerdict` + `readJudgeVerdicts` + `logSafeBaselineAction` + `readSafeBaselineActions` |
|
||||
| `tools/enforce-safe-baseline-metering.mjs` | safe-baseline метеринг | Modify — логировать ЛЮБОЙ action (incl hard_block) |
|
||||
| `tools/observer-v4-signals.mjs` | **новый** чистый ридер runtime-сигналов в окне хода | Create |
|
||||
| `tools/observer-transcript-parser.mjs` | сборка эпизода | Modify — позвать ридер, добавить поля эпизода |
|
||||
| `tools/cost-pricing.mjs` | ставки | Modify — `JUDGE_PER_CALL_USD` |
|
||||
| `tools/cost-aggregator.mjs` | стоимость per-episode/day | Modify — компонент `judge_spend_usd` |
|
||||
| `tools/brain-retro-analyzer.mjs` | факторные оси | Modify — 4 новые `FACTOR_FNS` |
|
||||
| `docs/observer/brain-data-catalog.md` | каталог данных мозга | Modify — перенести сигналы из F в A/B/E |
|
||||
| `tools/*.test.mjs` | тесты | Create/Modify per task |
|
||||
|
||||
**Новые поля эпизода (schema_minor bump 3 → 4):**
|
||||
- `task_cost.judge_spend_usd` (number, default 0)
|
||||
- `v4_signals.rationalization_flag_count` (int)
|
||||
- `v4_signals.safe_baseline_action` (`allow`/`soft_flag`/`hard_block`/`null`)
|
||||
- `v4_signals.judge_verdict` (`YES`/`NO`/`block`/`disabled`/`null`)
|
||||
- `v4_signals.judge_calls` (int)
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Логирование вердикта судьи
|
||||
|
||||
**Files:**
|
||||
- Modify: `tools/enforce-hook-helpers.mjs` (после `readRationalizationFlags`, ~:299)
|
||||
- Modify: `tools/enforce-llm-judge-per-tool.mjs` (`runPerTool`, :112)
|
||||
- Test: `tools/enforce-hook-helpers.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Написать падающий тест на `logJudgeVerdict`/`readJudgeVerdicts`**
|
||||
|
||||
```js
|
||||
// tools/enforce-hook-helpers.test.mjs — добавить describe-блок
|
||||
import { tmpdir } from 'node:os';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { logJudgeVerdict, readJudgeVerdicts } from './enforce-hook-helpers.mjs';
|
||||
|
||||
describe('judge verdict log', () => {
|
||||
it('appends and reads back verdict records', () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'jv-'));
|
||||
logJudgeVerdict('sess1', { tool: 'Edit', verdict: 'YES' }, { baseDir: dir });
|
||||
logJudgeVerdict('sess1', { tool: 'Bash', verdict: 'NO' }, { baseDir: dir });
|
||||
const recs = readJudgeVerdicts('sess1', { baseDir: dir });
|
||||
expect(recs.length).toBe(2);
|
||||
expect(recs[0]).toMatchObject({ tool: 'Edit', verdict: 'YES' });
|
||||
expect(typeof recs[0].ts).toBe('string');
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
it('returns [] when no file', () => {
|
||||
expect(readJudgeVerdicts('nope', { baseDir: tmpdir() })).toEqual([]);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Прогнать — убедиться, что падает**
|
||||
|
||||
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/enforce-hook-helpers.test.mjs`
|
||||
Expected: FAIL — `logJudgeVerdict is not a function`.
|
||||
|
||||
- [ ] **Step 3: Реализовать функции в `enforce-hook-helpers.mjs`** (вставить после `readRationalizationFlags`, перед `readRouterState`)
|
||||
|
||||
```js
|
||||
export function logJudgeVerdict(sessionId, { tool, verdict }, opts = {}) {
|
||||
try {
|
||||
const dir = opts.baseDir || runtimeDir();
|
||||
const f = join(dir, `llm-judge-verdicts-${sessionId || 'unknown'}.jsonl`);
|
||||
appendFileSync(f, JSON.stringify({
|
||||
ts: new Date().toISOString(),
|
||||
tool: tool || 'unknown',
|
||||
verdict: verdict == null ? null : String(verdict),
|
||||
}) + '\n');
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
export function readJudgeVerdicts(sessionId, opts = {}) {
|
||||
try {
|
||||
const dir = opts.baseDir || runtimeDir();
|
||||
const f = join(dir, `llm-judge-verdicts-${sessionId || 'unknown'}.jsonl`);
|
||||
if (!existsSync(f)) return [];
|
||||
return readFileSync(f, 'utf-8').split('\n').filter(Boolean).map((l) => {
|
||||
try { return JSON.parse(l); } catch { return null; }
|
||||
}).filter(Boolean);
|
||||
} catch { return []; }
|
||||
}
|
||||
```
|
||||
|
||||
(`runtimeDir`, `appendFileSync`, `readFileSync`, `existsSync`, `join` уже импортированы в файле — проверить шапку, при отсутствии `existsSync` добавить в существующий `import { ... } from 'node:fs'`.)
|
||||
|
||||
- [ ] **Step 4: Прогнать тест — PASS**
|
||||
|
||||
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/enforce-hook-helpers.test.mjs`
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Вызвать логгер из судьи.** В `enforce-llm-judge-per-tool.mjs:112` `runPerTool`, после строки `if (result.verdict !== undefined) bumpBudgetImpl({ sessionId, by: 1 });` добавить:
|
||||
|
||||
```js
|
||||
if (result.verdict !== undefined) {
|
||||
bumpBudgetImpl({ sessionId, by: 1 });
|
||||
logVerdictImpl({ sessionId, tool: event && event.tool_name, verdict: result.block ? 'block' : (result.verdict ?? 'YES') });
|
||||
}
|
||||
```
|
||||
|
||||
В сигнатуру `runPerTool({...})` добавить параметр `logVerdictImpl = () => {}` (no-op по умолчанию — тесты не пишут на диск). В `main()` (:153) передать `logVerdictImpl: ({ sessionId, tool, verdict }) => logJudgeVerdict(sessionId, { tool, verdict })` и добавить `logJudgeVerdict` в импорт из `./enforce-hook-helpers.mjs`.
|
||||
|
||||
- [ ] **Step 6: Тест на проброс `logVerdictImpl`**
|
||||
|
||||
```js
|
||||
// tools/enforce-llm-judge-per-tool.test.mjs — добавить
|
||||
it('runPerTool logs verdict only when a real judge call happened', async () => {
|
||||
const logged = [];
|
||||
const r = await runPerTool({
|
||||
event: { tool_name: 'Edit', session_id: 's', tool_input: {} },
|
||||
judgeConfig: { enabled: true, apiKey: 'k' },
|
||||
readDeclaredTaskImpl: () => ({ task_summary: 't' }),
|
||||
readBudgetImpl: () => 0,
|
||||
bumpBudgetImpl: () => {},
|
||||
llmJudgeCallImpl: async () => 'YES',
|
||||
logVerdictImpl: (rec) => logged.push(rec),
|
||||
});
|
||||
expect(r.block).toBe(false);
|
||||
expect(logged).toEqual([{ sessionId: 's', tool: 'Edit', verdict: 'YES' }]);
|
||||
});
|
||||
```
|
||||
|
||||
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/enforce-llm-judge-per-tool.test.mjs`
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add tools/enforce-hook-helpers.mjs tools/enforce-hook-helpers.test.mjs tools/enforce-llm-judge-per-tool.mjs tools/enforce-llm-judge-per-tool.test.mjs
|
||||
git commit -m "feat(brain): log per-tool judge verdicts to runtime jsonl"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Логирование любого safe-baseline action (incl hard_block)
|
||||
|
||||
**Files:**
|
||||
- Modify: `tools/enforce-hook-helpers.mjs`
|
||||
- Modify: `tools/enforce-safe-baseline-metering.mjs` (`runMain`, :195)
|
||||
- Test: `tools/enforce-hook-helpers.test.mjs`, `tools/enforce-safe-baseline-metering.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Тест на `logSafeBaselineAction`/`readSafeBaselineActions`**
|
||||
|
||||
```js
|
||||
// tools/enforce-hook-helpers.test.mjs
|
||||
import { logSafeBaselineAction, readSafeBaselineActions } from './enforce-hook-helpers.mjs';
|
||||
describe('safe-baseline action log', () => {
|
||||
it('appends and reads action records', () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'sb-'));
|
||||
logSafeBaselineAction('s', { tool: 'Edit', action: 'hard_block' }, { baseDir: dir });
|
||||
const recs = readSafeBaselineActions('s', { baseDir: dir });
|
||||
expect(recs[0]).toMatchObject({ tool: 'Edit', action: 'hard_block' });
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Прогнать — FAIL** (`logSafeBaselineAction is not a function`).
|
||||
|
||||
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/enforce-hook-helpers.test.mjs`
|
||||
|
||||
- [ ] **Step 3: Реализовать** (рядом с judge-логгером из Task 1)
|
||||
|
||||
```js
|
||||
export function logSafeBaselineAction(sessionId, { tool, action }, opts = {}) {
|
||||
try {
|
||||
const dir = opts.baseDir || runtimeDir();
|
||||
const f = join(dir, `safe-baseline-actions-${sessionId || 'unknown'}.jsonl`);
|
||||
appendFileSync(f, JSON.stringify({
|
||||
ts: new Date().toISOString(),
|
||||
tool: tool || 'unknown',
|
||||
action: String(action || 'allow'),
|
||||
}) + '\n');
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
export function readSafeBaselineActions(sessionId, opts = {}) {
|
||||
try {
|
||||
const dir = opts.baseDir || runtimeDir();
|
||||
const f = join(dir, `safe-baseline-actions-${sessionId || 'unknown'}.jsonl`);
|
||||
if (!existsSync(f)) return [];
|
||||
return readFileSync(f, 'utf-8').split('\n').filter(Boolean).map((l) => {
|
||||
try { return JSON.parse(l); } catch { return null; }
|
||||
}).filter(Boolean);
|
||||
} catch { return []; }
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Прогнать — PASS.**
|
||||
|
||||
- [ ] **Step 5: Вызвать из `enforce-safe-baseline-metering.mjs` `runMain`.** Заменить блок `if (res.action === 'soft_flag') logFlag(...)` на запись любого не-allow действия через новый логгер, сохранив старый `logFlag` для совместимости:
|
||||
|
||||
```js
|
||||
if (res.action !== 'allow') {
|
||||
logSafeBaselineAction(sess, { tool: event.tool_name, action: res.action });
|
||||
if (res.action === 'soft_flag') logFlag(dir, sess, { tool: event.tool_name, reason: res.reason });
|
||||
}
|
||||
```
|
||||
|
||||
Добавить `import { logSafeBaselineAction } from './enforce-hook-helpers.mjs';` (или дополнить существующий импорт из helpers). NB: `logSafeBaselineAction` сам резолвит `runtimeDir()` — в проде это совпадает с `dir`; для теста `runMain` принимает `runtimeDir` override, но логгер пишет в реальный runtime — поэтому в тесте инъектировать override не обязательно (проверяем экспортируемый логгер отдельно в Step 1).
|
||||
|
||||
- [ ] **Step 6: Регрессия safe-baseline.**
|
||||
|
||||
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/enforce-safe-baseline-metering.test.mjs`
|
||||
Expected: PASS (существующие тесты не трогают `action !== 'allow'` ветку с диском, либо обновить ожидания).
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add tools/enforce-hook-helpers.mjs tools/enforce-hook-helpers.test.mjs tools/enforce-safe-baseline-metering.mjs tools/enforce-safe-baseline-metering.test.mjs
|
||||
git commit -m "feat(brain): log every safe-baseline action incl hard_block"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Чистый ридер сигналов в окне хода — `observer-v4-signals.mjs`
|
||||
|
||||
**Files:**
|
||||
- Create: `tools/observer-v4-signals.mjs`
|
||||
- Test: `tools/observer-v4-signals.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Тест ридера**
|
||||
|
||||
```js
|
||||
// tools/observer-v4-signals.test.mjs
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { extractV4Signals } from './observer-v4-signals.mjs';
|
||||
|
||||
function write(dir, name, lines) { writeFileSync(join(dir, name), lines.map((l) => JSON.stringify(l)).join('\n') + '\n'); }
|
||||
|
||||
describe('extractV4Signals', () => {
|
||||
it('counts rationalization flags inside the turn window only', () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'v4-'));
|
||||
write(dir, 'rationalization-flags-s.jsonl', [
|
||||
{ ts: '2026-05-31T10:00:00.000Z', kind: 'x' }, // before window
|
||||
{ ts: '2026-05-31T10:05:00.000Z', kind: 'y' }, // in
|
||||
{ ts: '2026-05-31T10:06:00.000Z', kind: 'z' }, // in
|
||||
]);
|
||||
write(dir, 'llm-judge-verdicts-s.jsonl', [
|
||||
{ ts: '2026-05-31T10:05:30.000Z', tool: 'Edit', verdict: 'YES' },
|
||||
{ ts: '2026-05-31T10:05:40.000Z', tool: 'Bash', verdict: 'block' },
|
||||
]);
|
||||
write(dir, 'safe-baseline-actions-s.jsonl', [
|
||||
{ ts: '2026-05-31T10:05:10.000Z', tool: 'Edit', action: 'soft_flag' },
|
||||
{ ts: '2026-05-31T10:05:50.000Z', tool: 'Write', action: 'hard_block' },
|
||||
]);
|
||||
writeFileSync(join(dir, 'llm-judge-budget-s.json'), JSON.stringify({ calls: 7 }));
|
||||
const sig = extractV4Signals('s', {
|
||||
startMs: Date.parse('2026-05-31T10:04:00.000Z'),
|
||||
endMs: Date.parse('2026-05-31T10:07:00.000Z'),
|
||||
baseDir: dir,
|
||||
});
|
||||
expect(sig.rationalization_flag_count).toBe(2);
|
||||
expect(sig.judge_verdict).toBe('block'); // last in-window verdict
|
||||
expect(sig.safe_baseline_action).toBe('hard_block'); // worst in-window action
|
||||
expect(sig.judge_calls).toBe(7);
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
it('returns zero/null defaults when files absent', () => {
|
||||
const sig = extractV4Signals('nope', { startMs: 0, endMs: 1, baseDir: tmpdir() });
|
||||
expect(sig).toEqual({ rationalization_flag_count: 0, judge_verdict: null, safe_baseline_action: null, judge_calls: 0 });
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Прогнать — FAIL** (модуль не существует).
|
||||
|
||||
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/observer-v4-signals.test.mjs`
|
||||
|
||||
- [ ] **Step 3: Реализовать `observer-v4-signals.mjs`**
|
||||
|
||||
```js
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Pure reader of router-gate v4 runtime signals, scoped to one episode's
|
||||
* [startMs, endMs] turn window. Feeds observer-transcript-parser (new
|
||||
* episode.v4_signals block + task_cost.judge_spend_usd). No exec/exit.
|
||||
*/
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
function rtDir(override) { return override || join(homedir(), '.claude', 'runtime'); }
|
||||
|
||||
function readJsonl(path) {
|
||||
if (!existsSync(path)) return [];
|
||||
try {
|
||||
return readFileSync(path, 'utf-8').split('\n').filter(Boolean).map((l) => {
|
||||
try { return JSON.parse(l); } catch { return null; }
|
||||
}).filter(Boolean);
|
||||
} catch { return []; }
|
||||
}
|
||||
|
||||
function inWindow(rec, startMs, endMs) {
|
||||
const ms = Date.parse(rec && rec.ts);
|
||||
return Number.isFinite(ms) && ms >= startMs && ms <= endMs;
|
||||
}
|
||||
|
||||
// soft order: hard_block > soft_flag > allow.
|
||||
const ACTION_RANK = { allow: 0, soft_flag: 1, hard_block: 2 };
|
||||
function worstAction(records) {
|
||||
let best = null;
|
||||
for (const r of records) {
|
||||
const a = r && r.action;
|
||||
if (a == null) continue;
|
||||
if (best === null || (ACTION_RANK[a] ?? -1) > (ACTION_RANK[best] ?? -1)) best = a;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
export function extractV4Signals(sessionId, { startMs, endMs, baseDir } = {}) {
|
||||
const dir = rtDir(baseDir);
|
||||
const sess = sessionId || 'unknown';
|
||||
|
||||
const flags = readJsonl(join(dir, `rationalization-flags-${sess}.jsonl`))
|
||||
.filter((r) => inWindow(r, startMs, endMs));
|
||||
|
||||
const verdicts = readJsonl(join(dir, `llm-judge-verdicts-${sess}.jsonl`))
|
||||
.filter((r) => inWindow(r, startMs, endMs));
|
||||
const judge_verdict = verdicts.length ? (verdicts[verdicts.length - 1].verdict ?? null) : null;
|
||||
|
||||
const actions = readJsonl(join(dir, `safe-baseline-actions-${sess}.jsonl`))
|
||||
.filter((r) => inWindow(r, startMs, endMs));
|
||||
const safe_baseline_action = worstAction(actions);
|
||||
|
||||
let judge_calls = 0;
|
||||
const bp = join(dir, `llm-judge-budget-${sess}.json`);
|
||||
if (existsSync(bp)) {
|
||||
try { judge_calls = Number(JSON.parse(readFileSync(bp, 'utf-8')).calls) || 0; } catch { /* keep 0 */ }
|
||||
}
|
||||
|
||||
return {
|
||||
rationalization_flag_count: flags.length,
|
||||
judge_verdict,
|
||||
safe_baseline_action,
|
||||
judge_calls,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Прогнать — PASS.**
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add tools/observer-v4-signals.mjs tools/observer-v4-signals.test.mjs
|
||||
git commit -m "feat(brain): pure reader for v4 runtime signals in turn window"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Внести сигналы в эпизод (`parseTranscript`)
|
||||
|
||||
**Files:**
|
||||
- Modify: `tools/observer-transcript-parser.mjs` (:888 return object)
|
||||
- Test: `tools/observer-transcript-parser.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Тест — эпизод несёт `v4_signals` и `task_cost.judge_spend_usd`**
|
||||
|
||||
```js
|
||||
// tools/observer-transcript-parser.test.mjs — добавить
|
||||
it('episode carries v4_signals block (default zero/null when no runtime files)', () => {
|
||||
const ep = parseTranscript(JSON.stringify({
|
||||
sessionId: 'no-runtime-sess',
|
||||
message: { role: 'user', content: 'сделай фичу X' },
|
||||
timestamp: '2026-05-31T10:00:00.000Z',
|
||||
}), null, { routerStateBaseDir: tmpdir() });
|
||||
expect(ep.v4_signals).toEqual({
|
||||
rationalization_flag_count: 0, judge_verdict: null, safe_baseline_action: null, judge_calls: 0,
|
||||
});
|
||||
expect(typeof ep.task_cost.judge_spend_usd).toBe('number');
|
||||
expect(ep.schema_minor).toBe(4);
|
||||
});
|
||||
```
|
||||
|
||||
(`tmpdir` импортировать в тест-файле, если ещё не.)
|
||||
|
||||
- [ ] **Step 2: Прогнать — FAIL** (`ep.v4_signals` undefined).
|
||||
|
||||
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/observer-transcript-parser.test.mjs`
|
||||
|
||||
- [ ] **Step 3: Реализовать.** В `observer-transcript-parser.mjs`:
|
||||
|
||||
(а) импорт вверху рядом с другими: `import { extractV4Signals } from './observer-v4-signals.mjs';` и `import { JUDGE_PER_CALL_USD } from './cost-pricing.mjs';`
|
||||
|
||||
(б) в `parseTranscript`, после вычисления `started_at`/`ended_at` (:847), добавить:
|
||||
|
||||
```js
|
||||
const _v4 = extractV4Signals(sessionId, {
|
||||
startMs: new Date(started_at).getTime(),
|
||||
endMs: new Date(ended_at).getTime(),
|
||||
baseDir: options.runtimeBaseDir || routerStateBaseDir,
|
||||
});
|
||||
```
|
||||
|
||||
(в) bump `schema_minor: 3` → `schema_minor: 4` в return.
|
||||
|
||||
(г) в return-объект добавить поле `v4_signals: _v4,`.
|
||||
|
||||
(д) в `task_cost` влить судейский spend: заменить строку `task_cost: { ...extractTokenUsage(turn), ...((routerState && routerState.task_cost) || {}) },` на:
|
||||
|
||||
```js
|
||||
task_cost: {
|
||||
...extractTokenUsage(turn),
|
||||
...((routerState && routerState.task_cost) || {}),
|
||||
judge_spend_usd: _v4.judge_calls * JUDGE_PER_CALL_USD,
|
||||
},
|
||||
```
|
||||
|
||||
NB: `routerStateBaseDir` уже извлекается из `options` (:838). `extractV4Signals` принимает тот же baseDir — в проде это `~/.claude/runtime`, в тестах — temp.
|
||||
|
||||
- [ ] **Step 4: Прогнать — PASS** (после Task 5, где появится `JUDGE_PER_CALL_USD`). Если выполняется до Task 5 — импорт `JUDGE_PER_CALL_USD` упадёт; **зависимость: Task 5 Step 3 делать до Task 4 Step 3** (или внести константу в этом же шаге). Для линейного исполнения: добавить `JUDGE_PER_CALL_USD` в `cost-pricing.mjs` здесь же (Step 3.е) и продублировать тест в Task 5.
|
||||
|
||||
(3.е) В `tools/cost-pricing.mjs` добавить экспорт:
|
||||
|
||||
```js
|
||||
// Оценочная средняя стоимость одного вызова per-tool судьи (Sonnet, ~600 in / ~10 out
|
||||
// токенов на короткий YES/NO-промпт). Уточнить, когда появится по-вызовный учёт токенов.
|
||||
export const JUDGE_PER_CALL_USD = 600 * PRICING.sonnet.input + 10 * PRICING.sonnet.output;
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Прогнать целевой тест — PASS.**
|
||||
|
||||
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/observer-transcript-parser.test.mjs`
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add tools/observer-transcript-parser.mjs tools/observer-transcript-parser.test.mjs tools/cost-pricing.mjs
|
||||
git commit -m "feat(brain): surface v4_signals + judge_spend_usd into episode (schema_minor 4)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Денежный компонент `judge_spend_usd` в агрегаторе и cost-daily
|
||||
|
||||
**Files:**
|
||||
- Modify: `tools/cost-aggregator.mjs` (`episodeUsd` :13, `aggregateDay` :40)
|
||||
- Test: `tools/cost-aggregator.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Тест — `episodeUsd` отдаёт `judge_spend_usd`, `total_usd` его включает**
|
||||
|
||||
```js
|
||||
// tools/cost-aggregator.test.mjs
|
||||
it('includes judge_spend_usd in episodeUsd + total', () => {
|
||||
const pricing = { sonnet: { input: 3e-6, output: 15e-6 }, opus: { input: 15e-6, output: 75e-6 } };
|
||||
const ep = { task_cost: { judge_spend_usd: 0.002 } };
|
||||
const u = episodeUsd(ep, pricing);
|
||||
expect(u.judge_spend_usd).toBe(0.002);
|
||||
expect(u.total_usd).toBeCloseTo(0.002, 9);
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Прогнать — FAIL** (`u.judge_spend_usd` undefined).
|
||||
|
||||
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/cost-aggregator.test.mjs`
|
||||
|
||||
- [ ] **Step 3: Реализовать.** В `episodeUsd` (:13) добавить строку и включить в `total_usd` + объект:
|
||||
|
||||
```js
|
||||
const judge_spend_usd = tc.judge_spend_usd || 0;
|
||||
const total_usd =
|
||||
classifier_usd + self_assessment_usd + reviewer_subagent_usd +
|
||||
reviewer_direct_fallback_usd + self_retrospect_usd + judge_spend_usd;
|
||||
return {
|
||||
classifier_usd, self_assessment_usd, reviewer_subagent_usd,
|
||||
reviewer_direct_fallback_usd, self_retrospect_usd, judge_spend_usd, total_usd,
|
||||
};
|
||||
```
|
||||
|
||||
В `aggregateDay` (:40) добавить `judge_spend_usd: 0` в `totals`, прибавлять `totals.judge_spend_usd += u.judge_spend_usd;` в цикле и включить в финальный `total_usd`.
|
||||
|
||||
- [ ] **Step 4: Прогнать — PASS** + регрессия `cost-stop-hook`:
|
||||
|
||||
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/cost-aggregator.test.mjs tools/cost-stop-hook.test.mjs`
|
||||
Expected: PASS (cost-daily.json теперь получит поле `judge_spend_usd` автоматически — оно идёт из `aggregateDay`; при необходимости обновить ожидания в `cost-stop-hook.test.mjs`).
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add tools/cost-aggregator.mjs tools/cost-aggregator.test.mjs tools/cost-stop-hook.test.mjs
|
||||
git commit -m "feat(brain): add judge_spend_usd cost component to aggregator + cost-daily"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Четыре новые факторные оси
|
||||
|
||||
**Files:**
|
||||
- Modify: `tools/brain-retro-analyzer.mjs` (`FACTOR_FNS` :326)
|
||||
- Test: `tools/brain-retro-analyzer.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Тест — матрица содержит новые оси**
|
||||
|
||||
```js
|
||||
// tools/brain-retro-analyzer.test.mjs
|
||||
import { buildFactorMatrix } from './brain-retro-analyzer.mjs';
|
||||
it('factor matrix exposes v4-signal axes', () => {
|
||||
const eps = [
|
||||
{ _inferredOutcome: 'success', v4_signals: { rationalization_flag_count: 0, judge_verdict: 'YES', safe_baseline_action: 'allow', judge_calls: 0 }, task_cost: { judge_spend_usd: 0 } },
|
||||
{ _inferredOutcome: 'rework', v4_signals: { rationalization_flag_count: 3, judge_verdict: 'block', safe_baseline_action: 'hard_block', judge_calls: 12 }, task_cost: { judge_spend_usd: 0.03 } },
|
||||
];
|
||||
const m = buildFactorMatrix(eps);
|
||||
expect(m.rationalization_flag_count['0']).toEqual({ success: 1 });
|
||||
expect(m.rationalization_flag_count['2+']).toEqual({ rework: 1 });
|
||||
expect(m.judge_verdict.YES).toEqual({ success: 1 });
|
||||
expect(m.judge_verdict.block).toEqual({ rework: 1 });
|
||||
expect(m.safe_baseline_action.hard_block).toEqual({ rework: 1 });
|
||||
expect(m.judge_calls_bucket['0']).toEqual({ success: 1 });
|
||||
expect(m.judge_calls_bucket['10+']).toEqual({ rework: 1 });
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Прогнать — FAIL** (осей нет).
|
||||
|
||||
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/brain-retro-analyzer.test.mjs`
|
||||
|
||||
- [ ] **Step 3: Реализовать.** В `FACTOR_FNS` (:326) добавить (используя существующий `countBucket012` :321 для 0/1/2+ и новый локальный bucket для judge_calls):
|
||||
|
||||
```js
|
||||
// Pass 5 — router-gate v4 signal axes (brain-data-catalog раздел F → факторы).
|
||||
rationalization_flag_count: (e) => countBucket012((e.v4_signals || {}).rationalization_flag_count),
|
||||
judge_verdict: (e) => (e.v4_signals || {}).judge_verdict || 'null',
|
||||
safe_baseline_action: (e) => (e.v4_signals || {}).safe_baseline_action || 'null',
|
||||
judge_calls_bucket: (e) => judgeCallsBucket((e.v4_signals || {}).judge_calls),
|
||||
```
|
||||
|
||||
И рядом с другими bucket-хелперами (около :321) добавить:
|
||||
|
||||
```js
|
||||
function judgeCallsBucket(n) {
|
||||
const v = Number(n) || 0;
|
||||
if (v === 0) return '0';
|
||||
if (v < 10) return '1-9';
|
||||
return '10+';
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Прогнать — PASS** + полная регрессия анализатора:
|
||||
|
||||
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/brain-retro-analyzer.test.mjs`
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add tools/brain-retro-analyzer.mjs tools/brain-retro-analyzer.test.mjs
|
||||
git commit -m "feat(brain): add 4 v4-signal factor axes (rationalization/judge/safe-baseline)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Обновить каталог данных + полная регрессия tools
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/observer/brain-data-catalog.md`
|
||||
|
||||
- [ ] **Step 1: Перенести сигналы из раздела F.** В `brain-data-catalog.md`: в A.7 добавить `judge_spend_usd` в список `task_cost`; добавить новую группу **A.12 `v4_signals`** (`rationalization_flag_count`, `judge_verdict`, `safe_baseline_action`, `judge_calls`); в раздел B дописать «Pass 5 (4 оси): rationalization_flag_count / judge_verdict / safe_baseline_action / judge_calls_bucket»; в разделе F пометить F.1–F.3 как «✅ заведено в эпизод (см. A.12)», оставив F.4/F.5 как есть; bump «схема v4.3» → «v4.4 (schema_minor 4)».
|
||||
|
||||
- [ ] **Step 2: Полная регрессия tools-only**
|
||||
|
||||
Run: `npx vitest run --root app --config vitest.config.tools.mjs`
|
||||
Expected: все GREEN (новые тесты + прежний baseline без падений).
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/observer/brain-data-catalog.md
|
||||
git commit -m "docs(observer): catalog — v4 signals promoted from F into episode + factors"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Self-Review
|
||||
|
||||
**1. Покрытие требований (кандидаты F из каталога):**
|
||||
- `rationalization_flag_count` → Task 3 (ридер) + Task 4 (поле) + Task 6 (ось). ✅
|
||||
- `safe_baseline_action` → Task 2 (логирование) + Task 3 + Task 4 + Task 6. ✅
|
||||
- `judge_verdict` → Task 1 (логирование) + Task 3 + Task 4 + Task 6. ✅
|
||||
- `judge_spend_usd` → Task 4 (поле, из `judge_calls × JUDGE_PER_CALL_USD`) + Task 5 (агрегатор/cost-daily). ✅
|
||||
- `judge_calls` → Task 3 (бюджет) + Task 6 (ось `judge_calls_bucket`). ✅
|
||||
|
||||
**2. Заглушки:** код в каждом шаге полный; команды и ожидания указаны. `JUDGE_PER_CALL_USD` — явная оценочная константа с комментарием (не TODO-заглушка), уточняемая, когда появится по-вызовный учёт токенов.
|
||||
|
||||
**3. Согласованность типов/имён:**
|
||||
- `extractV4Signals(sessionId, {startMs,endMs,baseDir})` → возвращает `{rationalization_flag_count, judge_verdict, safe_baseline_action, judge_calls}` — те же ключи в Task 3 тесте, Task 4 поле `v4_signals`, Task 6 осях. ✅
|
||||
- `logJudgeVerdict`/`readJudgeVerdicts`, `logSafeBaselineAction`/`readSafeBaselineActions` — пары имён согласованы между Task 1/2 и Task 3 (файлы `llm-judge-verdicts-<sess>.jsonl`, `safe-baseline-actions-<sess>.jsonl`). ✅
|
||||
- `judge_spend_usd` — одно имя в Task 4 (запись), Task 5 (агрегатор), каталоге. ✅
|
||||
- **Зависимость порядка:** Task 5 Step 3 (или Task 4 Step 3.е) должен внести `JUDGE_PER_CALL_USD` в `cost-pricing.mjs` ДО импорта в `observer-transcript-parser.mjs`. Зафиксировано в Task 4 Step 4 примечанием.
|
||||
|
||||
**4. Риски/допущения, проверить при исполнении:**
|
||||
- `observer-transcript-parser` читает runtime по абсолютному `baseDir` (прод `~/.claude/runtime`); тесты инъектируют temp через `options.runtimeBaseDir || routerStateBaseDir`.
|
||||
- `judge_calls` — кумулятивно per-session (бюджет не сбрасывается per-turn), поэтому `judge_calls_bucket` отражает нагрузку сессии, не хода; это осознанно (для USD это и нужно). Если потребуется per-turn — считать вердикты в окне (`verdicts.length`), отдельная ось.
|
||||
- `enforce-llm-judge-per-tool` логирует вердикт только при реальном вызове (`result.verdict !== undefined`) — disabled/readonly/test-runner не пишут (verdict отсутствует) → `judge_verdict` останется `null`, что корректно отражает «судья не сработал».
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,374 @@
|
||||
# Роутер-наставник — мастер-карта стройки (координирующий план)
|
||||
|
||||
> **Для исполнителя:** это **карта**, а не пошаговый чертёж. Она задаёт 7 машин, строгий порядок их сборки, зависимости и точки стыковки. Детальные TDD-планы (task-by-task, REQUIRED SUB-SKILL `superpowers:subagent-driven-development`) пишутся **отдельно по каждой машине** на основе этой карты — по одному, в порядке снизу вверх.
|
||||
|
||||
**Цель:** заменить нынешний «зоопарк» из ~12 живых блокеров одной цельной конструкцией «план → роутер-наставник → судья → владелец», где **ни одно действие не исполняется без шага замороженного плана, прошедшего судью**, при этом детерминированная защита-пол остаётся независимым слоем.
|
||||
|
||||
**Архитектура:** семь машин друг на друге. Низ — неподделываемый фундамент (журнал действий + подписанные расписки + защита секретов/служебки). Над ним — стена (верховный хук default-deny на ВСЁ) и замок плана (хеш-печать). Сбоку у стены — голова (роутер-наставник). На воротах — судья A–I (безопасный отказ, не переопределяет пол). По бокам — укрепление существующей защиты и переделка дисциплины. Сверху — видимость и поэтапное «тихое» включение (D28).
|
||||
|
||||
> **СТАТУС РЕВЬЮ 2026-06-04:** Машина 1 (Фундамент) — **✅ СОБРАНА и проверена по коду** (переделка не нужна, см. её раздел). Машина 2 — идёт построчное ревью решений A–G (владелец ведёт). **Закрыто:** A (структура шага), B (строгий матч), **C (C-1…C-14: два плана / две печати / закрытая дверь / контракты+охват / апелляция)** и **L-серия** (меньше итераций: L1/L3/L4/L5/L7; L2/L6 в утиль; принцип «умный не баран»). **Закрыто также:** D (матчер `*` + зелёный проход строго по способности — семена + только-смотрящие, никогда мутирующий; под аудитом дверей), E (указатель шага — линейный сейчас, «волны» → Машина 3), F (авто-аудит дверей + страховка «зелёного прохода» к D), G (сверка план↔журнал постфактум). **ВСЕ A–G + L закрыты — Машина 2 готова к плану сборки.** Детали дельты C/L — в разделе **«НАДСТРОЙКА C/L»** ниже + в чертеже Машины 2. Код Машин 2+ ещё НЕ строим — сначала владелец подтверждает D–G.
|
||||
|
||||
**Источники истины (spec):**
|
||||
- `docs/superpowers/specs/2026-06-01-router-discipline-plan-anchor-design-notes.md` — логика план→роутер→судья→владелец (D1–D36, OQ1–OQ7, судья A–I, снаряжение роутера 6 групп / 24 пункта).
|
||||
- `docs/superpowers/specs/2026-06-01-router-mentor-hooks-decisions.md` — хуки: 26-пунктная карта покрытия + два прохода усилений + «ПРЕД-СПЕКА #1 — чистовой список 🟥/🟩/🟨/🆕».
|
||||
|
||||
**Технологии:** Node.js хуки `tools/enforce-*.mjs` (PreToolUse/PostToolUse/Stop), Sonnet/Haiku/Opus как судьи/роутер через `callAnthropicAPI` (кэш промпта), `audit_chain_hash`-паттерн PostgreSQL для журнала, HMAC-ключ из OS-keychain (`keytar`), Pest 4 mutation для проверки тестов, vitest tools-only для регрессии хуков. **Портативность — жёсткое требование: никакого project-specific хардкода; судья/роутер РАССУЖДАЮТ, не читают зашитые списки.**
|
||||
|
||||
---
|
||||
|
||||
## СКВОЗНЫЕ НЕСУЩИЕ ПРИНЦИПЫ (держат всю конструкцию — закладываются в фундамент, используются всеми машинами)
|
||||
|
||||
1. **Журнал S1 — полный и неподделываемый.** Лог пишется **ДО** действия (нет записи → действие не исполняется, P21-h); append-only хеш-цепочка (как `audit_chain_hash`, стереть/править задним числом нельзя, P21-k); первичен над моими словами (что сделано — из журнала, мой отчёт сверяется, P21-e). Под него легли: честность (п.21), вход судьи (J7), результат субагента (п.13), сверка-аудит по сессии (п.25).
|
||||
2. **Замороженный план (хеш-печать, P15-f).** После одобрения план «запечатан»; правка = новое одобрение, видимое владельцу. Без этого «в плане» — фикция (я переписал бы план под действие). Закрывает circularity «дисциплину собираю я сам».
|
||||
3. **Судья НЕ переопределяет детерминированный пол (J9).** «Да» судьи не отпирает тонкий пол/верховный хук — судья только блокирует ИЛИ пропускает к детерм-слоям. Обманутый судья = ограниченный ущерб (не авторизует force-push/секрет/необратимое), не катастрофа.
|
||||
4. **Машинная правда владельцу при одобрении (E10 / P4-b / J10).** Окно «да» показывает риск/последствие **машинно из фактов действия**, не из моей формулировки. Опасное нельзя подать мягкими словами. Против предела «обманули владельца».
|
||||
5. **Подпись расписок ключом-вне-меня (P10-c, HMAC из OS-keychain).** Расписки судьи/владельца/GREEN подписаны; взлома служебной папки **мало** — нужен ключ, мне недоступный.
|
||||
6. **Матчер-«всё» (`*`), не список имён.** Ворота судят по СУТИ действия, не по имени инструмента (урок F1: следили за Bash → PowerShell мимо). Новый канал → закрыт сам, пока осознанно не впишут. + авто-аудит покрытия дверей (P15-b).
|
||||
7. **Портативность.** Что project-specific — берётся живьём из реального состояния, не вшивается. Зашитой «карты критического» НЕТ — судья рассуждает.
|
||||
|
||||
> **О счёте несущих (во избежание путаницы трёх разных счётчиков):** выше — **7 принципов** (высокий уровень). Конкретных несущих **механизмов**, которыми эти принципы держатся, — **10**; их поимённый список с привязкой к машинам — в коверидж-таблице «10 несущих механизмов → машина» внизу. Отдельно «**7 пределов**» (абзац ниже) — это про честный пол ~0.5% (предел защиты), а НЕ про несущие. Три счёта — **7 принципов / 10 механизмов / 7 пределов** — разные, не путать.
|
||||
|
||||
**Честный пол ~0.5% (7 пределов, неустраним по конструкции, заложен сознательно):** recursive-trust (я — соавтор плана и частично вход судьи) · user-approval (суверена можно обмануть, E10 сужает) · физ-канал без хуков (#15) · субъективная оценка качества · эфемерная негейтнутая мысль (#22) · cross-session дробление · info-theoretic утечка. Усиления подняли стены, пол не убрали.
|
||||
|
||||
**Режим внедрения (D28, общий для всех машин):** чертёж → сборка по TDD **в стороне, не вживую** → **тихий режим** (новые гейты логируют, что бы заблокировали/эскалировали, реально НЕ блокируют — **только дисциплинарные ворота; защита-пол (секреты/runtime/force-push) включена сразу, не «тихо»**) → подкрутка порогов по реальным данным → **включение по одному** (сперва мутации, потом чтения). Живая система во время стройки не ломается. Две сегодняшние починки кода (`max_tokens` 1500→15000, fix `task_type`) независимы — выкатываемы сразу.
|
||||
|
||||
---
|
||||
|
||||
## НАДСТРОЙКА C/L (ревью 2026-06-04) — что изменилось в конструкции
|
||||
|
||||
> Этот раздел — **актуальный слой поверх** детальных разделов машин ниже (они написаны ДО ревью C/L и остаются верны как фундамент; здесь — дельта). Закрыто на ревью: **A, B, C (C-1…C-14), L-серия, D** (матчер `*` + зелёный проход по способности), **E** (указатель шага линейный; «волны» → Машина 3), **F** (авто-аудит дверей + страховка «зелёного прохода»), **G** (сверка план↔журнал постфактум). **Все A–G + L закрыты 2026-06-04.**
|
||||
|
||||
**Главное изменение — план расщепился на ДВА (C-7):**
|
||||
1. **Разговорный план** (думательный): цель + только семена (думать/спрашивать, реализация запрещена) + обязательный артефакт-результат и условие «готово». Последовательность эмерджентна (следующий вопрос рождается из ответа). Защита в этом окне — НЕ печать (её ещё нет), а запрет инструментов реализации + всегда-включённый пол (секреты/runtime/force-push). Один разговорный план на фазу, внутри — петля до готовности.
|
||||
2. **Реализационный план** (строящий): фиксирован, опечатан заранее, строгий матч (A/B), каждый шаг ссылается на опечатанное решение артефакта.
|
||||
|
||||
**Две печати / два гейта судьи (C-10):**
|
||||
- **Гейт-1** (после разговора) — судим ПРОДУКТ разговора: артефакт полон/здоров? Вход судьи = артефакт + контракты вызываемых скилов + цель (слеп к живой болтовне, артефакты-документы читать может). GO → печать артефакта → к плану стройки. Ловит «мусор на входе» РАНО.
|
||||
- **Гейт-2** (после реализационного плана) — судим ПЛАН: верно применяет опечатанный артефакт, строгий, ссылки §X резолвятся? GO → печать плана → стройка.
|
||||
- Асимметрия честная: план судим ДО (фиксирован), разговор — ПОСЛЕ (эмерджентен → судим продукт-артефакт).
|
||||
|
||||
**Закрытая дверь (C-5):** на стройке решать на ходу нельзя. (а) исполнение применяет ТОЛЬКО опечатанные решения; (б) шаг реализации ОБЯЗАН ссылаться на конкретное решение артефакта («крашу в цвет §X»), ссылка не резолвится → стоп; (в) нужно решение, которого нет → единственный легальный ход — возврат в разговор (семя) → пере-печать. Хода «спросил на ходу и продолжил» физически нет → петля обязательна.
|
||||
|
||||
**Контракт скила {needs/produces/constraints} (C-13) + L-поля (L-серия):** скил объявляет про себя машинно-читаемо. Базовый контракт `needs / produces / constraints`, расширен L-полями: `preview-form` (L1 — чем дёшево показать образец; `none` для мелочи), `defaults` (L3 — разумный дефолт под нужду, СВЕЖИЙ под задачу, не штамп), `key-decisions` (L4 — что фундаментально), `acceptance-criteria` (L7 — как поймём, что готово). На контракте проверки полноты/охвата = операции над множествами и графом (механика, не «ум ИИ»). Свои скилы — пишем контракт сами (точно); чужие (маркетплейс) — тонкий файл-адаптер рядом, гард-рейлы G1–G6, ключевой **G4: хеш-страж дрейфа** (версия+хеш чужого скила; расхождение → громкий флаг + откат на мягкое рассуждение судьи).
|
||||
|
||||
**Машина охвата A/B/C/D поверх контрактов (C-14):** A — граф зависимостей (узлы=скилы, рёбра=needs↔produces; декомпозиция=связные группы, порядок=топосортировка, дыра=нужда без producer, цикл=флаг); B — двусторонний реестр нужды↔решения (дыра / сирота-скоупкрип); C — чек-лист «просьбы цели → план» (мягкий край на извлечении просьб); D — ограничения как полноправные нужды. Хребет — буквальный чек-лист готовности (галочки + указатели §). Прецеденты прошлых планов — advisory, не гейт. + **look-ahead**: при сборке плана роутер читает контракты вызываемых скилов и тащит их вопросы ВПЕРЁД, в разговорную фазу.
|
||||
|
||||
**Апелляционный цикл (C-12):** деньги → сразу к владельцу; структурные замечания (нет §, ссылка не резолвится, нужда не покрыта, матч не сошёлся) → судья мотивированно назад ко мне → правлю (сначала сам, потом роутер) → роутер возвращает СУХОЕ (без мотиваций/диалогов) → судья пере-судит; **цикл до 3 раз, не прошло → к владельцу**; суть/дух → сразу к владельцу. Счётчик раундов неподделываемый (журнал М1), канал сухой (анти-инъекция), классификация «деньги» детерминированно-осторожная. Применяется на ОБОИХ гейтах.
|
||||
|
||||
**L-серия (меньше итераций, универсально):** L1 дешёвый образец до полной стройки (ОБЯЗАТЕЛЕН при `preview-form ≠ none`) · L3 предложи-и-вето (дефолт на вето вместо открытого вопроса) · L4 порядок решений по влиянию (фундамент раньше мелочи) · L5 ограничения-как-пре-мортем · L7 критерий приёмки заранее (объективное — авто-проверка, вкусовое — глаз владельца). Отклонены L2 (референсы, не универсально) и L6 (скелет-прецедент — «баранит», полноту лучше даёт граф A + look-ahead). **Принцип «умный не баран»:** половина ПЛАНИРОВАНИЯ/РАССУЖДЕНИЯ (роутер/разведка/декомпозиция) думает свежо каждую задачу, никаких шаблонов-решений; половина ЗАЩИТЫ/ПРИНУЖДЕНИЯ (стена/гейт/судья/пол) остаётся тупо-механической (тупой гейт нельзя уболтать).
|
||||
|
||||
**Куда садятся изменения (по машинам — детальные разделы ниже надстраиваются этим):**
|
||||
- **Контракт скила** (новый сквозной артефакт) — общий вход для М3/М4; защищён от само-ослабления (убрал нужду/критерий/расширил дефолт → блок + да владельца + diff; анти-отрава #9/#11). Дом схемы — общий; чтение — М3/М4.
|
||||
- **Машина 2 (стена):** ДВА режима — разговорный (только семена, артефакт обязателен, матча нет) + реализационный (строгий матч + закрытая дверь); ВТОРАЯ печать — заморозка артефакта (гейт-1), рядом с заморозкой плана; резолвер закрытой двери (шаг ↔ опечатанный §). Детально — в чертеже Машины 2 (Tasks 10–12, дефолты `[РЕШЕНИЕ H–K]`).
|
||||
- **Машина 3 (роутер):** look-ahead + декомпозиция графом A из контрактов + L-ядро (дефолты L3 / порядок по влиянию L4 / образец L1 / ограничения L5 / критерии L7) + адаптер чужих скилов с G4.
|
||||
- **Машина 4 (судья):** ДВА гейта (артефакт + план) + машина охвата A/B/C/D на гейте-1 + апелляционный цикл (3 раунда сухой петли) + 4 входа (план/артефакт/цель/контракты) + L7 на «готово» (объективные критерии прошли машинно).
|
||||
- **Защитный пол (М1/М5):** почти не тронут; добавлены типы записей runtime (вторая печать + счётчик апелляции) + защита контрактов под анти-отраву.
|
||||
|
||||
**Открытость закона (C-11):** структурные законы (матч, «шаг ссылается на §», полнота нужд, защиты, семена) — публикуются целиком (как СНиП: знание помогает честному, не помогает обходчику). Судейские законы (рассуждение план↔цель, артефакт↔нужды): критерий открыт, вердикт защищён (слепота к уговорам + независимость + консенсус). Принцип «нет защиты через секретность».
|
||||
|
||||
**Предел честности (C-9):** конструкция полна настолько, насколько точно объявлены/выведены нужды скилов. Свои скилы — точно (свой контракт); чужие — мягче (судья выводит из текста рецепта, остаточный риск). Машина держит «наличие/происхождение решения» (прошло ли диалог+печать), но не «применили ли именно его значение/вкус» — это владелец (граница C-6/B). Добивает владелец.
|
||||
|
||||
---
|
||||
|
||||
## ПОРЯДОК СБОРКИ (строгий, по зависимостям)
|
||||
|
||||
```
|
||||
Машина 1 (Фундамент) ──┬─→ Машина 2 (Замок плана + Верховный хук)
|
||||
│ │
|
||||
│ ├─→ Машина 3 (Роутер-наставник)
|
||||
│ │ │
|
||||
│ │ └─→ Машина 4 (Судья A–I)
|
||||
│ │
|
||||
├─→ Машина 5 (Укрепление защиты-пола 🟩)
|
||||
│
|
||||
└────────┴──→ Машина 6 (Переделка дисциплины + чистка 🟨/🟥)
|
||||
│
|
||||
Машина 7 (Видимость + теневой режим + включение) — поверх всего
|
||||
```
|
||||
|
||||
**Правило порядка:** ничего из 2–7 не строится, пока Машина 1 (фундамент) не готова и не проверена. Машина 4 (судья) при сборке требует 1+2 + **граф-из-реестра** (фундаментный артефакт, НЕ готовый роутер); с роутером (М3) стыкуется на runtime. **Защитная часть Машины 6** (тонкий пол / escape-защита / авто-снимок) стоит ТОЛЬКО на М1 и строится сразу после фундамента, параллельно 2–4. Машина 7 (включение) — последняя и постепенная.
|
||||
|
||||
---
|
||||
|
||||
## МАШИНА 1 — ФУНДАМЕНТ (пол, на котором стоит всё)
|
||||
|
||||
**Одной фразой:** неподделываемая основа — журнал действий, подписанные расписки, защита секретов и служебки, честный приёмщик «да».
|
||||
|
||||
**На чём стоит:** ни на чём (основание). **Строится ПЕРВОЙ.**
|
||||
|
||||
> **✅ СОБРАНА (2026-06-04, ветка `worktree-brainrepo`).** Код-коммиты: `d7dc0327` receipt-key-config · `9bd45ce5` receipt-sign (HMAC) · `e3da14a7` action-journal (журнал S1) · `3af57e18` askuser signed receipts · `56da7fab` pathNormalize NFC · `7af68d62` runtime-write-deny (все каналы) · `b83cea2e` cross-инварианты. Регрессия 2035 GREEN. **Переделка НЕ нужна (проверено по коду):** `signPayload`/`verifyReceipt` подписывают/проверяют ЛЮБОЙ объект; `journalAppend` принимает ЛЮБОЙ payload + хеш-цепочка + HMAC-голова. → две печати / счётчик апелляции / записи приёмки = новые НАГРУЗКИ на тех же примитивах (хуки Машин 2/4 импортируют М1, не правят). Ноль переделок, только надстройка.
|
||||
|
||||
**Что входит:**
|
||||
- **Журнал S1** (🆕, несущий №1, TO-BUILD №4) — append-only хеш-цепочка, лог-до-действия, первичен над словами. Базовый паттерн — `audit_chain_hash` проекта.
|
||||
- **Подпись расписок HMAC** (🆕, несущий №5, P10-c) — ключ из OS-keychain (`keytar`), мне недоступен. Все расписки (судья/владелец/GREEN) подписаны.
|
||||
- **runtime-write-deny** (🟩, п.10, существует) — нельзя писать в `~/.claude/runtime` (расписки). +все-каналы записи (`echo>`/`cp`/`mv`/MCP/помощник, матчер-«всё», P10-a) +нормализация пути (symlink/hardlink/8.3/NFC, P10-b).
|
||||
- **read-path-deny** (🟩, п.8, существует) — нельзя читать чужие транскрипты `.jsonl` / `~/.claude/runtime` / настройки / `.env`. Базовый узкий список (over-block fix v2.43 сохранить — CLAUDE.md/нормативку/память НЕ блокировать).
|
||||
- **askuser-answer-parser** (🟩-фундамент, п.4/26, существует) — честный приёмщик: ловит ТВОЙ ответ в окне AskUser, пишет официальную расписку «владелец разрешил X на 5 мин» в подписанную служебку. Расписку пишет КОД из твоего ответа, не я.
|
||||
|
||||
**Инвариант (НИКОГДА не убирать, Р2):** без любого из {journal, signed receipts, runtime-write-deny, read-path-deny, askuser-parser} судью можно подделать → вся конструкция рушится. Это краеугольные камни.
|
||||
|
||||
**Как проверяется отдельно:** журнал — записать действие до исполнения, попытаться исполнить без записи → блок; попытаться стереть/подменить запись задним числом → отказ цепочки. Расписка — подделать без HMAC-ключа → отвергнута. runtime-write-deny — попытка записи в `~/.claude/runtime` любым каналом → блок. read-path-deny — Read `.env`/`.jsonl` → блок, Read CLAUDE.md/память → проходит.
|
||||
|
||||
**Точки стыковки (что отдаёт наружу):**
|
||||
- Журнал S1 → читают Машины 2 (сверка план↔след), 4 (вход судьи J7), 5 (неподделываемый GREEN/RED), 6 (счётчики escape/оправданий).
|
||||
- Подписанные расписки → пишет askuser-parser, читают верховный хук (М2) и судья (М4).
|
||||
- Защита-пол (runtime/read) → действует под всеми остальными машинами независимо.
|
||||
|
||||
---
|
||||
|
||||
## МАШИНА 2 — ЗАМОК ПЛАНА + ВЕРХОВНЫЙ ХУК (стена)
|
||||
|
||||
**Одной фразой:** план «замораживается» печатью; и главная стена — ни одно действие без шага замороженного плана, на любой инструмент.
|
||||
|
||||
**На чём стоит:** Машина 1 (журнал + расписки).
|
||||
|
||||
**Что входит:**
|
||||
- **Замороженный план** (🆕, несущий №2, P15-f, TO-BUILD заморозка) — хеш-печать после одобрения; правка плана = новое одобрение, видимое владельцу. Терминальная метка обмена роутер↔Claude `СОГЛАСОВАНО`/`СПОР→ВЛАДЕЛЕЦ` (A0b) задаёт состояние «заморожен».
|
||||
- **Верховный хук** (🆕, несущий, п.15/16, TO-BUILD №1) — default-deny на ВСЕ действия, **матчер = `*`** (не список имён). «В плане» = машинная сверка действия с объявленным объектом шага (P15-a), а не «это шаг 3». Совпадение по намерению/операции, не только объекту (P15-e). Объяви-потом-делай по каждому действию (P15-g). Лимит действий на шаг (P15-h). Сам хук **fail-CLOSED** (сбой → стоп + сигнал; рубильник у владельца — P15-d).
|
||||
- **Семена-исключения (D12/D13)** — `writing-plans` + `AskUser` вызываемы БЕЗ готового плана, иначе стену нельзя загрузить (дедлок «нет плана → нечем создать план»). Реализуются ровно через escape = Skill/EnterPlanMode/AskUser (М6, E1) — никакого отдельного послабления; верховный хук пропускает только эти семена, всё остальное по-прежнему требует шаг замороженного плана. **H4-страховка:** «семя чистое» — не обещание, а проверка (writing-plans только пишет план-файл, не читает/не мутирует — иначе блок).
|
||||
- **Классификатор режима (D33)** — по СМЫСЛУ (не код-список, П3-A) делит разговор: тривиальная болтовня → машина спит (плана не требует); содержательное мышление → мыслительный скил обязателен (D32, семенной класс: brainstorming/discovery-interview/writing-plans); продукт/код/прод → полная машина. **Страж секретов (D18) работает во ВСЕХ трёх режимах.**
|
||||
- **[ревизия 2026-06-04]:** D33 расщеплён. **ПРИМЕНЕНИЕ режима — механически в Машине 2** (`decideMode`: разговор→только семена, стройка→строгий матч) — уже есть. **СМЫСЛОВОЙ разбор** (болтовня vs думанье, подтолкнуть к мыслительному навыку) — это рассуждение → **Машина 3** (роутер), не стена («умный не баран»). **finding 8 РАСТВОРЁН призмой владельца (2026-06-04):** в этой среде болтовни нет → off-состояния нет → «чем включается слой» не существует как вопрос; дисциплина всегда на, остаются ДВА режима. Механического триггера и «спящего» режима не строим.
|
||||
- **Сверка план↔сырая просьба владельца (D29, независимый проверяющий)** — отдельный прогон ДО исполнения: совпадает ли замороженный план с ЖИВЫМИ словами владельца (внешний якорь = его просьба, не мой пересказ). Отличен и от судьи (тот: действие↔план), и от оценщика (тот: постфактум); проверяющий ≠ автор плана. Закрывает остаток L2 (recursive-trust). Сырую просьбу подаёт роутер (P16-e, М3), но сама сверка независима.
|
||||
- **[ревизия 2026-06-04]:** D29 снята как ОТДЕЛЬНАЯ сущность — её роль выполняет **слой общения (C-7)**: артефакт, подтверждённый владельцем на гейте-1, + **закрытая дверь** (план обязан ссылаться на опечатанный артефакт). Привязка плана к настоящей воле владельца идёт ЧЕРЕЗ артефакт (владелец якорит на гейте-1), а не через отдельный сравниватель. Механический хвост «каждая явная просьба → шаг» — это машина охвата C-14-C (М3/М4). Отдельного D29-прогона больше не нужно.
|
||||
- **Авто-аудит покрытия дверей** (🆕, P15-b, TO-BUILD №2) — перечислить все инструменты; непокрытый матчером → флаг + default-deny. Закрывает «забытый канал» как КЛАСС (не только WebFetch/WebSearch). Редкий ручной аудит «все ли двери под воротами» (остаток #4/#15).
|
||||
- **Сверка план↔след по сессии** (🆕, п.25/M2, TO-BUILD №9) — каждая запись журнала ложится на шаг плана? запись-сирота → где-то пропустил гейт → флаг владельцу.
|
||||
|
||||
**Инвариант:** без замороженного плана «в плане» — фикция. Матчер обязан быть `*` — иначе «забытый канал» возвращается. A0: подсудный (я) дверь в зал суда не контролирует — ворота срабатывают структурно от состояния «заморожен», меня не зовут по желанию.
|
||||
|
||||
**Как проверяется отдельно:** действие без шага плана → блок (любой инструмент). Попытка переписать план под действие → требует нового одобрения (печать сменилась, видно владельцу). Новый канал/инструмент без матчера → default-deny + флаг аудита. Действие-сирота в журнале без шага → флаг.
|
||||
|
||||
**Точки стыковки:**
|
||||
- Замороженный план → читают Машина 3 (роутер ведёт по нему), Машина 4 (вход судьи = заморож. план).
|
||||
- Состояние «заморожен» (метка A0b) → триггерит автоворота судьи (М4, A0).
|
||||
- Верховный хук → стена, под которой работают роутер (М3, «голова при стене») и переделанная дисциплина (М6).
|
||||
|
||||
---
|
||||
|
||||
## МАШИНА 3 — РОУТЕР-НАСТАВНИК (голова у стены)
|
||||
|
||||
> 🚨🔴 **МИНА ОТ РЕВИЗИИ МАШИНЫ 2 (2026-06-04, K4) — РЕШИТЬ ОСОЗНАННО, НЕ НА АВТОМАТЕ:** в разговорном режиме свободный Write закрыт, а семена `brainstorming`/`writing-plans` (их рекомендует роутер) сами пишут файлы (спеку/план). Артефакт/спека должны материализоваться ЧЕРЕЗ КАНАЛ ОДОБРЕНИЯ гейта-1 (семя выдаёт предложение-текст), а НЕ широким Write. «Починишь» широким Write → откроешь обход (freestyle-артефакт мимо думанья). Полный список — аварийный блок в чертеже Машины 2.
|
||||
|
||||
**Одной фразой:** видит весь граф инструментов, выбирает рассуждением, ловит размытый шаг, риск-фильтр, очередь «на твоё одобрение».
|
||||
|
||||
**На чём стоит:** Машины 1 (журнал) + 2 (замороженный план, верховный хук — детерминированная стена держит даже при «поглупевшем» роутере, P16-d).
|
||||
|
||||
**Что входит (снаряжение роутера, 6 групп / 24 пункта из spec-1):**
|
||||
- **Движок выбора (1.1/1.2):** скил по СМЫСЛУ+рассуждением, ключевые слова сняты; вшитый метод брейнсторма (2–3 варианта → спор с собой → выбор). Граф+каталог узлов на **100%** через кэш промпта (никакого код-сужения — решение владельца).
|
||||
- **Граф УЗЛОВ из РЕЕСТРА** (3.1/3.2/3.3) — стабильный (не graphify!), рёбра = альтернативы/подсказки/конфликты (ПОДСКАЗКИ, не рецепты). Цепочки СОБИРАЮТСЯ рассуждением (ОВ-Д1), не хранятся. Заземление: скилы резолвятся в реальный узел механически (ОВ-Д2-1), пути → на факты + пометка «гипотеза» (ОВ-Д2-2).
|
||||
- **Прогресс плана выжимкой (2.3)**, **самообъясняющие шаги + цель (В6/D36 вар.B)**, **память спора (D36-1)**, **свежий каталог (D36-2)**.
|
||||
- **🔖 ВОЛНЫ / дерево указателей шага (D6/OQ1) — ПЕРЕНОС ИЗ МАШИНЫ 2 (решение E, 2026-06-04, НЕ ЗАБЫТЬ):** в Машине 2 указатель шага сознательно линейный (прямой план). Ветвление — когда шаг раскрывается в под-план («волны») — живёт ЗДЕСЬ, где роутер собирает/разбивает планы. При сборке Машины 3 верховный хук Машины 2 переводится с линейного счётчика на **дерево указателей**.
|
||||
- **Риск-фильтр у роутера** (W1+W2) — та же тройка, что у судьи (где сломается→больно?→откатимо?), но роутер сперва ЧИНИТ сам (откат/безопаснее/разбей/замена); не смог → флажок владельцу. Роутер НЕ блокирует.
|
||||
- **Качество/честность (5.1–5.5):** видимая трасса 5.1 (⭐ сердце «крутить роутер»), право воздержаться 5.2, нюх на размытость 5.3, самопроверка 5.4 (слита с 1.2), согласованность плана целиком 5.5.
|
||||
- **Анти-обманная тройка:** 5.2 воздержание + 5.3 нюх + 4.4/D39 интервьюер. `no_skill_found` = СТОП к владельцу (P16-c, OQ7 PRIMARY), не «свобода».
|
||||
- **Роутер по СЫРОЙ просьбе владельца** (P16-e, несущий), не по моему пересказу. **[РЕВИЗИЯ 2026-06-05 — spec `2026-06-05-router-conversational-input-and-emergent-catch-design.md`]:** роли НЕ меняются (контроллер ПРОИЗВОДИТ разговорный план, роутер КРИТИКУЕТ — как в фазе стройки 1б); якорь роутера = сырой промпт + ВСЕ сырые ответы владельца по порядку + мой план (не пересказ). Интент P16-e (нет подкрутки маршрута пересказом) сохранён через сырой якорь. Добавлены: анти-внушение (роутер сперва сам выводит из сырых слов, потом сверяет — слоты трассы), покруговой контроль (код: скил вызван / вопрос не повис; роутер: смысл; мягкий возврат → 3 прохода → владелец), выходная 3-шаговая лесенка (существование $0 → роутер-верность спеки → судья полнота+K7). Полный дизайн — в спеке. **Высокий риск → скил обязателен** независимо от уверенности ИИ (P16-g: прод-код/деньги/авторизация/многошаговость). Назначение скила + его вызов = машинный факт (P16-b, S1). missed-activation = блок на высокорисковых классах (P16-h).
|
||||
- **Дисциплина роутера** (он тоже ИИ): обязательные структурные слоты трассы 5.1 + механический валидатор (пустой слот = красный флаг); пропуск уровня только из ЗАКРЫТОГО списка категорий; счётчик частоты пропусков; оценщик постфактум.
|
||||
- **Очередь одобрений 3.4/3.5** (🆕, hard-rule владельца 2026-06-03) — механизм СТРОИМ; **наполнение фонда примеров (3.4) и долгой памяти (3.5) — ТОЛЬКО по явному «да» владельца, иначе НИКАК** (без авто-обучения, без тихого накопления). Доставка: очередь-файл → раздел «На одобрение» в `/brain-retro` → сигнал в STATUS.md «ждут: N» → одобрение пачкой. Переиспользует существующий brain-retro + STATUS-генератор.
|
||||
- **Чувство свежести (3.6)** — дата каталога/реестра; устарело → предупреждает + пересобирает, не работает уверенно по старью.
|
||||
- **Группа 6 (Деньги)** — **цена = разделитель равноценных кандидатов** (6.1: при двух одинаково-годных узлах роутер берёт дешевле; «цена НЕ важнее правильности» — действует ТОЛЬКО при равенстве качества); крик при нуле бюджета (D27, стык М7); выжимка-не-свалка с исключением для графа (6.2: контекст экономим, но граф+каталог узлов всегда на 100% — решение владельца). Этим закрываются заявленные «6 групп / 24 пункта» снаряжения.
|
||||
- **Разведка начинается с графа (Q-GRAPH)** + **ручка «% разведки» (крит-разбор #3)** — Q-GRAPH: дефолтный первый пункт любого плана-разведки (сперва `/graphify query`, потом Read/Grep, CLAUDE.md §5 п.14). Ручка #3 (заложить СЛОТ СЕЙЧАС, не пропустить при детализации): доля разрешённого «попробовать новый узел» (0 по умолчанию) + счётчик использования узлов + роутер СПРАШИВАЕТ разрешение на новый узел — анти-самоослепление петли обучения; наполняется ТОЛЬКО по «да» владельца (как 3.4/3.5).
|
||||
|
||||
**Инвариант:** граф = граф УЗЛОВ из реестра (стабильный), НЕ graphify (волатильный). Цель остаётся владельца (D29), роутер улучшает только МЕТОД. Вето Claude → арбитраж владельца. Очередь обучения наполняется ТОЛЬКО по «да».
|
||||
|
||||
**Как проверяется отдельно:** выдуманный скил → отклонён механически. Размытый шаг → нюх→интервьюер→стоп, не «свобода». Высокий риск без скила → обязателен. Трасса с пустым слотом → флаг валидатора. Кандидат в фонд без «да» владельца → НЕ добавляется.
|
||||
|
||||
**Точки стыковки:**
|
||||
- Роутер → отдаёт замороженный план со скилами (М2 печатает, М4 судит).
|
||||
- Граф УЗЛОВ из реестра → общий с судьёй (М4, только чтение).
|
||||
- Очередь одобрений → `/brain-retro` (существующий) + STATUS.md (М7).
|
||||
- Трасса 5.1 → оценщик (постфактум) + владелец (D17), НЕ судья (тот слеп).
|
||||
|
||||
---
|
||||
|
||||
## МАШИНА 4 — СУДЬЯ A–I (ворота)
|
||||
|
||||
> 🚨🔴 **КОНТРАКТЫ ОТ РЕВИЗИИ МАШИНЫ 2 (2026-06-04) — НЕ ПРОПУСТИТЬ ПРИ СБОРКЕ:** на Машину 4 ляжет КОНЦЕНТРАЦИЯ доверия — три закрытия ревизии держатся здесь.
|
||||
> - **K1 (гейт-1):** печать артефакта/плана ставит ТОЛЬКО процесс одобрения гейта-1 (ключ контроллеру недоступен); артефакт ВСЕГДА доводится до владельца — это ЗАМЕНА снятой сверки D29. Пропустишь → контроллер сам себе печатает; план не привязан к воле владельца.
|
||||
> - **K2 (A2):** «взял ли навык» сверяется по ФАКТИЧЕСКОМУ журналу вызовов (М1), НЕ по тексту TodoWrite/coverage-ярлыка. Пропустишь → зелёный проход TodoWrite (М2) = путь фейкового покрытия навыком.
|
||||
> - **K5 (гейт-2, добавлен 2026-06-04 — канон M3) — судья судит ПЛАН как БУДУЩЕЕ.** Судья проверяет, что шаг объявляет конкретный проверяемый КРИТЕРИЙ приёмки (наличие+конкретность, да/нет); слова «сделано»/«проверено» в тексте плана за ФАКТ НЕ принимает; реальное «проверено» ставит только рантайм-сентинел при исполнении (М5: GREEN + мутация). Этот контракт ОБЯЗАН попасть в аварийный блок чертежа Машины 4. **Пропустишь → судью усыпляет слово «проверено» в плане → GO на ИМИТАЦИЮ проверяемости.**
|
||||
> - **K6 (анти-откат, добавлен 2026-06-05 — аудит Машин 1+2) — судья держит МОНОТОННУЮ зарубку (high-water-mark) ВНЕ откатываемого набора.** Журнал М1 (`action-journal-*.jsonl`+`.head`), замороженный план/артефакт и указатель шага М2 лежат в `~/.claude/runtime` и ПОДПИСАНЫ — но СТАРАЯ подпись валидна, поэтому подмена на более раннюю валидную пару файлов = откат: прячет прошлое действие или отматывает шаг заново. `verifyChain`/`verifyFrozenPlan` это НЕ ловят (печать цела, просто старая). Судья на каждом действии запоминает максимум (`seq` журнала / номер шага); при старте/проверке сверяет загруженное — `seq`/шаг МЕНЬШЕ виденного → ОТКАТ → блок. **Зарубка обязана жить ВНЕ досягаемости записи контроллера** (OS-keychain рядом с ключом печати ИЛИ собственный append-only канал судьи) — зарубка в самом runtime-файле = ПРИЗРАК (откатится вместе с журналом). Подделку нового состояния закрывает печать (нужен ключ); откат-сокрытие прошлого — НЕ закрыт ничем, кроме пола (`runtime-write-deny`), а если пол пробьют — откат свободен. **Пропустишь → откат к старому подписанному состоянию стирает/реплеит действия мимо журнала; "я этого не делал" становится недоказуемым.**
|
||||
> - **K7 (линза пре-мортема на Гейте-1, добавлен 2026-06-05 — brainstorm входа роутера) — судья на Гейте-1 несёт ОТДЕЛЬНЫЙ обязательный слот вердикта «какие следствия принятых решений НЕ закрыты?».** Другая линза, чем проверка полноты (полнота = «все названные нужды закрыты»; пре-мортем = «что от решений сломается/вылезет, чего никто не назвал, напр. больше отступ → поехал мобильный»). Разные линзы ловят разные промахи (диверсификация > повтор одной проверки). Слот обязателен ПО ФОРМЕ (пустой → невалидно); содержательно — размышление ИИ, поднимает шанс, НЕ гарантирует (часть потолка ~0.5%). Источник — spec `2026-06-05-router-conversational-input-and-emergent-catch-design.md` §4 #4. **Пропустишь → всплывающие невысказанные следствия ловятся только мной и владельцем, без второго независимого взгляда на гейте.**
|
||||
> Полный список — аварийный блок в чертеже Машины 2 + канон Машины 3 (§«K5 → МАШИНА 4»).
|
||||
|
||||
**Одной фразой:** перестройка нынешнего llm-judge в полного судью — риск + качество + проверка скилов, безопасный отказ, не переопределяет пол.
|
||||
|
||||
**На чём стоит:** Машины 1 (журнал/расписки) + 2 (замороженный план, состояние «заморожен» триггерит ворота A0). **Граф узлов — фундаментный артефакт из РЕЕСТРА** (стабильный, не от роутера): судья читает его напрямую, от М3 нужен лишь единый формат, не готовность роутера. Build-time: судью собираем/тестируем на тестовых планах после 1+2+граф; runtime: судья судит планы, которые подаёт роутер (М3). Ложной жёсткой зависимости М4→М3 нет.
|
||||
|
||||
**Что входит (карта A–I из spec-1 + J1–J10 из spec-2):**
|
||||
- **Функции (блок A):** A0 автоворота от состояния «заморожен» (подсудный дверь не контролирует); A0b метка `СОГЛАСОВАНО`/`СПОР→ВЛАДЕЛЕЦ`; A1 ворота на план (вкл. перемаршрутизированный после находки — #6); A2 надзор на исполнении (звенья цепочки не схлопнуты, скилы взяты, «сделано=плану»); A3-блок (ТОЛЬКО фатальное) + A3-замечание (качество → СОВЕТ владельцу).
|
||||
- **Снаряжение:** гибрид риска 2+3 (где сломается → больно? → откатимо? → больно+необратимо=стоп); форма вердикта go/no-go №4 (GO / NO-GO+категория / GO+замечание); мерило качества плана (3 пункта из write-plans); 3 вопроса по скилам; граф УЗЛОВ (общий с роутером, только чтение); короткая память задачи (П1, вердикты не доводы).
|
||||
- **Независимость (блок B/E):** вход = ТОЛЬКО цель + план со скилами + тип задачи + граф 100% + память задачи. **НЕ кладём** спор роутер↔Claude, рассуждения, весь чат, graphify (J4: проза обрезается механически — «слеп к рассуждениям» как механика).
|
||||
- **Критерии (блок C):** граница «грубо vs качество» режется ОБРАТИМОСТЬЮ — необратимое → сомнение в СТОП; обратимое → ПРОПУСК+совет. Фатальное = тяжёлое И необратимое.
|
||||
- **Модель (блок D):** ворота = **1 вызов Sonnet** на весь план (риск+качество+скилы разом). **Haiku не используем** (плоское «вроде ок» обнуляет свежий взгляд). Исполнение: код сверяет механику → совпало → пропуск ($0); расхождение ИЛИ разрушительная команда (`rm`/`DROP`/`DELETE`/`TRUNCATE`/`migrate`/push прод) даже при совпадении → Sonnet (I-2).
|
||||
- **⚠️ ОТКРЫТО (решить в детальном плане Машины 4):** напряжение «Haiku НЕ используем» (блок D: плоское «вроде ок» обнуляет свежий взгляд) против «J3 = Sonnet+**Haiku**+Opus на высоком риске». Спор унаследован из самих источников, не ошибка карты. Зафиксировать выбор при сборке судьи (вероятная развязка: одиночные ворота = Sonnet без Haiku; высокорисковый консенсус J3 = с Haiku как «третий голос ради расхождения», где плоскость не вредит). Решение — за владельцем.
|
||||
- **Усиления J1–J10:** J1 fail-closed на разрушительном (откатимое — fail-open); J2 судья ПОСЛЕДНИЙ, под ним детерм-сетка; J3 мульти-судья (Sonnet+Haiku+Opus, «один НЕТ→блок») только на высоком риске; **J9 (несущий) судья НЕ переопределяет пол**; J10 критичное = судья + ВЛАДЕЛЕЦ; J5 здоровье на доске + крик при тихой деградации; J6 критерии в защищённом файле (мне нельзя смягчить); J8 каждое решение append-only логируется (аудит судьи).
|
||||
- **Дисциплина судьи** (он тоже ИИ, главный риск — штамповка): структурные слоты вердикта (риск/качество/скилы) — пустой слот = флаг; механическая проверка «GO с пустыми слотами = невалидно»; оценщик судит судью постфактум (D31); владелец крутит обе ручки.
|
||||
- **Реакция владельца (блок H):** СТОП-карточка (что+почему одной фразой) / СОВЕТ списком с планом / споры в журнал. Ответы: снять/подтвердить/сам-поправить (стоп), принять/отклонить/отложить (совет). Решение владельца после судьи — финал, не пере-судится (П2). Сам Claude блок не снимает (A0).
|
||||
|
||||
**Инвариант:** судья слеп к рассуждениям (иначе сваливается в мою логику). Не выбирает план/скилы (роутер), не калибрует роутер (оценщик), блокирует только фатальное (качество — совет). J9: «да» судьи не отпирает пол.
|
||||
|
||||
**Как проверяется отдельно:** план с фатальным необратимым риском → NO-GO. План с косметическим изъяном → GO+замечание. Действие чужим скилом на исполнении → блок. GO с пустым слотом риска → невалидно (флаг). Попытка судьи одобрить force-push → пол не отпирается (J9).
|
||||
|
||||
**Точки стыковки:**
|
||||
- Вход судьи ← замороженный план (М2) + журнал (М1) + граф узлов (М3/реестр).
|
||||
- Вердикты → владелец (СТОП/совет, М7 доска) + оценщик (постфактум).
|
||||
- J9 → стыкуется с тонким полом и верховным хуком (М2/М6): судья не первый и не один.
|
||||
|
||||
---
|
||||
|
||||
## МАШИНА 5 — УКРЕПЛЕНИЕ ЗАЩИТЫ-ПОЛА (🟩, существующие хуки + усиления)
|
||||
|
||||
> 🚨🔴 **КОНТРАКТ ОТ РЕВИЗИИ МАШИНЫ 2 (2026-06-04) — НЕ ПРОПУСТИТЬ:**
|
||||
> - **K3 (`classifyBashCommand`):** команда помечается `readonly` ТОЛЬКО если заведомо смотрящая; редирект `>`/`>>`/`|`-в-писатель, `git config`/`gc`/`push`/`commit`, `tee`, `$(...)`, и ЛЮБАЯ неуверенность → НЕ readonly. От этого зависит безопасность «зелёного прохода» Машины 2 (read-only Bash пропускается без шага плана). Пропустишь → мутация просочится мимо плана (напр. `git log > evil.sh`).
|
||||
> Полный список — аварийный блок в чертеже Машины 2.
|
||||
|
||||
**Одной фразой:** усилить то, что оставляем, чтобы судья опирался на крепкий пол.
|
||||
|
||||
**На чём стоит:** Машина 1 (фундамент). Параллельна 2–4 (защита независима от дисциплины).
|
||||
|
||||
**Что входит (🟩-список из пред-спеки + усиления второго прохода):**
|
||||
- **read-path-deny → +content-class** (P8-a: детектор секрет-паттернов на выдаче, как gitleaks) +все-каналы чтения (P8-b, матчер-«всё») +внутри субагента (P8-c) +список в защищённом файле (P8-d).
|
||||
- **normative-content (анти-отрава, п.9/11) → +по-смыслу** (P9-b: diff удаляет/ослабляет правило → блок+да; семантически судья «ослабляет ограничение?») +контрольные суммы зависимостей сторожей (P9-a) +видно владельцу с diff (P9-c) +**имя скила → портативный канал** (TO-BUILD №10: «назначенный роутером editing-скил + правка в плане + судья + твоё да», не зашитое `claude-md-management`). **Память = СОВЕТ, НИКОГДА не источник разрешения (P11-d)** — «мне в памяти разрешили» не проходит.
|
||||
- **mcp-classification (п.12) → +скан ИСХОДЯЩЕГО запроса** (P12-a: внешний «read» = тоже отправка, проверяем что УХОДИТ — секрет-паттерны+размер) +SQL/команда fail-closed при неуверенном разборе (P12-b) +URL по сущности (P12-c, блок `data:`/`file:`/IP-литерал). 3 уровня для владельца: читать-наружу свободно / менять-по-плану / необратимое-наружу = план+твоё-да.
|
||||
- **workflow-gate (п.13) → +наследование ДОКАЗЫВАЕТСЯ пробой** (P13-a, TO-BUILD №5: родитель гонит через помощника заведомо-плохое, убеждается что блокнуло; не доказано → помощнику мутации запрещены) +результат помощника через журнал S1 (P13-b, анти-228) +помощник под фундаментом (P13-c).
|
||||
- **parallel-session-lock (п.14) → +pid-liveness** (P14-a, TO-BUILD №11) +**freshness-guard на слепой Write** (P14-c, несущий — **дом сборки: Машина 6**; здесь только стык: parallel-lock и freshness — одна линия защиты от потери чужой работы; механика — файл не менялся с момента чтения? менялся → стоп, файловый анти-клоббер). Основа коллизии — W-1 (1 задача=1 worktree=1 ветка=1 сессия).
|
||||
- **tdd-real-test-verifier (п.18) → +мутационная проверка** (P18-a, несущий, TO-BUILD: Pest 4 mutation — сломай правленый код, тест ОБЯЗАН покраснеть; остался зелёным → не проверяет → флаг/блок) +покрытие изменённого (P18-b).
|
||||
- **Неподделываемый GREEN/RED** (TO-BUILD №3, П6/П17/П21-S4) — привязан к ОТПЕЧАТКУ тестированного кода (правка после прогона аннулирует мгновенно, P6-a); пишет ТОЛЬКО харнесс в подписанную служебку (P6-b); детектор массовости (анти-228); несёт ЧТО прогонялось, покрытие изменённого (P6-c).
|
||||
|
||||
**Инвариант:** защита-пол non-escapable (даже escape/мёртвый судья её не отпирают, J1/J2/E2). Эти хуки чистая защита — судью НЕ заменяют и им НЕ заменяются.
|
||||
|
||||
**Как проверяется отдельно:** секрет в `.env.bak` → ловится по содержимому. Фейк-тест → мутация краснеет → блок. 228 липовых GREEN → детектор массовости. Помощник без доказанного наследования → мутации запрещены. Слепой Write по изменённому файлу → стоп.
|
||||
|
||||
**Точки стыковки:** журнал S1 (М1) ← GREEN/RED записи + результат помощника. Мутационная проверка → судья опирается (М4 верит факту прогона).
|
||||
|
||||
---
|
||||
|
||||
## МАШИНА 6 — ПЕРЕДЕЛКА ДИСЦИПЛИНЫ + ЧИСТКА (🟨 + 🟥)
|
||||
|
||||
**Одной фразой:** растворить старый «зоопарк» в стену+судью, удалить костыли, новый escape только через твоё «да», авто-снимок перед опасным.
|
||||
|
||||
**На чём стоит:** **раздельно (защита и дисциплина — разные опоры).** Защитная часть (тонкий детерминированный пол / escape-защита E2 non-escapable / авто-снимок P1-b / freshness-guard P14-c) стоит ТОЛЬКО на М1 — строится сразу после фундамента, параллельно 2–4 (приоритет защиты). Дисциплинарная часть (растворение router-gate/branch-switch/verify-before-push/script-watcher/tdd-gate/decomposition/self-debrief/rationalization/coverage в стену+судью, escape-механизм) стоит на Машинах 2 (верховный хук) + 3 (роутер) + 4 (судья).
|
||||
|
||||
**Что входит:**
|
||||
- **🟥 УДАЛИТЬ:** `safe-baseline-metering` (п.19, костыль-счётчик чтений → работу берёт верховный хук); `override-vocab` (п.26, уже мёртв → escape проектируем заново).
|
||||
- **🟨 router-gate Bash/PowerShell** (п.1/2/3/15) → растворить в верховный хук (default-deny, матчер=*) + **тонкий детерминированный пол** (ТОЛЬКО необратимое-невозвратимое: force-push/перепись истории, отправка секретов наружу, затирание несохранённого, запись в защищённые не-git пути — глухой, НЕ завязан на судью, P1-d список в защищённом файле) + **разбор цепочек на атомы** (P2-a по `&&`/`;`/`|`/подстановкам; непрозрачные `bash -c`/`node -e`/`eval`/`$()` → только план+судья, P2-b; не разобрал → СТОП, P2-c) + белый список git-флагов (P3-a инверсия: разрешаем заведомо-безопасные, нераспознанное → стоп; P3-b проверка ЭФФЕКТА; P3-c отключение сторожей `LEFTHOOK=0` → требует твоего да, не блок наглухо). **Секрет-страж безусловно** (зеркало `rm`: много раздражения/мало пользы → режем; ноль раздражения/много пользы → держим).
|
||||
- **🟨 branch-switch + git-approval** (п.4/5) → AskUser-канал (расписка на ПОЛНОЕ действие: сервер+ветка+коммиты=отпечаток, P4-a; прод-push показывает diff, P4-b; не переиспользуется) + ожидаемая ветка БЕРЁТСЯ ИЗ WORKTREE (P5-b, физ-факт, подделать нечем) + замороженный план. Локальный commit по плану → верховный хук+судья (без попапа); прод-push = высший ярус (план+да+судья+`prod-deploy-validator` 8 SSH-проверок, P4-d).
|
||||
- **🟨 verify-before-push** (п.6) → шаг тестов в плане + машинный GREEN (см. М5).
|
||||
- **🟨 script-watcher** (п.7) → тонкий бэкстоп языконезависимый (P7-a) + **код сторожей = высший ярус** (P7-b, корона самоизменения: правка гейтов/хуков/судьи → канал+коммит+GREEN+твоё да; исполняется ВСЕГДА закоммиченная версия, никогда правка из рабочей копии; P7-c хеш-сверка).
|
||||
- **🟨 router-tool-gate** (п.16) → верховный хук (стена) + роутер-наставник (голова) — см. М2/М3.
|
||||
- **🟨 tdd-gate** (п.17) → план(роутер) + **порядок тест→RED→код→GREEN машинно по времени** (P17-a: RED на тесте РАНЬШЕ правки кода в журнале S1; нет → стоп; P17-b RED = настоящий assertion-провал, не сломанный импорт; P17-c только прод-код).
|
||||
- **🟨 decomposition-detector** (п.20, заглушка) → **сигнал-растяжка роутеру, НЕ блокер** (P20-a граф работы по сессии; P20-b эскалация к фича-строгости по накоплению, не блок-по-счётчику; P20-c видно владельцу). Повтор костыля 19 запрещён.
|
||||
- **🟨 self-debrief-detector** (п.22) → гейт на ПОСЛЕДСТВИЕ мысли (U1–U4: действие/артефакт/сохранение требуют машинный факт мыслительного скила; вольная проза инертна; P22-e вниз пускается только выход скила; P22-g вывод владельцу помечен «выверен/СЫРОЙ»).
|
||||
- **🟨 rationalization-audit** (п.23) → сигнал-паттерн владельцу (P23-a, не превенция); превенция СТРУКТУРНАЯ (P23-b: судья слеп + детерм-гейт + план↔сырая просьба + заморозка → оправданию некуда приземлиться).
|
||||
- **🟨 coverage-verify + memory-coverage** (п.25) → машинная метка из следа (M3, моей руки нет) + сверка след↔заморож.план по сессии (P25-d, аудит полноты — стык М2). Самописный ярлык не раздуваем.
|
||||
- **🆕 Escape-механизм** (п.26, E1–E10) — **только Skill/EnterPlanMode/AskUser**; самописный routing-tag УБРАТЬ (E4: самоиздаваемого обхода НЕ существует). E1 код пишет из твоего ответа, узкий/точный/одноразовый/короткое окно; **E2 escape снимает ДИСЦИПЛИНУ, НИКОГДА защиту** (секреты/runtime/force-push/необратимое — non-escapable жёсткий список); E3 журнал+видим+счётчик; E7 узкий (один гейт, не «всё»); E8 cooldown+нарастающее трение; E9 критичное = полный ярус; **E10 окно показывает машинный риск** (не мои слова).
|
||||
- **🆕 Авто-снимок перед разрушительным (P1-b) + freshness-guard на Write (P14-c)** — необратимое локальное → откатимое по построению (копия в бэкап); файловый анти-клоббер. P1-a «откатимо» = проверенный факт (есть чистая закоммиченная копия?), не допущение. P1-c рушить только НАЗВАННОЕ в плане.
|
||||
|
||||
**Инвариант:** escape снимает только дисциплину, не защиту (E2). Код сторожей = высший ярус (P7-b) — прямой путь самоосвобождения, держим жёстко. Тонкий пол — глухой, не завязан на судью.
|
||||
|
||||
**Как проверяется отдельно:** `rm` вне плана → блок верховным хуком (не потому что `rm`, а потому что не в плане). `rm` в плане над названным файлом → авто-снимок + судья. force-push → блок наглухо (non-escapable). Обфусцированная цепочка → не разобралась → стоп. escape-фраза, набранная мной → не существует (только твоё да). Правка кода судьи → только закоммиченная версия + твоё да.
|
||||
|
||||
**Точки стыковки:** верховный хук (М2) поглощает дисц-часть. Судья (М4) досматривает разрушительное. AskUser-канал (М1) пишет расписки. Счётчики escape/оправданий → журнал (М1) + доска (М7).
|
||||
|
||||
---
|
||||
|
||||
## МАШИНА 7 — ВИДИМОСТЬ + ТЕНЕВОЙ РЕЖИМ + ВКЛЮЧЕНИЕ (поверх всего)
|
||||
|
||||
**Одной фразой:** доска «кто на посту» + здоровье судьи; сперва всё «тихо» (логирует, не блокирует), мерим, включаем по одному.
|
||||
|
||||
**На чём стоит:** все 1–6 (наблюдает и включает их).
|
||||
|
||||
**Что входит:**
|
||||
- **Доска «кто на посту»** (🆕, TO-BUILD №12, остаток #5) — в STATUS.md: верховный хук ✓ / фундамент 3/3 ✓ / судья ✓ / роутер ✓. Владелец видит, а не гадает. Закрывает «тихую дыру при недонастройке» (забыл включить → никто не кричит).
|
||||
- **Здоровье судьи + крик при тихой деградации** (J5) — судья включён, но падает «открыто» → громкий крик «пост пустой» (прецедент bootstrap-smoke). Связь с D27: сбой ИИ = деградация до кода + крик про деньги (бюджет кончился → ГРОМКО; сеть → тихий повтор).
|
||||
- **Очередь одобрений в `/brain-retro`** (стык М3) — раздел «На одобрение» + сигнал «ждут: N» в STATUS.md.
|
||||
- **Теневой режим (D28 шаг 3)** — новые гейты работают и ЛОГИРУЮТ, что бы заблокировали/эскалировали, реально НЕ блокируют. Мерим: долю ложных блоков, цену, объём эскалаций. **ИСКЛЮЧЕНИЕ (важно):** тихий режим — ТОЛЬКО для новых **дисциплинарных** ворот (М2 верховный хук / М3 роутер / М4 судья / дисц-часть М6). **Защита-пол (М1 + защитная часть М5/М6: секреты, runtime, force-push, подпись расписок) включена с первой секунды и тихим режимом НЕ покрывается** — иначе во время стройки зияет дыра в защите.
|
||||
- **Подкрутка порогов (D28 шаг 4)** по реальным данным — пороги уверенности роутера, частоты escape, грань тривиал↔содержательное.
|
||||
- **Включение по одному (D28 шаг 5)** — сперва мутации, потом чтения. Старые дисциплинарные хуки снимаем ПО ОДНОМУ, после того как новое доказало себя в тихом режиме (турникет временный, с понятным концом — крит-разбор #1, вариант B).
|
||||
|
||||
**Инвариант:** живая система во время стройки не ломается. Рубильник держит ВЛАДЕЛЕЦ (настройки мне закрыты) — это защита, не дыра (подсудный не распускает сторожей). H1/I-3 (простой owner-facing переключатель / усталость владельца) — НЕ давим сейчас, настроим по факту эксплуатации.
|
||||
|
||||
**Как проверяется отдельно:** доска показывает пост → выключить хук → доска показывает дыру + крик. Тихий режим → лог «заблокировал бы X», действие прошло. Включение одного гейта → старый снят, новый блокирует.
|
||||
|
||||
**Точки стыковки:** читает журнал (М1), статус всех хуков (М1–М6), очередь роутера (М3), здоровье судьи (М4). Пишет в STATUS.md (существующий генератор).
|
||||
|
||||
---
|
||||
|
||||
## КОВЕРИДЖ-ТАБЛИЦА — доказательство «ничего не пропущено»
|
||||
|
||||
26 пунктов пред-спеки → машина:
|
||||
|
||||
| Пункт | Решение | Машина |
|
||||
|---|---|---|
|
||||
| 1 разрушительные shell | 🟨 тонкий пол + судья | 6 |
|
||||
| 2 sub-shell/цепочки | 🟨 разбор на атомы + суть | 6 |
|
||||
| 3 git-трюки обхода | оставить (тонкий пол) | 6 |
|
||||
| 4 git без одобрения | 🟨 AskUser-канал | 6 (канал — 1) |
|
||||
| 5 git на чужой ветке | 🟨 ветка из worktree | 6 |
|
||||
| 6 commit без тестов | 🟨 машинный GREEN | 5/6 |
|
||||
| 7 запуск правленого скрипта | 🟨 корона самоизменения | 6 |
|
||||
| 8 чтение secrets | 🟩 read-path-deny | 1 (+усил. 5) |
|
||||
| 9 shell-чтение/запись нормативки | 🟩 portable канал + protected | 5 |
|
||||
| 10 запись в runtime | 🟩 runtime-write-deny | 1 (+подпись 1) |
|
||||
| 11 правка нормативки/памяти | 🟩 анти-отрава | 5 |
|
||||
| 12 внешний MCP | 🟩 классификатор + outbound | 5 |
|
||||
| 13 workflow/субагенты | 🟩 наследование пробой | 5 |
|
||||
| 14 лок сессии | 🟩 parallel-lock + freshness | 5 |
|
||||
| 15 default-deny | 🆕 верховный хук матчер=* | 2 |
|
||||
| 16 действие без скила | 🆕 верховный хук + роутер | 2+3 |
|
||||
| 17 код без теста (TDD) | 🟨 порядок машинно | 6 (+5) |
|
||||
| 18 фейк-тест | 🟩 мутационная проверка | 5 |
|
||||
| 19 safe-baseline | 🟥 удалить | 6 |
|
||||
| 20 тайное дробление | 🟨 сигнал-растяжка | 6 |
|
||||
| 21 TodoWrite липовый скил | 🟨 судья A2 + журнал | 4 (+1) |
|
||||
| 22 само-ретро без скила | 🟨 гейт на последствие | 6 |
|
||||
| 23 фраза-самооправдание | 🟨 структурная превенция | 6 |
|
||||
| 24 llm-judge | 🟨 Судья A–I (J1–J10) | 4 |
|
||||
| 25 coverage/memory тег | 🟨 машинный след + аудит | 6 (+2) |
|
||||
| 26 escape-фразы | 🆕 escape Skill/AskUser | 6 |
|
||||
|
||||
12 TO-BUILD → машина: №1 верховный хук (2), №2 WebFetch/Search аудит (2), №3 неподдел. GREEN/RED (5), №4 журнал S1 (1), №5 наследование субагенту (5), №6 decomposition-растяжка (6), №7 судья J1/J3/J5/J6 (4), №8 routing-tag→AskUser (6), №9 сверка план↔след (2), №10 portable normative-канал (5), №11 pid-liveness (5), №12 доска+здоровье (7).
|
||||
|
||||
10 несущих механизмов → машина (= 7 принципов из шапки, воплощённых конкретикой): журнал S1 (1), заморож. план (2), судья-не-переопределяет-пол J9 (4), машинная правда владельцу E10 (6, cross-cut), подпись расписок P10-c (1), авто-снимок P1-b + freshness P14-c (6), доказательство наследования P13-a (5), мутационная проверка P18-a (5), матчер-всё + аудит дверей P15-b (2), роутер по сырой просьбе P16-e + высокий риск всегда скил P16-g (3).
|
||||
|
||||
### Ключевые решения дизайна (D / OQ) → машина
|
||||
|
||||
Коверидж-таблица выше доказывала покрытие **хуков** (26 пунктов + 12 to-build). Этот блок доказывает покрытие **логики** источника-1 (D-решения + открытые вопросы) — то, что владелец просил проверить. Перечислены несущие/спорные решения; остальные D1–D36 живут внутри перечисленных машин.
|
||||
|
||||
| Решение | Суть | Машина |
|
||||
|---|---|---|
|
||||
| D6 / OQ1 | волновая декомпозиция (под-план = новый круг) | 3 (+6 decomposition-сигнал) |
|
||||
| D10 / D21 | судья всеобъемлющий, код-вперёд | 4 |
|
||||
| D11 / D12 | без плана нет действия | 2 |
|
||||
| D12 / D13 | семена writing-plans/AskUser (bootstrap стены) | 2 (через escape — 6) |
|
||||
| D15 / D22 / D26 | два вида «без скила» → СТОП к владельцу | 3 (P16-c) |
|
||||
| D17 | владелец видит ВСЕ трассы/под-планы (дерево) | 3 → 7 (доска/очередь) |
|
||||
| D23–D25 | роутер = наставник | 3 |
|
||||
| D27 | сбой ИИ = деградация + крик про деньги | 7 (J5) |
|
||||
| D28 | режим внедрения (тихо → по одному) | 7 |
|
||||
| D29 | ~~независимая сверка план↔сырая просьба~~ → **поглощена** слоем общения C-7 (артефакт+печать владельца на гейте-1) + закрытой дверью (ревизия 2026-06-04); хвост «просьба→шаг» = C-14-C | (нет отдельной) М3/М4 |
|
||||
| D31 | оценщик постфактум (судит судью/роутер) | 4 (+3) |
|
||||
| D32 | мыслительные скилы = семенной класс + Stop-гейт | 2 (режим) + 6 (P22) |
|
||||
| D33 | ~~три режима~~ → **ДВА** (думанье/стройка); «болтовня» неприменима (призма владельца 2026-06-04, болтовня → claude.ai). Применение режима — механически в М2 (`decideMode`); смысловой нудж — М3 | 2 (механика) + 3 (смысл) |
|
||||
| D34 / D35 | роутер вправе разбить/переделать план | 3 |
|
||||
| D36 | самообъясняющие шаги + память спора + свежий каталог | 3 |
|
||||
| Группа 6 | деньги: цена-разделитель + выжимка | 3 |
|
||||
| Q-GRAPH | разведка начинается с графа | 3 |
|
||||
| #3 крит-разбор | ручка «% разведки» + счётчик проб (слот СЕЙЧАС) | 3 |
|
||||
| OQ4 | чтение как канал утечки под планом | 5 (read-path +content) |
|
||||
| OQ7 | no_skill_found = СТОП, не свобода | 3 (P16-c) |
|
||||
|
||||
**Покрытие:** все 26 пунктов хуков + 12 to-build + **10 несущих механизмов** + ключевые D/OQ-решения привязаны к машинам. Оставшиеся D1–D9 / D14 / D16 / D18–D20 / D30 — внутри перечисленных машин (страж секретов D18 → М1/М5; финальный поток D30 → М2/М4). Дыр покрытия не выявлено — ни по хукам, ни по логике дизайна.
|
||||
|
||||
---
|
||||
|
||||
## ЧТО НЕ ВХОДИТ В ЭТУ КАРТУ (по делу, не пропуск)
|
||||
|
||||
- **Пошаговые TDD-шаги** (тест→RED→код→GREEN→commit, с точным кодом и путями) — пишутся в **детальных планах по каждой машине отдельно**, на основе этой карты. Карта задаёт ЧТО и в каком порядке; детальные планы — КАК по шагам.
|
||||
- **Точная регистрация хуков в `settings.json`** — шаг владельца (Claude'у settings.json закрыт; до регистрации хуки инертны).
|
||||
- **Две независимые починки кода** (`max_tokens` 1500→15000, fix `task_type`, 1996 тестов GREEN, коммит-предки `6a8c8494`) — уже сделаны, выкатываемы отдельно, вне этой стройки.
|
||||
|
||||
---
|
||||
|
||||
## СЛЕДУЮЩИЙ ШАГ
|
||||
|
||||
Карта готова. По команде владельца — детальный TDD-чертёж **по первой машине (Фундамент)**, затем по второй, и так вверх по зависимости. Каждый детальный план — через `superpowers:writing-plans`, исполнение — через `superpowers:subagent-driven-development`.
|
||||
@@ -0,0 +1,103 @@
|
||||
# Машина 3 — журнал вопросов и решений по ходу автономной сборки (2026-06-04, ночь)
|
||||
|
||||
> Владелец дал мандат: «делай полностью Машину 3; где упрёшься в вопрос — не останавливайся, запиши вопрос и делай дальше что можешь». Этот файл — все вопросы/допущения, которые возникли и которые НАДО показать владельцу утром. Каждое допущение помечено `[ДОПУЩЕНИЕ]`, каждый открытый вопрос — `[ВОПРОС]`.
|
||||
|
||||
## Контекст
|
||||
- На чём стоит: Машина 1 (фундамент, собрана) + Машина 2 (стена/замок, собрана).
|
||||
- Порядок: 3-A контракты скилов → 3-B граф узлов из реестра → 3-C машина охвата A/B/C/D → 3-D движок роутера (+K4 + волны) → 3-E очередь одобрений.
|
||||
- Принцип: механика — чистый TDD (контроллер строит сам); рассуждение роутера — LLM, вызов мокается, проверки вокруг механические («умный не баран»). Портативность — без project-хардкода.
|
||||
|
||||
## Журнал
|
||||
|
||||
### 3-A (контракты скилов) — СОБРАНО
|
||||
- `[ВОПРОС]` Гард-рейлы G1–G3/G5–G6 в спеке поимённо НЕ перечислены (назван только G4 — хеш-страж дрейфа). Реализован G4 + механические валидаторы формы. Остальные G — уточнить при сборке 3-C/3-D или у владельца.
|
||||
- `[ДОПУЩЕНИЕ]` `PREVIEW_FORMS` = `none/outline/mockup/sample/dry-run/diagram` — выведен из духа L1, дословно в спеке не зафиксирован. Расширяемый.
|
||||
- `[ДОПУЩЕНИЕ]` Контракты лежат `docs/registry/contracts/*.contract.json` рядом с `nodes.yaml`. Массовое наполнение контрактов для всех ~86 узлов — отдельная задача данных, НЕ код 3-A (собраны 2 образца: own writing-plans + external operations:process-doc).
|
||||
- `[СТЫК]` Само-ослабление контракта (убрал нужду/критерий → блок + да владельца) — Машина 5, НЕ 3-A (только схема здесь).
|
||||
|
||||
### 3-B (граф узлов) — СОБРАНО
|
||||
- `[ДОПУЩЕНИЕ]` Явных рёбер «близнецы/конфликты» в схеме реестра НЕТ. Близнецы выведены = общий `subcategory`; связи-подсказки = со-членство в `chains`; конфликты = опциональное `attributes.conflicts_with` (пусто, если не задано). Free-text `boundaries` НЕ парсим (ненадёжно). Явные конфликт-рёбра — наполнение позже.
|
||||
- `[ДОПУЩЕНИЕ]` Члены цепочек вида `superpowers:brainstorming` могут не резолвиться в отдельный узел (суб-скилы superpowers не отдельные узлы) → в связях-подсказках пропускаются. Граф корректен для зарегистрированных узлов.
|
||||
|
||||
### 3-C (машина охвата) — СОБРАНО
|
||||
- `[ДОПУЩЕНИЕ]` Сопоставление need↔produce — по нормализованной строке (lower/trim; равенство или подстрока для просьб). Семантическое сопоставление («spec»≈«требования») — рассуждение роутера (3-D), НЕ механика. Машина охвата честно ловит ТОЧНЫЕ дыры/сироты/циклы.
|
||||
- `[ДОПУЩЕНИЕ]` Извлечение «просьб цели» из текста (C) — мягкий край, выполняется выше (роутер/владелец); 3-C принимает готовый массив просьб.
|
||||
|
||||
### 3-D (движок роутера) — границы автономной сборки (ВАЖНО для владельца)
|
||||
- `[РЕШЕНИЕ — отложено до Машины 4] K4-поправка к стене (узкое Write-исключение в decideMode)` НЕ активируется в этой сессии. Канон M3 §1 прямо: «ВКЛЮЧАТЬ исключение можно только ПОСЛЕ того, как K1/M4 реальны». Машина 4 (гейт-1/печать) ещё НЕ построена → включишь сейчас = либо инертно, либо дыра (фальшивый артефакт некому отбраковать). Поэтому 3-D строит движок роутера, но `enforce-supreme-gate.decideMode` НЕ трогает. Активация K4 — отдельным шагом после Машины 4.
|
||||
- `[РЕШЕНИЕ — отложено] Live-wiring роутера` (регистрация в settings.json + реальные вызовы Sonnet вместо моков) — шаг владельца (settings.json Claude'у закрыт) + режим D28 «сперва тихо, не вживую». 3-D строит и тестирует движок с МОКнутым llmCall (как router-classifier); живой вызов через `callAnthropicAPI` готов, но не регистрируется.
|
||||
- `[РЕШЕНИЕ — отложено] Перенос «волн» (дерево указателей шага)` в живой `enforce-supreme-gate.main()` НЕ делается сейчас (трогает рабочий код Машины 2 + указатель пока линейный достаточен — на прямых планах). Построен СТЕНДОВЫЙ модуль дерева-указателя (мех., оттестирован), готов к замене линейного при живом роутере. Live-замена — отдельный шаг.
|
||||
- `[ДОПУЩЕНИЕ] Нюх 5.3 / интервьюер 4.4 / самопроверка 5.4` — вшиты в system-инструкции брейнсторма роутера (рассуждение модели). Отдельные МЕХАНИЧЕСКИЕ детекторы «нюха на размытость» не строились жёстко (мягкий край, LLM-суждение). Механическая часть анти-обмана — валидатор трассы (пустой слот=флаг) + заземление (выдумка=отклонение).
|
||||
|
||||
### 3-E (очередь обучения) — СОБРАНО
|
||||
- `[РЕШЕНИЕ — отложено] Доставка в живую инфру` — врезка `statusSignal` в `tools/status-md-generator.mjs` + раздела «На одобрение» (`renderApprovalSection`) в `.claude/skills/brain-retro/SKILL.md` НЕ делается тихо ночью (owner-facing surface, режим D28). Рендер/сигнал — готовые функции, ждут врезки с одобрения.
|
||||
- `[ИНВАРИАНТ соблюдён] Hard-rule «наполнение только по да владельца, иначе никак»` — `enqueue` всегда pending; в фонд только `applyApprovalBatch` с явным approve; разведка rate=0 по умолчанию, проба=вопрос владельцу. Подтверждено инвариант-тестами.
|
||||
|
||||
---
|
||||
|
||||
## СВОДКА СБОРКИ МАШИНЫ 3 (2026-06-04, ночь) — ВСЕ 5 ПОД-ПЛАНОВ СОБРАНЫ
|
||||
Порядок выдержан: 3-A → 3-B → 3-C → 3-D → 3-E. Всё по TDD (тест→RED→прод→GREEN), контроллер строил сам. Финальная регрессия tools-only **2212 GREEN / 2 skipped**. 14 коммитов на ветке `worktree-brainrepo` (НЕ запушено — ждёт «да» владельца на push).
|
||||
|
||||
| Под-план | Модули | Тесты | Коммиты |
|
||||
|---|---|---|---|
|
||||
| 3-A контракты | skill-contract.mjs + skill-contract-registry.mjs + 2 образца | 28 | f82cefae, a905abd1, 53db0ee2 |
|
||||
| 3-B граф узлов | node-graph.mjs (поверх loadRegistry) | 20 | 750f406c, 699da97d |
|
||||
| 3-C охват A/B/C/D | coverage-machine.mjs | 19 | 8df8d056, 80ebec9e |
|
||||
| 3-D движок роутера | router-engine.mjs + step-pointer.mjs | 35 | 3a80bdde, 28b129ed, ed89028b |
|
||||
| 3-E очередь обучения | router-learning-queue.mjs + router-exploration.mjs | 19 | 4cb17fc4, dcf772ba, a27a848d |
|
||||
|
||||
**FOLLOW-UP для владельца (НЕ сделано осознанно — нужны Машина 4 / шаг владельца):**
|
||||
1. **Машина 4** (судья: гейт-1 K1 печать + гейт-2 K5 + апелляция + оценщик). Без неё K4-исключение, live-роутер, наполнение очереди — не активируются.
|
||||
2. **K4-поправка к стене** (узкое Write-исключение в `decideMode`) — включить ПОСЛЕ Машины 4.
|
||||
3. **Live-wiring роутера** — регистрация в settings.json (шаг владельца) + замена мокнутого llmCall на `callAnthropicAPI` + режим D28 «сперва тихо».
|
||||
4. **Перенос волн** — заменить линейный `step_ptr` в `enforce-supreme-gate.main()` на `step-pointer` (дерево).
|
||||
5. **Доставка 3-E** — врезать `statusSignal`/`renderApprovalSection` в STATUS-генератор + brain-retro SKILL.md.
|
||||
6. **Наполнение контрактов** — написать контракты для остальных ~84 узлов реестра (данные, не код).
|
||||
7. **Явные конфликт-рёбра** (`attributes.conflicts_with`) в реестре — наполнить (сейчас пусто → conflictsOf даёт []).
|
||||
8. **Push** ветки `worktree-brainrepo` в origin — по «да».
|
||||
|
||||
---
|
||||
|
||||
## АУДИТ МАШИНЫ 3 против канона (2026-06-05, утро) — находки + фикс-сет
|
||||
|
||||
Адверсари-сверка по факту (построчно, спека §2/§3 + решение C-13). Механический фундамент 3-A/3-B/3-C и hard-rule 3-E — логика цела, багов в алгоритмах нет; 2212 тестов зелёные. Бреши — на стыке промпта роутера со спекой + один недостроенный гард-рейл контракта.
|
||||
|
||||
### Самопоправка
|
||||
Утренняя пометка «G1–G6 не определены в спеке» — **СНЯТА как ошибочная**. G1–G6 полностью расписаны в решении **C-13** (`2026-06-04-router-mentor-m2-decisions-handoff.md`, стр. 116–121) — это шесть гард-рейлов адаптера ЧУЖИХ скилов. При сборке 3-A не сверился с источником.
|
||||
|
||||
### Расклад G1–G6 (адаптер чужих скилов)
|
||||
- **G1** этикетка про сам скил, без проектных фактов — ⚠️ держится дисциплиной + нейтральной схемой; механической проверки нет → **фикс 5 (опц.)**.
|
||||
- **G2** лениво (адаптер только для реально используемого) — ✅ соблюдено практикой (2 образца, не 86).
|
||||
- **G3** детерминированная диспетчеризация (есть+свежий→точно; нет/устарел→мягко, молча не доверять) — ⚠️ **частично**: куски есть (`buildRegistry`+дрейф→`soft-reasoning`), единой функции «по id → точно|мягко» нет → **фикс 4**.
|
||||
- **G4** хеш-страж дрейфа — ✅ построено (`checkContractDrift`).
|
||||
- **G5** честная рамка (мягкость→одноразовое авторство+ярлык+страж) — ✅ соблюдено конструкцией.
|
||||
- **G6** адаптер под журналом М1 + сверка судьёй — ⏸ ждёт Машину 1/4 (не зона 3-A).
|
||||
|
||||
### ФИКС-СЕТ (одобрено владельцем: блок А 1–4 + блок В 5)
|
||||
1. **Заземление семенных навыков** (3-B/3-D, `resolveNode`): резолв по префиксу до `:` ПОСЛЕ суффикса → `superpowers:writing-plans`→узел `superpowers` #19. Чинит L1-цепочку (0 рёбер) + роутер (отклонял свои навыки). Суффикс-резолв сохраняется (`superpowers:architecture-patterns`→#38).
|
||||
2. **look-ahead + контракты в промпт роутера** (3-D, `buildRouterPrompt`+`runRouter`): принимать контракты, тащить needs/key-decisions вызываемых навыков вперёд. Канон §3.
|
||||
3. **Нюх 5.3 + интервьюер 4.4 в текст промпта** (3-D): анти-обманная тройка (механически был только 5.2).
|
||||
4. **G3-диспетчер** (3-A, `skill-contract-registry`): `dispatchContract(registry, skill)` → `{mode:'exact'|'soft-reasoning'}`, отсутствует/дрейф → soft.
|
||||
5. **G1-страж** (3-A, `skill-contract`): `checkContractNeutrality(contract, {projectTerms})` — мягкая проверка против ИНЪЕЦИРУЕМОГО списка проектных терминов (портативно, без хардкода).
|
||||
|
||||
### Корректно отложено (не бреши)
|
||||
K4-поправка / live-wiring / перенос волн / доставка 3-E / G6 / наполнение контрактов ~84 узлов / конфликт-рёбра — ждут Машину 4 или шаг владельца.
|
||||
|
||||
---
|
||||
|
||||
## ОТЛОЖЕНО ПО МАШИНЕ 3 — ИТОГОВАЯ СВОДКА (зафиксировано 2026-06-05, после фикс-сета 1–5)
|
||||
|
||||
Машина 3 собрана и проаудирована; фикс-сет 1–5 влит (коммиты `14230814`→`b94f7d24`, регрессия 2222 GREEN). **Сознательно отложено** (каждое — ждёт Машину 4 или явный шаг владельца, НЕ брешь):
|
||||
|
||||
1. **Машина 4 (судья)** — гейт-1 (печать артефакта K1) + гейт-2 (план K5) + апелляция + оценщик. Корень: без неё не активируются K4 / live-роутер / наполнение очереди.
|
||||
2. **K4-поправка к стене** — узкое Write-исключение в `enforce-supreme-gate.decideMode`. Включать ТОЛЬКО после Машины 4 (канон M3 §1: фальшивый артефакт некому отбраковать до судьи).
|
||||
3. **Live-wiring роутера** — регистрация в `.claude/settings.json` (шаг владельца) + замена мокнутого `llmCall` на `callAnthropicAPI` + режим D28 «сперва тихо».
|
||||
4. **Перенос «волн»** — линейный `step_ptr` в `enforce-supreme-gate.main()` → дерево `step-pointer` (стендовый модуль готов, оттестирован).
|
||||
5. **Доставка 3-E** — `statusSignal` в `tools/status-md-generator.mjs` + `renderApprovalSection` в `.claude/skills/brain-retro/SKILL.md` (функции готовы, ждут врезки с одобрения).
|
||||
6. **G6** (адаптер чужого скила под журналом М1 + сверка судьёй) — зона Машин 1/4.
|
||||
7. **Наполнение контрактов** ~84 узлов реестра — данные, не код (собраны 2 образца).
|
||||
8. **Явные конфликт-рёбра** `attributes.conflicts_with` — данные (сейчас пусто → `conflictsOf` даёт []).
|
||||
9. **Push** ветки `worktree-brainrepo` в origin — по «да» владельца.
|
||||
|
||||
**Готово и НЕ требует возврата:** 3-A (схема+G1/G4-страж), 3-B (граф+заземление), 3-C (охват A/B/C/D), 3-D (движок+трасса+заземление+нюх/интервьюер/look-ahead), 3-E (очередь hard-rule+разведка rate=0).
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
# Машина 3 / под-план 3-A — Контракты скилов — план реализации
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax.
|
||||
> **NB сборки (урок M2):** субагент-исполнитель НЕ проходит TDD-гейт (его правка теста невидима хуку) → код строит САМ контроллер по TDD (тест→RED→прод→GREEN в одном ходе).
|
||||
|
||||
**Goal:** дать машинно-читаемую схему контракта скила `{needs/produces/constraints + preview-form/defaults/key-decisions/acceptance-criteria}`, механический валидатор формы и G4 хеш-страж дрейфа чужих скилов — фундамент для машины охвата 3-C (рёбра графа needs↔produces) и входа роутера 3-D.
|
||||
|
||||
**Architecture:** чистое ядро `tools/skill-contract.mjs` (схема + валидатор + нормализация + accessors needs/produces + G4 хеш-страж) + загрузчик `tools/skill-contract-registry.mjs` (чистый `buildRegistry` над записями + `loadRegistry` с диска, fs инъектируется — паттерн проекта). Без LLM: полнота/тип слотов — операции над структурой, не «ум ИИ» («умный не баран»). Портативность: никакого project-хардкода имён скилов в коде — контракты лежат данными на диске.
|
||||
|
||||
**Tech Stack:** Node.js ESM, `node:crypto` (sha256 для G4), vitest tools-only. Образец дисциплины — `tools/plan-lock.mjs` (Машина 2).
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Контекст исполнения (как M1/M2)
|
||||
- Worktree `brainrepo`, ветка `worktree-brainrepo`. Git — только `git -C "<worktree>"`, по одной команде.
|
||||
- Тесты: `npx vitest run --root ".claude/worktrees/brainrepo/app" --config vitest.config.tools.mjs <фильтр>` с `dangerouslyDisableSandbox=true`, через Bash (гейт ищет Bash-vitest для RED).
|
||||
- Тест-файлы — в `<worktree>\tools\` (конфиг резолвит `../tools/*.test.mjs`).
|
||||
- TDD-гейт прод-файла требует в одном ходе: Read этого плана прямыми слэшами + правка теста + Bash-vitest RED, потом прод.
|
||||
- Тест-файлы писать ЦЕЛИКОМ через Write (real-test-verifier блокирует голую правку импорта без `expect`).
|
||||
|
||||
## Границы 3-A (что НЕ здесь)
|
||||
- Чтение контрактов в машину охвата A/B/C/D, look-ahead, L-ядро роутера → **3-D/3-C**.
|
||||
- Защита контракта от само-ослабления (убрал нужду/критерий → блок + да владельца + diff, анти-отрава) → **Машина 5** (здесь только схема, не гейт).
|
||||
- Массовое НАПИСАНИЕ контрактов для всех ~86 узлов → задача наполнения (данные), не код 3-A. Здесь — машинерия + 2 образца (own + external).
|
||||
|
||||
---
|
||||
|
||||
## Структура файлов
|
||||
|
||||
**Создаём:**
|
||||
- `tools/skill-contract.mjs` — схема (`CONTRACT_FIELDS`, `PREVIEW_FORMS`), `validateContract`, `normalizeContract`, `needsOf`/`producesOf`, `contractHash`, `checkContractDrift` (G4).
|
||||
- `tools/skill-contract-registry.mjs` — `buildRegistry` (чистый, над записями) + `loadRegistry` (диск, fs инъектируется).
|
||||
- Тесты: `tools/skill-contract.test.mjs`, `tools/skill-contract-registry.test.mjs`.
|
||||
- Образцы данных: `docs/registry/contracts/writing-plans.contract.json` (own), `docs/registry/contracts/operations-process-doc.contract.json` (external, с `source{version,hash,path}`).
|
||||
|
||||
**Порядок:** схема+валидатор (Task 1) → нормализация+accessors (Task 2) → G4 хеш-страж (Task 3) → buildRegistry (Task 4) → loadRegistry+диск (Task 5) → образцы+инварианты (Task 6).
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Схема контракта + механический валидатор формы
|
||||
|
||||
**Files:** Create `tools/skill-contract.mjs`, `tools/skill-contract.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Падающий тест** — `tools/skill-contract.test.mjs`
|
||||
|
||||
```js
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { CONTRACT_FIELDS, PREVIEW_FORMS, validateContract } from './skill-contract.mjs';
|
||||
|
||||
const OWN = {
|
||||
skill: 'writing-plans', kind: 'own',
|
||||
needs: ['spec'], produces: ['implementation-plan'], constraints: ['no code'],
|
||||
'preview-form': 'outline', defaults: ['bite-sized tasks'],
|
||||
'key-decisions': ['file structure'], 'acceptance-criteria': ['each step 2-5 min'],
|
||||
};
|
||||
const EXT = {
|
||||
skill: 'operations:process-doc', kind: 'external',
|
||||
needs: ['as-is process'], produces: ['process doc'], constraints: [],
|
||||
'preview-form': 'none', defaults: [], 'key-decisions': [], 'acceptance-criteria': ['doc covers all steps'],
|
||||
source: { version: '1.2.0', hash: 'a'.repeat(64), path: 'x/SKILL.md' },
|
||||
};
|
||||
|
||||
describe('CONTRACT_FIELDS / PREVIEW_FORMS', () => {
|
||||
it('canonical fields present (C-13 + L-поля)', () => {
|
||||
for (const f of ['skill','kind','needs','produces','constraints','preview-form','defaults','key-decisions','acceptance-criteria'])
|
||||
expect(CONTRACT_FIELDS).toContain(f);
|
||||
});
|
||||
it('preview-form includes none', () => { expect(PREVIEW_FORMS).toContain('none'); });
|
||||
});
|
||||
|
||||
describe('validateContract (форма, без LLM)', () => {
|
||||
it('valid own contract → ok', () => { expect(validateContract(OWN)).toEqual({ ok: true, errors: [] }); });
|
||||
it('valid external contract → ok', () => { expect(validateContract(EXT).ok).toBe(true); });
|
||||
it('non-object → error', () => { expect(validateContract(null).ok).toBe(false); });
|
||||
it('empty skill → error', () => { expect(validateContract({ ...OWN, skill: '' }).ok).toBe(false); });
|
||||
it('bad kind → error', () => { expect(validateContract({ ...OWN, kind: 'maybe' }).ok).toBe(false); });
|
||||
it('needs not string-array → error', () => { expect(validateContract({ ...OWN, needs: 'spec' }).ok).toBe(false); });
|
||||
it('bad preview-form → error', () => { expect(validateContract({ ...OWN, 'preview-form': 'fancy' }).ok).toBe(false); });
|
||||
it('external without source → error', () => {
|
||||
const { source, ...noSrc } = EXT; expect(validateContract(noSrc).ok).toBe(false);
|
||||
});
|
||||
it('external with bad hash → error', () => {
|
||||
expect(validateContract({ ...EXT, source: { version: '1', hash: 'short' } }).ok).toBe(false);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: RED** — `npx vitest run --root ".claude/worktrees/brainrepo/app" --config vitest.config.tools.mjs skill-contract` → FAIL (import).
|
||||
|
||||
- [ ] **Step 3: Реализация** — `tools/skill-contract.mjs`
|
||||
|
||||
```js
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* skill-contract — схема контракта скила (C-13 + L-серия) + механический
|
||||
* валидатор формы + G4 хеш-страж дрейфа чужих скилов. Чистый модуль (без LLM):
|
||||
* полнота/тип слотов — операции над структурой («умный не баран»). Фундамент
|
||||
* для машины охвата 3-C (needs/produces = рёбра графа) и входа роутера 3-D.
|
||||
*/
|
||||
import { createHash } from 'node:crypto';
|
||||
|
||||
// Канонические поля (C-13 базовые needs/produces/constraints + L-поля).
|
||||
export const CONTRACT_FIELDS = Object.freeze([
|
||||
'skill', 'kind', 'needs', 'produces', 'constraints',
|
||||
'preview-form', 'defaults', 'key-decisions', 'acceptance-criteria',
|
||||
]);
|
||||
|
||||
// L1 preview-form — закрытый список форм дешёвого образца ('none' для мелочи).
|
||||
export const PREVIEW_FORMS = Object.freeze(['none', 'outline', 'mockup', 'sample', 'dry-run', 'diagram']);
|
||||
|
||||
const ARRAY_FIELDS = Object.freeze(['needs', 'produces', 'constraints', 'defaults', 'key-decisions', 'acceptance-criteria']);
|
||||
|
||||
function isStringArray(v) { return Array.isArray(v) && v.every((x) => typeof x === 'string'); }
|
||||
|
||||
/**
|
||||
* Механический валидатор контракта → {ok, errors[]}. Проверяет ФОРМУ (наличие/тип
|
||||
* полей, enum preview-form, source{version,hash} у external), НЕ «полноту под
|
||||
* задачу» (это машина охвата 3-C).
|
||||
*/
|
||||
export function validateContract(c) {
|
||||
if (!c || typeof c !== 'object') return { ok: false, errors: ['contract is not an object'] };
|
||||
const errors = [];
|
||||
if (typeof c.skill !== 'string' || !c.skill.trim()) errors.push('skill: non-empty string required');
|
||||
if (c.kind !== 'own' && c.kind !== 'external') errors.push("kind: must be 'own' or 'external'");
|
||||
for (const f of ARRAY_FIELDS) if (!isStringArray(c[f])) errors.push(`${f}: array of strings required`);
|
||||
if (!PREVIEW_FORMS.includes(c['preview-form'])) errors.push(`preview-form: one of ${PREVIEW_FORMS.join('|')}`);
|
||||
if (c.kind === 'external') {
|
||||
const s = c.source;
|
||||
if (!s || typeof s !== 'object') errors.push('external: source{version,hash} required');
|
||||
else {
|
||||
if (typeof s.version !== 'string' || !s.version.trim()) errors.push('source.version: non-empty string required');
|
||||
if (typeof s.hash !== 'string' || !/^[0-9a-f]{64}$/.test(s.hash)) errors.push('source.hash: sha256 hex required');
|
||||
}
|
||||
}
|
||||
return { ok: errors.length === 0, errors };
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: GREEN** — прогон → PASS.
|
||||
- [ ] **Step 5: Commit** — `git -C "<worktree>" add tools/skill-contract.mjs tools/skill-contract.test.mjs` затем `git -C "<worktree>" commit -m "feat(m3-a): skill-contract schema + mechanical form validator (C-13/L)"`
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Нормализация + accessors needs/produces
|
||||
|
||||
**Files:** Modify `tools/skill-contract.mjs`, `tools/skill-contract.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Падающие тесты (добавить)**
|
||||
|
||||
```js
|
||||
import { normalizeContract, needsOf, producesOf } from './skill-contract.mjs';
|
||||
|
||||
describe('normalizeContract', () => {
|
||||
it('missing arrays → [] and preview-form → none', () => {
|
||||
const n = normalizeContract({ skill: ' x ', kind: 'own' });
|
||||
expect(n.skill).toBe('x');
|
||||
for (const f of ['needs','produces','constraints','defaults','key-decisions','acceptance-criteria']) expect(n[f]).toEqual([]);
|
||||
expect(n['preview-form']).toBe('none');
|
||||
});
|
||||
it('does not mutate input', () => {
|
||||
const raw = { skill: 'x', kind: 'own' };
|
||||
normalizeContract(raw); expect(raw.needs).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('needsOf / producesOf', () => {
|
||||
it('return copies of arrays', () => {
|
||||
const c = { needs: ['a'], produces: ['b'] };
|
||||
expect(needsOf(c)).toEqual(['a']); expect(producesOf(c)).toEqual(['b']);
|
||||
needsOf(c).push('z'); expect(c.needs).toEqual(['a']);
|
||||
});
|
||||
it('missing → []', () => { expect(needsOf({})).toEqual([]); expect(producesOf({})).toEqual([]); });
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: RED.**
|
||||
- [ ] **Step 3: Реализация (дописать в `tools/skill-contract.mjs`)**
|
||||
|
||||
```js
|
||||
/** Нормализация формы перед чтением/валидацией: отсутствующие массивы → [],
|
||||
* preview-form → 'none', trim skill. Не «исправляет» невалидное — только форма. */
|
||||
export function normalizeContract(raw) {
|
||||
const c = { ...(raw || {}) };
|
||||
if (typeof c.skill === 'string') c.skill = c.skill.trim();
|
||||
for (const f of ARRAY_FIELDS) if (c[f] == null) c[f] = [];
|
||||
if (c['preview-form'] == null) c['preview-form'] = 'none';
|
||||
return c;
|
||||
}
|
||||
|
||||
/** Нужды контракта (копия) — рёбра графа 3-C. */
|
||||
export function needsOf(c) { return Array.isArray(c?.needs) ? [...c.needs] : []; }
|
||||
/** Что производит (копия) — рёбра графа 3-C. */
|
||||
export function producesOf(c) { return Array.isArray(c?.produces) ? [...c.produces] : []; }
|
||||
```
|
||||
|
||||
- [ ] **Step 4: GREEN.**
|
||||
- [ ] **Step 5: Commit** — `git -C "<worktree>" commit -am "feat(m3-a): normalizeContract + needs/produces accessors"`
|
||||
|
||||
---
|
||||
|
||||
## Task 3: G4 — хеш-страж дрейфа чужого скила
|
||||
|
||||
**Files:** Modify `tools/skill-contract.mjs`, `tools/skill-contract.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Падающие тесты (добавить)**
|
||||
|
||||
```js
|
||||
import { contractHash, checkContractDrift } from './skill-contract.mjs';
|
||||
|
||||
describe('contractHash', () => {
|
||||
it('sha256 hex, deterministic', () => {
|
||||
expect(contractHash('abc')).toMatch(/^[0-9a-f]{64}$/);
|
||||
expect(contractHash('abc')).toBe(contractHash('abc'));
|
||||
expect(contractHash('abc')).not.toBe(contractHash('abd'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkContractDrift (G4)', () => {
|
||||
const ext = (hash) => ({ skill: 's', kind: 'external', source: { version: '1', hash } });
|
||||
it('own contract → дрейф не сторожится (ok)', () => {
|
||||
expect(checkContractDrift({ contract: { skill: 's', kind: 'own' }, currentContent: 'anything' }).ok).toBe(true);
|
||||
});
|
||||
it('external, hash совпал → ok, not drifted', () => {
|
||||
const h = contractHash('SKILL body');
|
||||
expect(checkContractDrift({ contract: ext(h), currentContent: 'SKILL body' })).toMatchObject({ ok: true, drifted: false });
|
||||
});
|
||||
it('external, содержание изменилось → drifted + fallback soft-reasoning', () => {
|
||||
const h = contractHash('old body');
|
||||
const r = checkContractDrift({ contract: ext(h), currentContent: 'NEW body' });
|
||||
expect(r.drifted).toBe(true); expect(r.ok).toBe(false); expect(r.fallback).toBe('soft-reasoning');
|
||||
});
|
||||
it('external без сохранённого отпечатка → drifted (нельзя доверять)', () => {
|
||||
const r = checkContractDrift({ contract: { skill: 's', kind: 'external', source: { version: '1' } }, currentContent: 'x' });
|
||||
expect(r.drifted).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: RED.**
|
||||
- [ ] **Step 3: Реализация (дописать)**
|
||||
|
||||
```js
|
||||
/** sha256 содержания — отпечаток чужого скила для G4. */
|
||||
export function contractHash(content) {
|
||||
return createHash('sha256').update(String(content ?? '')).digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* G4 — хеш-страж дрейфа (C-13 ключевой гард-рейл): сверяет сохранённый отпечаток
|
||||
* (contract.source.hash) с актуальным содержанием чужого SKILL.md. Расхождение →
|
||||
* drifted=true + fallback 'soft-reasoning' (откат на мягкое рассуждение судьи).
|
||||
* Своим (kind='own') не сторожим — контракт пишем сами.
|
||||
*/
|
||||
export function checkContractDrift({ contract, currentContent }) {
|
||||
if (!contract || contract.kind !== 'external')
|
||||
return { ok: true, drifted: false, reason: 'own/нет внешнего источника — дрейф не сторожится' };
|
||||
const stored = contract.source?.hash;
|
||||
const actual = contractHash(currentContent);
|
||||
if (!stored) return { ok: false, drifted: true, reason: 'нет сохранённого отпечатка внешнего скила', fallback: 'soft-reasoning' };
|
||||
if (stored !== actual)
|
||||
return { ok: false, drifted: true, reason: `дрейф чужого скила: ${stored.slice(0, 12)}… ≠ ${actual.slice(0, 12)}…`, fallback: 'soft-reasoning' };
|
||||
return { ok: true, drifted: false, reason: 'отпечаток совпал' };
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: GREEN.**
|
||||
- [ ] **Step 5: Commit** — `git -C "<worktree>" commit -am "feat(m3-a): G4 drift-guard (contractHash + checkContractDrift)"`
|
||||
|
||||
---
|
||||
|
||||
## Task 4: buildRegistry — чистая сборка набора контрактов
|
||||
|
||||
**Files:** Create `tools/skill-contract-registry.mjs`, `tools/skill-contract-registry.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Падающий тест** — `tools/skill-contract-registry.test.mjs`
|
||||
|
||||
```js
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { buildRegistry } from './skill-contract-registry.mjs';
|
||||
|
||||
const own = (skill) => ({ skill, kind: 'own', needs: [], produces: [], constraints: [], 'preview-form': 'none', defaults: [], 'key-decisions': [], 'acceptance-criteria': [] });
|
||||
|
||||
describe('buildRegistry (чистый)', () => {
|
||||
it('валидные контракты собираются', () => {
|
||||
const r = buildRegistry([{ contract: own('a') }, { contract: own('b') }]);
|
||||
expect(r.contracts).toHaveLength(2); expect(r.errors).toEqual([]); expect(r.driftFlags).toEqual([]);
|
||||
});
|
||||
it('невалидный контракт → в errors, не в contracts', () => {
|
||||
const r = buildRegistry([{ contract: { skill: '', kind: 'own' } }]);
|
||||
expect(r.contracts).toHaveLength(0); expect(r.errors).toHaveLength(1);
|
||||
});
|
||||
it('дубль skill → ошибка, первый остаётся', () => {
|
||||
const r = buildRegistry([{ contract: own('a') }, { contract: own('a') }]);
|
||||
expect(r.contracts).toHaveLength(1); expect(r.errors[0].errors[0]).toMatch(/duplicate/);
|
||||
});
|
||||
it('external дрейф → в driftFlags, контракт всё равно загружен (с пометкой)', () => {
|
||||
const ext = { skill: 'e', kind: 'external', needs: [], produces: [], constraints: [], 'preview-form': 'none', defaults: [], 'key-decisions': [], 'acceptance-criteria': [], source: { version: '1', hash: 'f'.repeat(64) } };
|
||||
const r = buildRegistry([{ contract: ext, currentContent: 'changed body' }]);
|
||||
expect(r.contracts).toHaveLength(1); expect(r.driftFlags).toHaveLength(1); expect(r.driftFlags[0].skill).toBe('e');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: RED.**
|
||||
- [ ] **Step 3: Реализация** — `tools/skill-contract-registry.mjs`
|
||||
|
||||
```js
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* skill-contract-registry — сборка набора контрактов скилов. Чистый buildRegistry
|
||||
* (над записями {contract, currentContent?}) + loadRegistry (диск, fs инъектируется).
|
||||
* Валидирует каждый, для external сверяет дрейф G4. Вход для машины охвата 3-C.
|
||||
*/
|
||||
import fsDefault from 'node:fs';
|
||||
import { validateContract, normalizeContract, checkContractDrift } from './skill-contract.mjs';
|
||||
|
||||
/** Чистая сборка: валидирует, ловит дубли, помечает дрейф external. */
|
||||
export function buildRegistry(entries) {
|
||||
const contracts = [], errors = [], driftFlags = [], seen = new Set();
|
||||
for (const e of entries || []) {
|
||||
const c = normalizeContract(e.contract);
|
||||
const v = validateContract(c);
|
||||
if (!v.ok) { errors.push({ skill: c.skill || '(?)', errors: v.errors }); continue; }
|
||||
if (seen.has(c.skill)) { errors.push({ skill: c.skill, errors: ['duplicate skill contract'] }); continue; }
|
||||
seen.add(c.skill);
|
||||
if (c.kind === 'external') {
|
||||
const d = checkContractDrift({ contract: c, currentContent: e.currentContent });
|
||||
if (d.drifted) driftFlags.push({ skill: c.skill, reason: d.reason, fallback: d.fallback });
|
||||
}
|
||||
contracts.push(c);
|
||||
}
|
||||
return { contracts, errors, driftFlags };
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: GREEN.**
|
||||
- [ ] **Step 5: Commit** — `git -C "<worktree>" add tools/skill-contract-registry.mjs tools/skill-contract-registry.test.mjs` затем commit `"feat(m3-a): buildRegistry — validate + dedupe + drift-flag"`
|
||||
|
||||
---
|
||||
|
||||
## Task 5: loadRegistry — загрузка с диска (fs инъектируется)
|
||||
|
||||
**Files:** Modify `tools/skill-contract-registry.mjs`, `tools/skill-contract-registry.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Падающие тесты (добавить)**
|
||||
|
||||
```js
|
||||
import { loadRegistry } from './skill-contract-registry.mjs';
|
||||
|
||||
function memFs(map) {
|
||||
return {
|
||||
readdirSync: () => Object.keys(map).map((p) => p.split('/').pop()),
|
||||
readFileSync: (p) => { const k = String(p); if (!(k in map)) { const e = new Error('ENOENT'); e.code='ENOENT'; throw e; } return map[k]; },
|
||||
};
|
||||
}
|
||||
|
||||
describe('loadRegistry (диск, fs инъектируется)', () => {
|
||||
it('читает *.contract.json, валидирует, external тянет source.path для дрейфа', () => {
|
||||
const ownC = JSON.stringify({ skill: 'wp', kind: 'own', needs: [], produces: [], constraints: [], 'preview-form': 'none', defaults: [], 'key-decisions': [], 'acceptance-criteria': [] });
|
||||
const extC = JSON.stringify({ skill: 'pd', kind: 'external', needs: [], produces: [], constraints: [], 'preview-form': 'none', defaults: [], 'key-decisions': [], 'acceptance-criteria': [], source: { version: '1', hash: '0'.repeat(64), path: '/skills/pd/SKILL.md' } });
|
||||
const map = { '/c/wp.contract.json': ownC, '/c/pd.contract.json': extC, '/skills/pd/SKILL.md': 'current body' };
|
||||
const r = loadRegistry({ dir: '/c', fsImpl: memFs(map) });
|
||||
expect(r.contracts).toHaveLength(2);
|
||||
expect(r.driftFlags).toHaveLength(1); // hash 0..0 ≠ хеш 'current body'
|
||||
});
|
||||
it('игнорирует не-.contract.json', () => {
|
||||
const map = { '/c/readme.md': 'x' };
|
||||
const r = loadRegistry({ dir: '/c', fsImpl: memFs(map) });
|
||||
expect(r.contracts).toEqual([]); expect(r.errors).toEqual([]);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
> **NB по memFs:** `readdirSync` отдаёт basename'ы, `readFileSync` ждёт полный путь `${dir}/${file}`. В `loadRegistry` собираем путь как `` `${dir}/${f}` ``.
|
||||
|
||||
- [ ] **Step 2: RED.**
|
||||
- [ ] **Step 3: Реализация (дописать в `tools/skill-contract-registry.mjs`)**
|
||||
|
||||
```js
|
||||
/** Загрузка с диска: dir/*.contract.json. Для external читает source.path
|
||||
* (актуальный SKILL.md) для дрейф-сверки G4, если путь задан и доступен. */
|
||||
export function loadRegistry({ dir, fsImpl = fsDefault }) {
|
||||
const files = fsImpl.readdirSync(dir).filter((f) => f.endsWith('.contract.json'));
|
||||
const entries = files.map((f) => {
|
||||
const raw = JSON.parse(fsImpl.readFileSync(`${dir}/${f}`, 'utf8'));
|
||||
let currentContent;
|
||||
if (raw && raw.kind === 'external' && raw.source && raw.source.path) {
|
||||
try { currentContent = fsImpl.readFileSync(raw.source.path, 'utf8'); } catch { currentContent = undefined; }
|
||||
}
|
||||
return { contract: raw, currentContent };
|
||||
});
|
||||
return buildRegistry(entries);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: GREEN.**
|
||||
- [ ] **Step 5: Commit** — `git -C "<worktree>" commit -am "feat(m3-a): loadRegistry — disk loader, external pulls source.path for G4"`
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Образцы контрактов + инварианты 3-A + регрессия
|
||||
|
||||
**Files:** Create `docs/registry/contracts/writing-plans.contract.json`, `docs/registry/contracts/operations-process-doc.contract.json`, `tools/m3a-contract-invariants.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Образец own** — `docs/registry/contracts/writing-plans.contract.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "writing-plans",
|
||||
"kind": "own",
|
||||
"needs": ["spec or requirements", "file structure decisions"],
|
||||
"produces": ["implementation-plan with bite-sized TDD tasks"],
|
||||
"constraints": ["only writes the plan file", "no code, no reads beyond plan authoring"],
|
||||
"preview-form": "outline",
|
||||
"defaults": ["one action per step (2-5 min)", "test->RED->code->GREEN->commit"],
|
||||
"key-decisions": ["file structure / decomposition", "task granularity"],
|
||||
"acceptance-criteria": ["every step has concrete content (no placeholders)", "types consistent across tasks"]
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Образец external** — `docs/registry/contracts/operations-process-doc.contract.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "operations:process-doc",
|
||||
"kind": "external",
|
||||
"needs": ["as-is process description"],
|
||||
"produces": ["structured process documentation"],
|
||||
"constraints": ["marketplace skill — outputs doc only"],
|
||||
"preview-form": "none",
|
||||
"defaults": ["follow operations plugin process-doc template"],
|
||||
"key-decisions": ["scope of the process being documented"],
|
||||
"acceptance-criteria": ["doc covers all process steps and owners"],
|
||||
"source": { "version": "1.2.0", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
|
||||
}
|
||||
```
|
||||
|
||||
> **NB:** `hash` образца — плейсхолдер нулями + `path: ""` (пустой → дрейф-сверка пропускается, `currentContent` undefined → external без отпечатка-сверки даст «нет содержания»: при пустом path loadRegistry не читает файл, buildRegistry получает `currentContent: undefined` → `contractHash(undefined)` ≠ нули → дрейф-флаг). Чтобы образец грузился без шумного дрейф-флага в инварианте, в тесте подаём его через `buildRegistry` с `currentContent`, чей хеш = нули НЕ совпадёт — поэтому инвариант проверяет, что образец ВАЛИДЕН по форме (а не отсутствие дрейфа). Реальный отпечаток проставляется при наполнении (вне 3-A).
|
||||
|
||||
- [ ] **Step 3: Инвариант-тест** — `tools/m3a-contract-invariants.test.mjs`
|
||||
|
||||
```js
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { validateContract } from './skill-contract.mjs';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const contractsDir = join(here, '..', 'docs', 'registry', 'contracts');
|
||||
|
||||
describe('Машина 3-A — инварианты контрактов', () => {
|
||||
it('образец own (writing-plans) валиден по форме', () => {
|
||||
const c = JSON.parse(readFileSync(join(contractsDir, 'writing-plans.contract.json'), 'utf8'));
|
||||
expect(validateContract(c)).toEqual({ ok: true, errors: [] });
|
||||
});
|
||||
it('образец external (operations:process-doc) валиден по форме', () => {
|
||||
const c = JSON.parse(readFileSync(join(contractsDir, 'operations-process-doc.contract.json'), 'utf8'));
|
||||
expect(validateContract(c).ok).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
> **NB путь:** тест-файл лежит в `<worktree>/tools/`, контракты — в `<worktree>/docs/registry/contracts/`. `join(here, '..', 'docs', ...)` поднимается из `tools/` в корень worktree. Проверить фактический `here` при RED; если конфиг резолвит из другого корня — поправить относительный путь (записать в журнал, если разойдётся).
|
||||
|
||||
- [ ] **Step 4: GREEN** — `npx vitest run --root ".claude/worktrees/brainrepo/app" --config vitest.config.tools.mjs m3a-contract-invariants`
|
||||
|
||||
- [ ] **Step 5: Полная регрессия 3-A + всё** — `npx vitest run --root ".claude/worktrees/brainrepo/app" --config vitest.config.tools.mjs` → все прежние + M1 + M2 + 3-A зелёные.
|
||||
|
||||
- [ ] **Step 6: Commit** — `git -C "<worktree>" add docs/registry/contracts/ tools/m3a-contract-invariants.test.mjs` затем commit `"test(m3-a): contract sample fixtures (own+external) + 3-A invariants"`
|
||||
|
||||
---
|
||||
|
||||
## Self-Review (против канона §2 3-A + roadmap C-13)
|
||||
- **Схема `{needs/produces/constraints + preview-form/defaults/key-decisions/acceptance-criteria}`** → Task 1 `CONTRACT_FIELDS` + `validateContract`. ✅
|
||||
- **Свои контракты пишем сами** → формат own + образец writing-plans (Task 6). ✅
|
||||
- **Чужие — тонкий адаптер + G4 хеш-страж дрейфа (версия+хеш, расхождение → флаг + откат на мягкое рассуждение)** → Task 3 `checkContractDrift` (`fallback: 'soft-reasoning'`) + external `source{version,hash,path}` + образец external (Task 6). ✅
|
||||
- **Фундамент для машины охвата 3-C** → `needsOf`/`producesOf` (Task 2) + `buildRegistry`/`loadRegistry` (Task 4/5) дают набор контрактов с рёбрами. ✅
|
||||
- **Без LLM, «умный не баран»** → весь модуль чистый, операции над структурой. ✅
|
||||
- **Портативность** → имена скилов не зашиты в код, лежат данными в `docs/registry/contracts/`. ✅
|
||||
- **[ВОПРОС записан в журнал]** G1–G3/G5–G6 в спеке поимённо НЕ перечислены (назван только G4) → реализован G4 + механические валидаторы формы; остальные гард-рейлы уточнить у владельца/при сборке 3-C.
|
||||
- **[ДОПУЩЕНИЕ]** PREVIEW_FORMS список (`none/outline/mockup/sample/dry-run/diagram`) — выведен из духа L1, не зафиксирован дословно в спеке → расширяемый.
|
||||
- **[ДОПУЩЕНИЕ]** контракты хранятся как `docs/registry/contracts/*.contract.json` рядом с `docs/registry/nodes.yaml` → согласовать с 3-B (граф узлов из реестра).
|
||||
|
||||
**Заглушек нет. Само-ослабление контракта (M5) и массовое наполнение — вне 3-A (отмечено).**
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user