Compare commits

..

7 Commits

Author SHA1 Message Date
Дмитрий 6ce2f0058d fix(router-gate): session-lock skips readonly Bash (scope calibration)
The parallel-session-lock fired on every PreToolUse tool, blocking even
readonly Bash (git status/log/diff, cat, grep, ls) from a peer session.
The lock's purpose is to serialize concurrent FILE MUTATION on the same
worktree; readonly commands mutate nothing, so they are outside that scope.

isReadonlyBashEvent() reuses the router-gate Bash classifier (an allow-verdict
whose reason is readonly/reading), mirroring the LLM-judge readonly
calibration. main() short-circuits readonly Bash to allow without
acquiring/blocking. Mutating tools, git commit/push, dangerous Bash, and
every non-Bash tool still acquire/check the lock — same-worktree mutation
serialization is unchanged (scope fix, NOT a discipline drop).

TDD: +6 unit tests. Full tools-vitest 2038 passed / 2 skipped.
2026-06-01 07:46:26 +03:00
Дмитрий d35fefddd9 ci(a11y): bump Pa11y workflow Node 20 -> 22 (cspell@10 engine requirement)
The a11y (Pa11y live) PR check failed at "Install root JS deps": root `npm ci`
hits EBADENGINE because @cspell/cspell-*@10.0.0 require Node >=22.18.0 while the
workflow pinned Node 20. Pre-existing mismatch (cspell ^10 predates this branch
and fails identically on main), unrelated to the discipline-guard hook changes.
Node 22 satisfies both the repo engines (>=20) and cspell (>=22.18).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:00:05 +03:00
Дмитрий e56ddd6a1b fix(router-gate): coverage line honors cross-turn active skill (verify + remind)
Backlog item G. The `coverage:` line under-reported a skill chosen in a PRIOR turn:
enforce-coverage-verify credited channel=skill only if the Skill tool ran in the
CURRENT turn, so an honest `skill:X` continuation line was BLOCKED -> the controller
learned to under-report as direct/chain. Two-sided systemic fix, no weakening:

- enforce-coverage-verify: decide() also accepts skill:X when X was invoked anywhere
  earlier in THIS session (new priorSkillNames param; main() collects them via
  sessionToolUses). Still unforgeable -- a real Skill tool_use must exist in the
  transcript. The only residual is possibly-stale attribution, far better than the
  forced dishonest direct-reporting it replaces.
- enforce-prompt-injection: the §17 reminder now lists active skills carried over
  from earlier turns (read from the transcript) and tells the controller to report
  `coverage: skill:<name>` when work continues under one -- the proactive half, so
  the correct line is not merely allowed but prompted.

TDD: RED -> GREEN per behavior. tools-vitest 2032 passed / 2 skipped.
Plan docs/superpowers/plans/2026-05-31-discipline-guard-backlog.md (item G).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:37:44 +03:00
Дмитрий 53407a77cd feat(router-gate): tdd-gate credits delegated (subagent) TDD + transcript write-deny
Closes the TDD-gate cross-actor gap: when a subagent (spawned by a Task in the
controller's current turn) writes the failing test and confirms RED, the
controller's subsequent production edit was falsely blocked because the gate only
scanned the controller's own turn. Net strengthening, no discipline weakened.

- Part 1 (enforce-runtime-write-deny): block the Write tool from any
  ~/.claude/projects/**/*.jsonl (session/subagent transcripts). Memory *.md there
  stays writable (never matches .jsonl$). Resolving normalizer defeats ./.. evasion.
  This makes the agent-<id>.jsonl that Part 2 trusts unforgeable (it was the last
  ungated write channel; Bash/PowerShell/Read gates already covered it).
- Part 2 (enforce-tdd-gate): decide() also credits a subagent's matching test edit
  + RED via a new subagentEntriesList. turnTaskAgentIds() reads the hex agentId from
  the harness-written Task tool_result (the controller cannot forge its own
  tool_result; hex-only match blocks "agentId: ../../x" path traversal).
  subagentTranscriptPaths() derives <dir>/<controller-session>/subagents/agent-<id>.jsonl.
  main() reads them best-effort (missing/unreadable -> no extra credit = stricter).

No new weakening: a delegated subagent doing real TDD is legitimate; the only
forgery vector (overwriting the agent jsonl) is closed by Part 1. Existing
controller-turn behaviour is preserved (empty subagent list == old logic).

OWNER (settings.json, Claude can't edit it): enforce-tdd-gate is already a
registered PreToolUse hook -> Part 2 goes live on merge. enforce-runtime-write-deny
must be registered on PreToolUse(Edit|Write|MultiEdit|NotebookEdit) for Part 1 to be live.

TDD: RED -> GREEN per behavior. tools-vitest 2027 passed / 2 skipped.
Backlog item C (=Z); plan docs/superpowers/plans/2026-05-31-discipline-guard-backlog.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:18:44 +03:00
Дмитрий 6577c04a1f fix(router-gate): session-lock hygiene — clearer block message + stale-lock prune
Closes the remaining parallel-session-lock remarks on top of the keying fix
(7a469dc9), with NO weakening of same-worktree serialization:

- D: the block message now identifies the holder by its STABLE session_id and
  marks the recorded pid as transient ("may change between attempts"). Chasing
  the pid is what led to closing the wrong session. Decision logic is unchanged
  (text only) — existing /pid N/ triage assertion still holds.
- B: pruneStaleLocks() best-effort deletes leaked lock files that are ALREADY
  stale by the shared isStale() definition (now exported from the pure module —
  single source of truth). Active within-TTL locks are never touched, so the
  serialization guarantee is not weakened. Wired into the PreToolUse branch of
  main(), wrapped so hygiene can never break the gate (fail-open).
- C (no code): release-on-SessionEnd needs only a settings.json registration
  (owner action) — the existing !tool_name branch already releases. Documented
  in the plan. Until then, leaked locks self-heal via B + the 5-min TTL takeover.

TDD: RED -> GREEN per behavior. tools-vitest 2014 passed / 2 skipped.
Backlog items B/C/D; plan docs/superpowers/plans/2026-05-31-discipline-guard-backlog.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:43:03 +03:00
Дмитрий 7a469dc913 fix(router-gate): key session-lock by session work-tree root, not hook cwd
enforce-parallel-session-lock keyed the lock on the hook's process.cwd(),
which collapses to the main repo dir after a session resume — so sessions in
DIFFERENT git worktrees shared one lock and false-blocked each other (observed:
a brainrepo-worktree session blocked launching agents by a discipline-guard
session). New resolveWorkspacePath() keys on the session's stable cwd
(event.cwd) resolved to the git work-tree root (git -C <cwd> rev-parse
--show-toplevel), with fallback to process.cwd() so behaviour never regresses
when event.cwd is absent. Same-worktree concurrency stays serialized
(unchanged) — discipline not weakened; only cross-worktree false-blocks fixed.

TDD: RED (5 resolveWorkspacePath cases) -> GREEN -> tools-vitest 2003 passed /
2 skipped. Backlog item F; plan
docs/superpowers/plans/2026-05-31-discipline-guard-backlog.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:02:32 +03:00
Дмитрий be4e1a6123 feat(router-gate): whitelist npm ci in SAFE_EXACT (worktree dep restore)
`npm ci` does a clean install strictly from the committed lockfile
(deterministic, no version drift) — needed to restore junction node_modules
in a fresh worktree. Distinct from `npm install`/`npm i`, which stay
hard-blacklisted because they can pull new/updated versions; the blacklist
runs before the whitelist, so they remain blocked. Word boundary after `ci`
prevents `npm cider`-style prefix matches; chain semantics still block
`npm ci && <mutating>`.

TDD: RED (3 allow-cases failed default-deny) -> GREEN (/^npm\s+ci\b/) ->
tools-vitest 1998 passed / 2 skipped (2000). Backlog item A; plan
docs/superpowers/plans/2026-05-31-discipline-guard-backlog.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 14:46:58 +03:00
368 changed files with 1222 additions and 35579 deletions
+2 -2
View File
@@ -21,10 +21,10 @@ jobs:
extensions: pdo, pdo_pgsql, redis, mbstring, intl, bcmath
coverage: none
- name: Setup Node 20
- name: Setup Node 22
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'
cache: 'npm'
- name: Install root JS deps
+1 -5
View File
File diff suppressed because one or more lines are too long
+2 -7
View File
@@ -31,14 +31,9 @@ paths:
keyset (cursor) — O(1) глубины; offset-based — backward-совместимость.
При count_only=true возвращает только {"total": N} без строк.
parameters:
- name: status_in
- name: status_in[]
in: query
description: >
Фильтр по статусам (можно несколько). На проводе сериализуется
Laravel array-binding: status_in[]=NEW&status_in[]=WON. Имя параметра
в спецификации — без скобок: ключи свойств MCP-инструмента обязаны
матчить ^[a-zA-Z0-9_.-]{1,64}$ (скобки запрещены, иначе Anthropic
tools-схема падает с 400).
description: Фильтр по статусам (можно несколько)
required: false
schema:
type: array
-168
View File
@@ -1,168 +0,0 @@
# Каталог данных «мозга» Лидерры
Полный перечень всего, что новый «мозг» (наблюдатель + защиты 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.1F.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`).
@@ -1,18 +0,0 @@
# 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).
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,11 +0,0 @@
{
"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": ["каждый денежный путь проверен на потерю копеек и двойное списание"]
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,11 +0,0 @@
{
"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": ["проблема/контекст вскрыты до проектирования"]
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,11 +0,0 @@
{
"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-код следует конвенциям Лидерры"]
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,11 +0,0 @@
{
"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-ФЗ"]
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,11 +0,0 @@
{
"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 сходятся"]
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,11 +0,0 @@
{
"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-ФЗ проверено"]
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,11 +0,0 @@
{
"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 процесс восстановлен, узкие места выявлены"]
}
@@ -1,11 +0,0 @@
{
"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 модель покрывает поток управления и роли"]
}
@@ -1,11 +0,0 @@
{
"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 проверками"]
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,11 +0,0 @@
{
"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": ["налоговый расчёт/проводка корректны по НК РФ/РСБУ"]
}
@@ -1,11 +0,0 @@
{
"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 обоснован результатами проверок"]
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,11 +0,0 @@
{
"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": ["процесс-скил применён ДО реализации, дисциплина соблюдена"]
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,11 +0,0 @@
{
"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 картирован, защита приоритизирована"]
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,11 +0,0 @@
{
"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"]
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
@@ -1,12 +0,0 @@
{
"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": "" }
}
-5
View File
@@ -158,7 +158,6 @@ nodes:
attributes:
tooling_section: "§3.5 #10"
install: "composer require laravel/boost --dev"
conflicts_with: ["postgres-mcp"]
- id: "#11"
name: "Laravel Pint"
@@ -334,7 +333,6 @@ nodes:
chain_membership: []
attributes:
tooling_section: "§3.1 #1 (historic)"
conflicts_with: ["boost"]
- id: "#20"
name: "Volar"
@@ -525,7 +523,6 @@ 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"
@@ -546,7 +543,6 @@ nodes:
chain_membership: []
attributes:
tooling_section: "§4.5 #31"
conflicts_with: ["frontend-design", "21st-magic"]
- id: "#32"
name: "21st.dev Magic MCP"
@@ -563,7 +559,6 @@ nodes:
chain_membership: []
attributes:
tooling_section: "§4.6 #32"
conflicts_with: ["frontend-design", "ui-ux-pro-max"]
- id: "#33"
name: "claude-md-management"
@@ -1,144 +0,0 @@
# Аудит готовности М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`).
@@ -1,624 +0,0 @@
# Расширение факторного анализа «мозга» — кандидаты раздела 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`, что корректно отражает «судья не сработал».
@@ -0,0 +1,144 @@
# Discipline-guard backlog — router-gate `tools/enforce-*.mjs`
**Worktree:** `.claude/worktrees/discipline-guard` (branch `worktree-discipline-guard`).
**Date:** 2026-05-31. Owner-authorized backlog after quirk-2 + 1A closure (commit `b0cd18d7`).
## Context (already done — do NOT redo)
- **Quirk 2** — redirect detector is quote-aware (`stripQuotedSpans` in `tools/enforce-router-gate.mjs`): `>`/`2>` inside quotes no longer false-blocks. Commit `b0cd18d7`.
- **1A** — removed advertising of dead override phrases (`findOverride` is a v4 stub) from `enforce-prompt-injection` + verify-before-push / coverage-verify / memory-coverage / tdd-gate. Locked by negative tests. Same commit.
- Marketing MCP servers cut from `.mcp.json` (commit `63100dec`).
## Deliberately NOT doing (these are defense lines, not bugs)
- Calibration 6 of the judge (reading chat context) — weakens in-session defense.
- Quirk 3 (loosen exact-match of git approval) — that exact-match is an anti-injection property.
## Backlog (by priority)
### A. `npm ci` in router-gate whitelist (`SAFE_EXACT` in `tools/enforce-router-gate.mjs`) ← current
Restoring locked dependencies is safe and closes worktree-setup friction. `npm ci` installs
exactly the committed lockfile (deterministic, no version drift) — unlike `npm install`/`npm i`,
which stay hard-blacklisted because they can pull new/updated versions.
**TDD:**
1. RED — new describe block in `tools/enforce-router-gate.test.mjs`: allow `npm ci`,
`npm ci --no-audit`, `npm ci --prefer-offline`; still block `npm install`/`npm i`/
`npm install foo`/`npm i foo` (hard-blacklist), `npm cider` (word boundary → default-deny),
`npm ci && rm x` (chain mutating).
2. GREEN — add `/^npm\s+ci\b/` to `SAFE_EXACT` with rationale comment. `\b` prevents
`npm cider`-style prefix matches. Blacklist runs before whitelist, so `npm install`/`npm i`
stay blocked (the `i`-alternative needs `i` right after the space; `npm ci` has `c` there).
3. tools-vitest full run (also the push sentinel).
4. Commit via AskUserQuestion (label = exact command).
### B. Cosmetic path strings in gate messages
`c:/` vs `/c/`, unexpanded `$env:` in gate messages. Polish only.
### F. Parallel-session-lock false cross-worktree collision (2026-05-31, owner-raised)
Symptom: a session in worktree `discipline-guard` was blocked by
`enforce-parallel-session-lock` (held by another session `7f6efd48`, pid changed
12552→19044 across attempts → holder still active; pid is the transient hook-node pid,
session_id is the stable identity).
**Investigation (read-only):**
- Lock keyed by `computeWorkspaceHash(process.cwd())` = md5(cwd).slice(0,12); file
`~/.claude/runtime/session-lock-<hash>.json`; release only on Stop; TTL 5 min.
- 9 lock files accumulated → stale files leak when a session closes without a clean Stop.
- `enforce-branch-switch` read branch "worktree-discipline-guard" via
`git branch --show-current` from `process.cwd()` → the hook's cwd IS the worktree →
**keying is already per-worktree** (NOT coarse main-dir). So the holder shared this
worktree's hash → genuine same-worktree concurrency, the lock working as designed —
NOT a false positive. Do NOT re-key (would weaken same-tree serialization).
**Genuinely-fixable part (no weakening):** leaked lock on close-without-Stop blocks the next
same-worktree session for up to TTL. Fix: release on SessionEnd (not only Stop) + prune
stale lock files on acquire. Ground-truth the lock JSON before coding.
**Closure (2026-05-31).** All keying/hygiene/UX parts done, no discipline weakened:
- **A — keying by worktree root** (`resolveWorkspacePath`, commit `7a469dc9`): keys the
lock on the session's stable `event.cwd` → git toplevel, not the volatile hook
`process.cwd()` (which collapses to main on resume → cross-worktree false-blocks).
Same-worktree serialization unchanged; fallback to `process.cwd()` if `event.cwd` absent.
- **D — clearer block message**: identifies the holder by its STABLE `session_id`; marks
the recorded pid as transient ("may change between attempts"). Chasing the pid was what
led to closing the wrong session. Logic untouched (text only).
- **B — `pruneStaleLocks`**: best-effort delete of leaked lock files that are ALREADY
stale by the shared `isStale()` (now exported — single source of truth). Active
within-TTL locks are never touched → serialization not weakened. Wired into the
PreToolUse branch of `main()`, wrapped so hygiene can never break the gate.
- **C — release on SessionEnd**: NO new code. The existing `!event.tool_name` branch
already releases. To make release fire on session end (not only on Stop turns),
**OWNER ACTION in `.claude/settings.json`**: add `enforce-parallel-session-lock.mjs`
to the `SessionEnd` hook array (it already runs on `Stop`). Pure config; Claude cannot
edit settings.json. Until added, leaked locks are still self-healing via B (prune) +
the 5-min TTL takeover — so this is a reliability nicety, not a correctness gap.
- **E/F — live**: fix is on branch `worktree-discipline-guard`; the live hook executes
from `tools/` on **main**, so it is active only after merge to main. Runtime
effectiveness of A depends on the PreToolUse payload carrying `cwd`; if absent, the
safe fallback = prior behavior (no regression). Verify on main.
### C. TDD-gate cross-actor — chosen: **Z** (full, 2026-05-31; on hold behind F)
`enforce-tdd-gate` does not see test edits made by a subagent (scans only the controller's
own turn; subagent test edit + RED live in `agent-<id>.jsonl`). **Z = Part 1 (close the
projects/ Write hole — verified prerequisite) then Part 2 (read subagent transcript bound to
a Task in this turn).** Condition 1 verified VIOLATED (no Write-tool gate covers
`~/.claude/projects/`), so Variant 1 alone would weaken — safe only bundled with Part 1.
**Closure (2026-05-31, TDD, no discipline weakened — net strengthening):**
- **Part 1**`enforce-runtime-write-deny.mjs` extended with `TRANSCRIPT_RE`
(`(^|/)\.claude/projects/.*\.jsonl$`): the Write tool can no longer create/overwrite any
session/subagent transcript `.jsonl`. Memory files there are `.md` and stay writable
(never match `.jsonl$`). Resolving normalizer blocks `.`/`..` evasion. This makes the
agent-`<id>`.jsonl that Part 2 trusts unforgeable.
- **Part 2**`enforce-tdd-gate.mjs`: `decide()` now also credits a subagent's matching
test edit + RED run via new `subagentEntriesList`. `turnTaskAgentIds(turn)` extracts the
**hex** agentId from the harness-written `Task` tool_result ("agentId: <hex>") — the
controller cannot forge its own tool_result, and the hex-only match blocks
`agentId: ../../x` path-traversal. `subagentTranscriptPaths()` derives
`<dir>/<controller-session>/subagents/agent-<id>.jsonl` (bound to the controller session).
`main()` reads those transcripts best-effort (missing → no extra credit = stricter, never
an error). No NEW weakening: a delegated subagent doing real TDD is legitimate; the only
forgery vector (overwrite the agent jsonl) is closed by Part 1.
- Full tools-vitest: **2027 passed / 2 skipped**.
- **OWNER ACTION (settings.json, Claude can't edit it):** `enforce-tdd-gate.mjs` is already
a registered PreToolUse hook → Part 2 goes live on merge. **Part 1 requires that
`enforce-runtime-write-deny.mjs` be registered** on PreToolUse(Edit|Write|MultiEdit|
NotebookEdit); if it is not yet registered, the transcript Write-deny is inert until added.
### G. Coverage line under-reports cross-turn active skill (2026-05-31, owner-raised)
Symptom: the `coverage: <channel>:<id>` line says `direct`/`chain` when a skill chosen in a
PRIOR turn is still active in the current turn. Root cause: `enforce-coverage-verify.mjs`
credits `channel=skill` only if the `Skill` tool was invoked in the CURRENT turn
(`turnToolUses`). On a continuation turn (skill still active, not re-invoked) an honest
`skill:X` line would be BLOCKED → so the controller learns to under-report as `direct`/`chain`.
**Fix (no weakening):** also credit `skill:X` if X was invoked anywhere earlier in THIS
session (a real `Skill` tool_use in the transcript — still unforgeable). decide() gains a
`priorSkillNames` param; main() collects session-wide Skill names via `sessionToolUses`.
Residual: attribution may be stale (skill invoked long ago) — acceptable; the alternative
(forced dishonest `direct`) is worse, and the owner wants cross-turn skills honored.
### D. Smoke 8 — live Workflow-gate F2 test
Needs a clean session (not code).
### E. H10 — auto-bootstrap worktree (junction node_modules) in `tools/subagent-prompt-prefix.mjs`
### (later) Layer 5 — VM + YubiKey — needs hardware.
## Environment working rules
- Tests / push sentinel: `npx vitest run --root app --config vitest.config.tools.mjs`
(NOT `npm run test:tools` — breaks on keytar). From inside the worktree it's run as
`--root app`; from the main checkout, point `--root` at the worktree app dir.
- Commit: only via AskUserQuestion where the option label = the EXACT command (router-gate
compares verbatim) + plain-language explanation; commit text via `-F` file in `.scratch/`;
commit only explicit paths (parallel sessions).
- Push: needs a fresh verify-sentinel (full run ≤30 min); override phrases are dead
(`findOverride` is a stub) → the only path to push non-`.md` changes is to run the tests.
File diff suppressed because it is too large Load Diff
@@ -1,374 +0,0 @@
# Роутер-наставник — мастер-карта стройки (координирующий план)
> **Для исполнителя:** это **карта**, а не пошаговый чертёж. Она задаёт 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, дефолты `[РЕШЕНИЕ HK]`).
- **Машина 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 + J1J10 из 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 как «третий голос ради расхождения», где плоскость не вредит). Решение — за владельцем.
- **Усиления J1J10:** 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, E1E10) — **только 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 | 🟨 Судья AI (J1J10) | 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 / D18D20 / 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`.

Some files were not shown because too many files have changed in this diff Show More