Compare commits

...

273 Commits

Author SHA1 Message Date
Дмитрий d1ad4e8559 feat(m7-phase8): sealedPlanCoversEdit live-wiring + matcher extension to discipline sources
planCoversAction (signed-plan + tree-valid + leaf-match, fail-CLOSED) wires the §6
build-loop differentiator live. main() matcher now fires for tools/enforce-*.mjs
(ad-hoc → LAW/escape; under sealed plan → CARD). decide() skips doc-malice prose
layers for code and allows build-loop CARD (M2/content-floor/TDD govern). Hook inert
until Phase 8 registration. +10 tests; regression tools-only 3397 passed / 2 skip.

Plan: docs/superpowers/plans/2026-06-08-router-mentor-machine-7-phase-5.md (Deferred Ф8).
2026-06-12 08:45:17 +03:00
Дмитрий 440fd71bbf fix(content-floor): quote-aware redirect in matchBashHardBlacklist (port b0cd18d7 after --ours merge) 2026-06-09 13:35:24 +03:00
Дмитрий d59b9177d4 Merge branch 'main' into worktree-brainrepo
# Conflicts:
#	tools/enforce-coverage-verify.mjs
#	tools/enforce-coverage-verify.test.mjs
#	tools/enforce-router-gate.mjs
#	tools/enforce-router-gate.test.mjs
2026-06-09 13:26:12 +03:00
Дмитрий cc77a5817e docs(phase8): owner deployment runbook for M7 zoo dissolution + activation 2026-06-09 13:03:56 +03:00
Дмитрий c8639f2fd1 feat(manifest): register coverage-verify + todowrite-skill-verifier (G2 phase8 manifest 11to13) 2026-06-09 12:56:40 +03:00
Дмитрий 5fa17070a9 docs(router-mentor): Level B closure + phase 8 handoff 3 2026-06-09 12:46:13 +03:00
Дмитрий 068b03d946 feat(criterion-gate): register enforce-criterion-gate in manifest (Level B task6) 2026-06-09 12:30:06 +03:00
Дмитрий 4e54331ef4 feat(criterion-gate): consumer enforce-criterion-gate fail-CLOSE (Level B task5) 2026-06-09 12:26:33 +03:00
Дмитрий e229cf0706 feat(criterion-gate): per-criterion green producer (Level B task4) 2026-06-09 12:24:30 +03:00
Дмитрий caa41e6cac feat(criterion-gate): mutation runner in-place + restore (Level B task3) 2026-06-09 12:22:40 +03:00
Дмитрий 9414699ad9 feat(criterion-gate): vitest json run + test-count guard (Level B task2) 2026-06-09 12:21:21 +03:00
Дмитрий 67a46df978 feat(criterion-gate): JS mutation operators pure (Level B task1) 2026-06-09 12:19:50 +03:00
Дмитрий 2c41971d88 docs(router-mentor): G1 closure + phase 8 handoff 2 2026-06-09 10:32:47 +03:00
Дмитрий b92afad96c feat(verify-gate): register enforce-verify-gate in manifest (G1 task7) 2026-06-09 10:22:36 +03:00
Дмитрий 2bd7efbebd feat(verify-gate): consumer enforce-verify-gate fail-CLOSE (G1 task6) 2026-06-09 10:19:44 +03:00
Дмитрий 9ce6041be4 feat(verify-gate): producer staged-fingerprint + io main (G1 task5) 2026-06-09 10:18:11 +03:00
Дмитрий 93e516e680 feat(verify-gate): producer pure buildVerifyReceipt (G1 task4) 2026-06-09 10:16:22 +03:00
Дмитрий 0f20c944ca feat(verify-gate): rubilnik verify-gate-config (G1 task3) 2026-06-09 10:15:16 +03:00
Дмитрий 1088599023 feat(verify-gate): verify-receipt pure core sign/accept (G1 task2) 2026-06-09 10:13:57 +03:00
Дмитрий 1857773f89 feat(verify-gate): VERIFY_PASS receipt domain (G1 task1) 2026-06-09 10:12:41 +03:00
Дмитрий 1478f56b51 docs(router-mentor): phase 8 readiness audit findings 2026-06-09 09:54:07 +03:00
Дмитрий 4ea4806f71 docs(router-mentor): phase 8 migration handoff (audit tails then configure-test-deploy) 2026-06-09 09:20:20 +03:00
Дмитрий 487a6f1db9 docs(router-mentor): A1 judge gate2 activation note (implementation-specific) 2026-06-09 08:55:33 +03:00
Дмитрий e8958155c4 feat(judge-gate): runJudgeTurn three-mode wiring + main (shadow runs+logs, live-block fail-close) 2026-06-09 08:46:28 +03:00
Дмитрий b48fd86152 feat(judge-gate): shadow verdict log + judge-unavailable warn (J8) 2026-06-09 08:43:00 +03:00
Дмитрий 50550f311e feat(judge-gate): async runJudgeGate orchestration + degraded-allow on unavailable 2026-06-09 08:40:36 +03:00
Дмитрий 36545b636f feat(judge-gate): callJudgeModel async transport + spend-gate (unavailable vs malformed) 2026-06-09 08:38:38 +03:00
Дмитрий 0af6610b77 feat(judge-gate): extractGate2Product Write-only plan detector 2026-06-09 08:36:03 +03:00
Дмитрий 0c6f084f0e feat(judge-gate): parseJudgeResponse fail-closed parser 2026-06-09 08:33:46 +03:00
Дмитрий cfca7ecfaa docs(router-mentor): A1 pre-code chain amendments (Write-only, judge-unavailable degraded-allow) 2026-06-09 08:30:43 +03:00
Дмитрий 843097c0d6 docs(router-mentor): A1 judge gate2 wiring implementation plan 2026-06-09 08:19:54 +03:00
Дмитрий 44c194bc0f docs(router-mentor): A1 judge gate2 wiring design spec 2026-06-09 08:11:13 +03:00
Дмитрий 8ec7969551 docs(router-mentor): stage3 and m3 handoff 2026-06-09 07:55:03 +03:00
Дмитрий 681c2b8abc docs(router-mentor): activation runbook A1 A6 A7 2026-06-09 07:50:42 +03:00
Дмитрий 4c1ad03705 docs(shadow-replay): clean stage 3 report all walls green 2026-06-09 07:27:29 +03:00
Дмитрий a630053714 fix(shadow-replay): exclude runtime writes and scope M6 to bash 2026-06-09 07:20:48 +03:00
Дмитрий 647879adc6 feat(shadow-replay): main cli and runAll 2026-06-09 06:30:17 +03:00
Дмитрий dd63f950e2 feat(shadow-replay): buildCorpus runMachine report 2026-06-09 06:26:57 +03:00
Дмитрий ff8f14d8d2 feat(shadow-replay): M4 adapter and M3 divergence 2026-06-09 06:24:33 +03:00
Дмитрий a7f3ebd971 feat(shadow-replay): M2 adapter signed plan 2026-06-09 06:22:40 +03:00
Дмитрий d5feca2672 feat(shadow-replay): M5 M6 adapters 2026-06-09 06:21:01 +03:00
Дмитрий f139ad5320 feat(shadow-replay): classifyOutcome 2026-06-09 06:19:10 +03:00
Дмитрий 8228703320 feat(shadow-replay): fixtures 2026-06-09 06:17:38 +03:00
Дмитрий 2ba34dc0e2 docs(router-mentor): stage 3 replay plan 2026-06-09 06:12:55 +03:00
Дмитрий 45905432f8 docs(router-mentor): stage 3 replay spec data-source fix 2026-06-09 06:07:55 +03:00
Дмитрий 987feb2e40 docs(router-mentor): stage 3 replay spec 2026-06-09 06:00:15 +03:00
Дмитрий 7e5db3ef91 docs(router-mentor): self-consistent git anchor in handoff prompt 2026-06-09 05:31:03 +03:00
Дмитрий ad6fd73ec9 docs(router-mentor): handoff for next session (Block B plus R-08 closed, autonomous front exhausted) 2026-06-09 05:30:35 +03:00
Дмитрий fa07472dd0 docs(router-mentor): mark Block B closed, M1 pinned, fix stale notes in loose-ends registry 2026-06-09 05:25:26 +03:00
Дмитрий 5782ede3eb test(action-journal): pin M1 fail-closed on torn append (entry without head update) 2026-06-09 05:23:51 +03:00
Дмитрий 2b0c28e59f feat(supreme-gate): tree leaf resolution plus advance flag for R-08 waves 2026-06-09 05:12:38 +03:00
Дмитрий 16e0c1db09 feat(supreme-gate): pointer state accepts tree position as index array 2026-06-09 05:10:45 +03:00
Дмитрий 51f718ea07 feat(plan-lock): tree leaves, leaf resolver, validate, recursive criterion ids, hierarchical consumers 2026-06-09 05:09:21 +03:00
Дмитрий 1a3ccc2178 feat(step-pointer): serialize plus tree navigation for R-08 waves 2026-06-09 05:06:49 +03:00
Дмитрий 1651fdfb50 docs(router-mentor): R-08 implementation plan (6 tasks TDD) 2026-06-09 05:04:45 +03:00
Дмитрий 4a0c3a98d9 docs(router-mentor): R-08 spec hardened by adversarial pass (7 SE + 2 flat variants) 2026-06-09 04:59:10 +03:00
Дмитрий 93fcb5e141 docs(router-mentor): R-08 hierarchical waves design spec 2026-06-09 04:51:39 +03:00
Дмитрий 0e995970f9 feat(reconcile): wire reconcileEvent reader in main (inert, WARN-only) 2026-06-09 04:08:22 +03:00
Дмитрий 752512d3cd feat(status): R-30 journal integrity block via live verifyChain (read-only) 2026-06-09 04:06:53 +03:00
Дмитрий 1000abc7fc feat(status): R-24 door coverage block (read-only) 2026-06-09 04:05:15 +03:00
Дмитрий 0eef670e54 feat(door-coverage): door matcher reader and canonical mutating tools list 2026-06-09 04:02:27 +03:00
Дмитрий 2e6a6c0a7a feat(status): R-09 learning queue block (read-only) 2026-06-09 03:59:32 +03:00
Дмитрий 6c8918dff4 docs(router-mentor): plan blockB classes 1+2 (R-09/R-24/R-30/reconcile) 2026-06-09 03:57:03 +03:00
Дмитрий eb4b38f481 docs(router-mentor): handoff k novoy sessii (E/G zakryt, Blok B otbreynstormlen) 2026-06-09 03:46:59 +03:00
Дмитрий 2900554d5f docs(router-mentor): E/G warm-up batch zakryt v reestre hvostov (P1-P6) 2026-06-09 03:25:43 +03:00
Дмитрий a85d7f9d5f docs(router-mentor): DOC-1 escape-awareness callout pered aktivaciey M6 (P6 E/G) 2026-06-09 03:24:22 +03:00
Дмитрий 553ddea464 docs(tools): utochnit docstring node-graph + pinning-invarianty (P5 E/G) 2026-06-09 03:23:39 +03:00
Дмитрий cf9f803d36 feat(status): detail-render doski oborony + escapeCell anti-injection (P4 E/G) 2026-06-09 03:21:29 +03:00
Дмитрий 5ae0b1359f fix(observer): routing-detector ignorit PASTED-citaty /node (P1 E/G, 4-y FP-klass)
Novyy uzkiy stripPastedContext (fenced+blockquote only) pered detekciey. NE reuse stripQuotedContext (tot stripaet inline - FN). Sohraneno FP-smeshchenie routingGate (SE-1). Regressiya 3185 passed.
2026-06-09 03:18:55 +03:00
Дмитрий 84f75abb14 fix(observer): PII count via sanitize pipeline, overlap once (P2 E/G)
Edinyy PIPELINE: count ravno chislu redakciy sanitize (SE-2). sanitize byte-identical. Regressiya 3177 passed.
2026-06-09 03:16:49 +03:00
Дмитрий cfc4e0a853 fix(observer): release-класс не ловит голый commit (P3 E/G)
classifyTask: убран commit из release-регекса (коммит != релиз). push/merge/deploy/release/релиз/тегни остаются. Аналитика-only, гейтов-потребителей нет. Регрессия 3175 passed.
2026-06-09 03:14:24 +03:00
Дмитрий f801593987 docs(router-mentor): план E/G warm-up batch (7 тасков, bite-sized TDD)
Инлайн-исполнение (субагенты запрещены). Порядок простое-первым P3/P2/P1/P4/P5/P6 + закрытие реестра. Точный код тестов и правок, 4 правки безопасности учтены. Также факт-правка примеров P3 в спеке (first-match порядок classifyTask).
2026-06-08 19:47:30 +03:00
Дмитрий 0774a41f13 docs(router-mentor): E/G spec — 4 правки безопасности после adversarial-анализа
Цепочка audit-context-building/sharp-edges/variant-analysis. P1 узкий stripPastedContext (fenced+blockquote only, не reuse stripQuotedContext — сохранить FP-смещение гейта). P2 counting-replacers по конвейеру sanitize (count==редакции). P4 escapeCell anti-injection в STATUS.md. P5 пиннинг документируемого инварианта. Жёсткие стены М2/М5/М4/М6 не затронуты.
2026-06-08 19:40:34 +03:00
Дмитрий 62d3352d3e docs(router-mentor): spec для E/G warm-up batch (6 пунктов)
Дизайн утверждён владельцем. Точность наблюдателя (4-й FP-класс quoted /node, PII double-count, release-class commit) + детальный рендер доски обороны + косметика node-graph/reviewer + DOC-1 escape-awareness. Всё инлайн, TDD, commit-not-push, регрессия tools-only не ниже baseline 3174. Live-вшивка источников доски — граница B-блока.
2026-06-08 19:26:31 +03:00
Дмитрий acf1d90209 docs(router-mentor): handoff к следующей сессии (после закрытия H)
Ready-to-copy промт + карта оставшихся хвостов (B/A/C/E/F/G/H3) + квирки
(vitest --root, git PowerShell, node -e блок, память-гейт) + цепочка скилов
+ жёсткие правила (commit-not-push, CLAUDE.md off-limits). commit-not-push.

coverage: direct:session-handoff
2026-06-08 19:12:01 +03:00
Дмитрий 64fb063a22 docs(router-mentor): H закрыт — реестр хвостов + handoff обновлены
Блок H помечен ЗАКРЫТЫМ в реестре хвостов (H1/H2 + критический путь этап 1) и
handoff (финальная сводка решений + 5 находок аудита + квирк vitest --root для
worktree под .claude). commit-not-push.

coverage: direct:h-housekeeping
2026-06-08 19:07:37 +03:00
Дмитрий 65b3c57515 feat(nodes): конфликт-рёбра R-12 (Tooling §6 + R14.5, двусторонние)
4 пары attributes.conflicts_with из канона (mutual exclusion / replaces):
postgres-mcp↔boost (§6.1 «не оба активными»/replaces) + треугольник UI-генераторов
frontend-design↔ui-ux-pro-max↔21st-magic (R14.5 «один генератор на задачу,
не параллельно и не друг с другом»). ADR-границы — комплементарные различения,
не конфликты; §9.1-отвергнутые не узлы реестра. m3e: резолв+симметрия GREEN.

coverage: skill:executing-plans
2026-06-08 18:59:46 +03:00
Дмитрий 3dbd8a5cd9 test(m3e): живой инвариант покрытия реестра карточками + конфликт-целостность
Новый tools/m3e-card-coverage-invariants.test.mjs на реальном реестре:
(1) у каждого узла nodes.yaml есть карточка skill===slug (missingContracts пуст);
(2) нет пустых карточек (G-B); (3) конфликт-рёбра резолвятся и симметричны (G-H);
(4) реестр контрактов без формальных ошибок/дублей/дрейфа. GREEN — покрытие 86/86.
Конфликт-проверки пока тривиальны (0 рёбер), наполнятся в Task 4.

coverage: skill:test-driven-development
2026-06-08 18:56:10 +03:00
Дмитрий caadc92be0 feat(contracts): карточки marketing/project-agent/kg/historic (#74-#86 + #1) — все 86 готовы
14 финальных карточек: marketing (#74-#83: marketing-plugin, marketingskills,
brand-voice, marketing-ru[own], yandex-metrika-mcp, yandex-wordstat-mcp, telegram-mcp,
postiz, dataforseo-mcp[deferred], unisender-go-mcp[deferred]) + project-agent
(normative-sync[own], prod-deploy-validator[own]) + kg (graphifyy) + historic
(postgres-mcp #1). m3a GREEN. ВСЕ 86 узлов имеют карточку.

coverage: skill:executing-plans
2026-06-08 18:54:55 +03:00
Дмитрий 083095174c feat(contracts): карточки discovery/authoring/dev-support/finance/backend/infosec (#55-#73)
19 карточек. own (self-authored): discovery-interview, billing-audit, ru-tax-accounting,
laravel-backend-patterns, pdn-152fz-audit, threat-model, security-go-live. external:
skill-creator, plugin-dev, hookify, claude-code-setup, context7, finance-plugin, rector,
php-insights, nightowl[deferred], owasp-zap, nuclei, ward. m3a GREEN. 72/86 готово.

coverage: skill:executing-plans
2026-06-08 18:52:28 +03:00
Дмитрий d48365de5e feat(contracts): карточки design/integration/ml-ai/business-process (#44-#54)
11 карточек: design (figma-mcp[deferred], universal-icons-mcp, design-plugin) +
integration (openapi-mcp) + ml-ai (promptfoo, data-scientist, jupyter-mcp[deferred])
+ business-process (operations[зонтик], process-modeling[own], process-analysis[own],
n8n-mcp[deferred]). process-* = own (self-authored); остальные external. m3a GREEN.
53/86 карточек готово.

coverage: skill:executing-plans
2026-06-08 18:49:18 +03:00
Дмитрий 078e829b38 feat(contracts): карточки phase-3 + off-phase (UI-pool/debug/architecture/audit/PM)
18 карточек (все external): phase-3 (semgrep, trivy, dependabot, pg-audit,
pg-anonymizer) + UI-pool (ui-ux-pro-max, 21st-magic, claude-md-management) +
debug-runtime (sentry-mcp, redis-mcp) + architecture-tooling (adr-kit, mermaid,
architecture-patterns, deptrac) + audit-security (trail-of-bits, security-guidance)
+ project-management (ccpm, product-management). zero-hash + path"" → G4 инертен.
m3a 3/3 GREEN. 42/86 карточек готово.

coverage: skill:executing-plans
2026-06-08 18:47:19 +03:00
Дмитрий 9dc6bb55fd feat(contracts): карточки phase-1 + phase-2 (#10-#30)
16 карточек: phase-1 (boost, pint, larastan, roave-security, ide-helper, squawk,
pg-formatter, pg-partman[dormant], pest) + phase-2 (superpowers[own,зонтик], volar,
vue-tsc, eslint-prettier, vitest, histoire, frontend-design). superpowers = own
(без source); остальные external (zero-hash + path"" → G4 инертен). m3a 3/3 GREEN.

coverage: skill:executing-plans
2026-06-08 18:44:20 +03:00
Дмитрий 52cea07fee feat(contracts): карточки phase-0 (#2-#9) + m3a form-инвариант всех карточек
8 карточек-контрактов phase-0: playwright-mcp, github-mcp, markdownlint, cspell,
lychee, stylelint, gitleaks, pa11y (все external, zero-hash + path"" → G4 инертен).
m3a расширен: «ВСЕ файлы contracts/ form-валидны + нет дрейфа» (loadRegistry errors
+ driftFlags пусты) — per-батч валидация прогоном vitest. m3a 3/3 GREEN.

coverage: skill:executing-plans
2026-06-08 18:38:24 +03:00
Дмитрий f1c245f8f6 feat(card-coverage): чистый чекер покрытия узел↔карточка + конфликт-рёбра
Новый tools/card-coverage.mjs (H, R-11/R-12): missingContracts (узел без
карточки skill===slug), emptyCards (needs∪produces пусто — G-B), unresolvedConflicts
(висячая ссылка — G-H), asymmetricConflicts (A→B без B→A — G-H). Чистые функции,
данные инъектируются. TDD на фикстурах: RED→GREEN, 5/5.

coverage: skill:test-driven-development
2026-06-08 18:34:46 +03:00
Дмитрий d1a767867a fix(skill-contract): G-E — страж дрейфа инертен при пустом source.path без содержания
checkContractDrift: external без локального source.path И без поданного
currentContent (== null) → не сторожим (G4 инертен). Это прод-случай зеро-хеша
(Р5 MCP/marketplace): loadRegistry при пустом path не читает content. Прямой
вызов с поданным currentContent — drift сверяется как раньше. TDD: RED→GREEN,
48/48 skill-contract + registry. Дисциплина doubt→drift на реальных источниках
не понижена.

coverage: skill:test-driven-development
2026-06-08 18:33:05 +03:00
Дмитрий 4d257a1c44 docs(router-mentor): план реализации блока H (карточки + конфликт-рёбра)
Bite-sized TDD-план: Task1 G-E мех-правка, Task2 чистый чекер card-coverage,
Tasks 3.1-3.20 авторинг 86 карточек по батчам, Task4 конфликт-рёбра (§9+ADR),
Task5 живой инвариант m3e (последним → GREEN), Task6 гейт закрытия.
Исполнение инлайн (субагенты запрещены). commit-not-push.

coverage: skill:writing-plans
2026-06-08 18:07:22 +03:00
Дмитрий 1aacb2fe66 docs(router-mentor): H-design — вложены находки аудита достаточности (G-A/B/E/G/H)
Аудит «достаточно ли паспорта для роутера и судьи» (audit-context →
variant-analysis → sharp-edges → verification). Вердикт: схема достаточна,
новое поле не нужно. Вложены 5 находок: G-E (страж дрейфа инертен при
path=="" — точечная мех-правка), G-A (разделитель близнецов в capabilities),
G-B (инвариант «минимум содержания»), G-G (норма вход/выход), G-H (инвариант
резолв+симметрия). G-F/G-C/G-D отложены. commit-not-push.

coverage: direct:brainstorming-author
2026-06-08 18:03:17 +03:00
Дмитрий e2f5ba0406 docs(router-mentor): H-block design — node cards (R-11) + conflict edges (R-12)
Дизайн блока H реестра хвостов эпика «роутер-наставник»: наполнение графа
скилов данными — 86 карточек-контрактов (per-node, skill=slug) + явные
конфликт-рёбра (Tooling §9 + ADR, двусторонние) + инвариант покрытия (TDD).
Механику 3-A/3-B/3-C/3-D не трогаем. commit-not-push.

coverage: skill:brainstorming
2026-06-08 17:43:03 +03:00
Дмитрий 85ffb25e2d docs(router-mentor): сводный реестр хвостов эпика до боевого режима
Свод всех отложенных пунктов «роутер-наставник / реинжиниринг мозга»
(7 машин + router-discipline + периферия мозга + граф скилов): блоки
H/A/B/C/E/F/G, критический путь к продакшену, колонка «кто закрывает»
(Claude / владелец). Read-only сбор, ничего не чинится. Главный
недостающий кусок — карточки узлов (2 из ~86) + конфликт-рёбра (0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 16:40:40 +03:00
Дмитрий 7c3a495e5c docs(m7): build-handoff #5 — Фазы 4/5/6/7-код СОБРАНЫ, остались шаги владельца + переезд Ф8
Сменяет handoff #4. Готовый промт для новой сессии + полное состояние: 15 коммитов
02e3ff73..ee9e3123 (Ф4 8 / Ф5 2 / Ф6 2 / Ф7 3), регрессия 3090→3163+2 без регрессий,
commit-not-push. Детальные шаги ВЛАДЕЛЬЦА: (А) завершить ИИ-проводку судьи (реальный
транспорт runJudgeGate + ключ/флаг/обкатка shadow→block), (Б) Фаза 8 переезд (регистрация
М1–М6 в settings.json + увольнение v4-зоопарка + тест-гейт §9.2 + откат). Цепочка скилов,
квирки, якоря коммитов — для подхвата следующей сессией.
2026-06-08 15:07:01 +03:00
Дмитрий ee9e312300 feat(m7-phase7): доска Ф6 — judgeMode ← judgeGateMode() (реальный режим судьи: inert до активации)
CLI status-md-generator передаёт в доску «кто на посту» реальный режим судьи через
judgeGateMode() вместо хардкода 'inert'. До активации владельцем (флаг+ключ) → 'inert'
(видимое поведение не меняется); после активации доска покажет shadow/live-block.
Структурный тест фиксирует проводку (импорт + judgeMode: judgeGateMode(), не хардкод). 51/51 GREEN.
2026-06-08 14:46:12 +03:00
Дмитрий 1bc8b30563 feat(m7-phase7): enforce-judge-gate mode-aware + finalGate + runJudgeGate seam + live fail-CLOSE (§8)
Обёртка судьи переписана с {active}-shadow-заглушки на mode-aware: decide({mode,verdict,
floorBlocked}) — inert/shadow → allow ($0/D28 тихий); live-block → finalGate (судья GO И пол
чист → allow; иначе block; битый вердикт → NO-GO, сомнение→блок). runJudgeGate — owner-seam
(§8 последняя фаза: реальный llmCall-транспорт + извлечение продукта подключает владелец; до
этого нейтральный GO wired:false — flip mode=block без транспорта НЕ кирпичит). main:
inert/shadow → allow fail-open ($0); live-block → exitDisciplineDecision (fail-CLOSE, судья жив).
7/7 GREEN. Инертно до активации владельцем.
2026-06-08 14:44:35 +03:00
Дмитрий 84de110fee feat(m7-phase7): judgeGateMode — inert/shadow/live-block (§8, default shadow D28; рубильник важнее режима)
Новый резолвер режима судьи М4 в judge-gate-config: inert ($0, нет флага/ключа) / shadow
(active, логирует не блокирует — D28 «сперва тихо») / live-block (active + ROUTER_MENTOR_JUDGE_
MODE=block, блокирует на NO-GO). Рубильник judgeActive важнее режима: MODE=block без флага/ключа
→ всё равно inert. Default при активации — shadow (рекомендуемая обкатка §11). 9/9 GREEN.
2026-06-08 14:42:52 +03:00
Дмитрий 942f9bb8a1 feat(m7-phase6): доска «кто на посту» (computeGuardBoardBlock) — манифест М1–М6 + режим судьи + ПОСТ ПУСТОЙ (§7)
Новый чистый computeGuardBoardBlock в status-md-generator: read-only снимок обороны М1–М6 из
checkManifest (registered/missing) — missing → «⚠️ ПОСТ ПУСТОЙ» (нельзя ложно объявить protected,
SE-B), + режим судьи М4 (пока inert; live — Ф7) + счётчики недавних escape/блоков. Врезан в
renderStatus сразу после таблицы контролёров C1–C6; CLI читает .claude/settings.json (fail-quiet
→ {}). GUARD_LABELS маппит хуки на машины М1–М6. Read-only, ничего не блокирует. 49/49 GREEN.
2026-06-08 14:28:56 +03:00
Дмитрий 6c53fe998a feat(m7-phase6): SE-B — DEFAULT_REQUIRED_HOOKS до полного М1–М6 (+М4 judge-gate, М6 snapshot/escape, М1 журналер)
floor-manifest-check.DEFAULT_REQUIRED_HOOKS расширен с 5 (пол+стена+3 exfil-стража) до 9 —
+enforce-judge-gate (М4), +enforce-snapshot и +enforce-floor-escape-consume (М6), +enforce-
skill-journaler (М1). Раньше доска рапортовала бы «protected» при незарегистрированных М4/М6
(SE-B). Теперь «ПОСТ ПОЛНЫЙ» = весь контур М1–М6. WARN-only (сигнал, не блок, Δ8). 13/13 GREEN.
2026-06-08 14:26:57 +03:00
Дмитрий 3ef853695b feat(m7-phase5): decide §6 — ЗАКОН требует escape владельца, КАРТА — claude-md-management (build-loop SE-D)
decide переорганизован: escape-allow → контент-слои (recovery/suspicious/fake-rule, defense
для всей нормативки, сохраняют reason) → §6 classification (LAW non-escaped → block «требует
escape владельца, скил недостаточен») → CARD-поток (skillActive + judge + H3-degradation).
ЗАКОН (Pravila/PSR/Tooling + ad-hoc дисциплинарный исходник + контент-правка правил) больше НЕ
проходит по одному claude-md-management-скилу — только escape. КАРТА (CLAUDE.md/memory) —
прежний скил-канал. build-loop sealedPlanCoversEdit (Ф8 live через plan-lock). Tooling-тест
обновлён под §6; +4 §6-теста. 48/48 GREEN.
2026-06-08 12:49:01 +03:00
Дмитрий 2d2f3fc591 feat(m7-phase5): classifyNormative — детерминированный КАРТА/ЗАКОН + build-loop SE-D (§6)
Новые чистые экспорты в enforce-normative-content-rules: isDisciplineSourcePath (исходники
машин М1–М6 по префиксам enforce-/judge-/floor-/escape-grant/action-journal/receipt-/
shell-content-rules/plan-lock/classify-destructive/path-normalization), contentTouchesLaw
(контент правит правила/дисциплину), classifyNormative → {kind:'CARD'|'LAW'}. КАРТА =
CLAUDE.md/MEMORY.md/memory; ЗАКОН = Pravila/PSR/Tooling + дисциплинарный исходник ВНЕ плана +
контент-правка правил; build-loop: дисциплинарный исходник ПОД sealed-планом → КАРТА; сомнение
→ ЗАКОН. Тотален (null guard). Интеграция в decide — Task 2. 44/44 GREEN.
2026-06-08 12:46:46 +03:00
Дмитрий f9a4b10d1f test(m7-phase4): §12 whole-phase инвариант-гейт — анти-регресс поглощённой дисциплины
Lock-in тест над поглощённой дисциплиной Фазы 4a/4b (coverage/todowrite/rationalization +
self-debrief в манифесте): fail-CLOSE (exitDisciplineDecision, нет fail-open catch→block:false),
escape≠override (нет findOverride-вокабуляра), нет controller-text→allow (text-bypass вырезан,
Класс 1), манифест-членство (Ф6 self-check). Scope только поглощённые 4a/4b — не ещё-не-
поглощённые (memory-coverage/branch-switch/verify-* retire Ф8). 16/16 GREEN. Закрывает Фазу 4.
2026-06-08 12:28:31 +03:00
Дмитрий 9763025621 feat(m7-phase4c): Гейт-2 planGateSteps/runPlanGate (sealed план-требование) + Гейт-3 verify absorption proof (§4.2)
Гейт-2 combiner (зеркало criterionGateSteps): runPlanGate = specToPlanCoverage + k5CriterionCheck
через runGateLadder. Поглощает tdd-gate Rule #6 «план перед prod-кодом» в ЗАПЕЧАТАННОЙ форме
(покрытие sealed-плана + критерий значимого шага), без Класс-1 text-mention hasPlanIndicator.
Синергия с М2-стеной. Гейт-3 absorption-proof: runCriterionGate засчитывает только ПОДПИСАННЫЙ
зелёный по критерию (расписка М5), отвергает само-написанный sentinel. Live-wiring обоих — Ф7;
retire tdd-gate/verify-* — Ф8. 43/43 GREEN.
2026-06-08 12:26:34 +03:00
Дмитрий f3f3a70aa5 test(m7-phase4b): decomposition покрыт existenceCheck Гейт-1 через журнал (§5 coverage-map)
Proof покрытия: обещанный планировочный навык, не вызванный по журналу (extractSkillCalls),
→ existenceCheck.missingSkills непуст → NO-GO. Доказывает, что Гейт-1 М4 ловит скрытое
дробление через журнал-факт ДО retire no-op enforce-decomposition-detector (Ф8). Live-wiring
«обещанные навыки ← журнал» в judge-gate — Фаза 7. 39/39 GREEN (примитивы уже построены).
2026-06-08 12:21:12 +03:00
Дмитрий e791d33c78 feat(m7-phase4b): enforce-rationalization-audit fail-OPEN → fail-CLOSE (exitDisciplineDecision, §2.1 Класс 2)
main конвертирован с fail-OPEN (catch→block:false) на fail-CLOSE через exitDisciplineDecision
(throw/малформ → блок, анти-SE2). decide/audit/паттерны/halt-counter (priorFlagCount≥2) —
без изменений; язык-детектор остаётся мягким сигналом (flags), блок только halt-counter'ом.
Структурный тест сверяет наличие exitDisciplineDecision + отсутствие fail-open catch. 37/37 GREEN.
2026-06-08 12:20:10 +03:00
Дмитрий 2f39286ddf feat(m7-phase4b): rationalization + self-debrief в FAIL_CLOSE_DISCIPLINE_HOOKS (манифест Ф6)
Обе Stop-дисциплины Фазы 4b зарегистрированы в манифесте fail-CLOSE. self-debrief уже
fail-CLOSE поведенчески; rationalization конвертируется отдельным коммитом (Task 2).
Манифест-тест (for-of) сверяет наличие обоих. 89/89 тестов хелперов GREEN.
2026-06-08 12:18:51 +03:00
Дмитрий a630c994db feat(m7-phase4a): coverage-verify + todowrite-skill-verifier в FAIL_CLOSE_DISCIPLINE_HOOKS (манифест Ф6)
Оба поглощённых дисциплинарных стража Фазы 4a добавлены в FAIL_CLOSE_DISCIPLINE_HOOKS —
манифест-самопроверка Фазы 6 потребует их регистрации. Манифест-тест (for-of) сверяет
наличие обоих. 87/87 тестов хелперов GREEN.
2026-06-08 11:10:41 +03:00
Дмитрий a29fa9caa9 feat(m7-phase4a): todowrite-skill-verifier — журнал-факт session-scope + fail-CLOSE + PreToolUse (§4.2)
Выполненный todo, claim'ящий Skill, теперь сверяется с ЖУРНАЛОМ вызовов (extractSkillCalls,
канал М1) вместо transcript-извлечения. Session-scope осознанно (выполненный todo мог
закрыться в прошлом ходе — отличие от coverage, которое turn-scoped). decide получает
journalSkillCalls; main грузит журнал через loadJournal+extractSkillCalls, обёрнут
exitDisciplineDecision (fail-CLOSE Фазы 0). Переориентирован на PreToolUse-семантику
(предотвращение, §4.2 [Pre]; регистрация matcher — шаг владельца Ф8). 5/5 тестов GREEN.
2026-06-08 11:08:56 +03:00
Дмитрий 02e3ff7379 feat(m7-phase4a): coverage-verify — журнал-факт K2 (turn∩journal) + fail-CLOSE + снят override (§4.2)
coverage skill:X теперь требует X в ПЕРЕСЕЧЕНИИ «Skill-tool_use этого хода ∩ журнал
вызовов» (turn-scope от границы хода transcript, факт от журнала М1 через skillTakenByJournal
K2). Не по строке coverage: — Класс 1 закрыт; turn-scoping без false-pass (X из прошлого хода
в журнале, но не в transcript этого хода → block). direct/node/chain/hook/agent приняты на
этом слое (журналом не верифицируемы, §4.2). main обёрнут exitDisciplineDecision (fail-CLOSE
Фазы 0). Override-вокабуляр снят (§12 escape≠override). 9/9 тестов GREEN.
2026-06-08 11:07:35 +03:00
Дмитрий 3bd3caee40 docs(m7): build-handoff #4 — Фазы 0/2/3 собраны + Фаза 4 SCOPED, промт для новой сессии 2026-06-08 10:47:41 +03:00
Дмитрий 1d8457e671 docs(m7): SCOPED план Фазы 4 — поглощение дисциплины в М4 (§4.2, под-фазы 4a/4b/4c) 2026-06-08 10:42:15 +03:00
Дмитрий 521a64ed05 docs(m7): план Фазы 3 — skill-журналер + seed-allow реактивных навыков (SE-K, §4.2/§12) 2026-06-08 10:37:32 +03:00
Дмитрий 5320de8371 feat(m7-phase3): enforce-skill-journaler в FAIL_CLOSE_DISCIPLINE_HOOKS (P-7, манифест Фазы 6) 2026-06-08 10:36:42 +03:00
Дмитрий 3fad5e0401 feat(m7-phase3): SEED_SKILLS +реактивные дисциплинарные навыки (SE-K — стена не рубит вне плана) 2026-06-08 10:35:33 +03:00
Дмитрий 576e9c6079 feat(m7-phase3): enforce-skill-journaler — PostToolUse(Skill) журнал op:Skill + мост K2 (SE-K) 2026-06-08 10:34:19 +03:00
Дмитрий db3224992c docs(m7): план Фазы 2 — escape-survivability полная (правило 7б,в + §6 escape-honor) 2026-06-08 10:30:00 +03:00
Дмитрий 317f8cb6fb feat(m7-phase2): supreme-gate panicEscapeDecision — escape переживает сбой сетапа main (правило 7б) 2026-06-08 10:28:43 +03:00
Дмитрий b6fe66e34c feat(m7-phase2): normative-content-rules чтит escape — §6 канал правки ЗАКОНА (правило 7в) 2026-06-08 10:25:00 +03:00
Дмитрий 73fa2d61ff feat(m7-phase2): read-path-deny чтит escape владельца (правило 7в) 2026-06-08 10:21:34 +03:00
Дмитрий 9148c8c6bd feat(m7-phase2): enforce-floor panic-ветка — escape переживает throw floorDecide (правило 7б) 2026-06-08 10:19:48 +03:00
Дмитрий f71f1abef8 feat(m7-phase2): escapeAllowsEvent — panic-предикат escape-survivability (правило 7б) 2026-06-08 10:17:43 +03:00
Дмитрий 8a9e21c280 docs(m7): план Фазы 0 — fail-CLOSE-карвут + escape-survivability примитивы 2026-06-08 10:08:09 +03:00
Дмитрий 91a5acc4bf fix(m7-phase0): disciplineOutcome строгий fail-CLOSE на малформ-возврат (sharp-edges) 2026-06-08 10:07:52 +03:00
Дмитрий dc30c5daee feat(m7-phase0): disciplineOutcome fail-CLOSE + P-7 списки + контракт helpers:7 (правило 1) 2026-06-08 10:02:43 +03:00
Дмитрий bbd66c9b61 feat(m7-phase0): canonicalAction тотальна — внешний try + pathNormalizeSafe (правило 7а) 2026-06-08 09:59:29 +03:00
Дмитрий 06ad12cd94 feat(m7-phase0): pathNormalizeSafe — тотальный normalize (правило 7а) 2026-06-08 09:57:41 +03:00
Дмитрий 8153f96aff docs(m7): build-handoff #3 — Фаза 1 content-floor собрана + PS single-source (HEAD 8e56df38)
Сменяет build-handoff #2. Фиксирует: Фаза 1 (content-floor V1/V1-PS) реализована
полностью (11 коммитов 1c251d25..8e56df38), регрессия 3044+2 GREEN 0 регрессий,
commit-not-push. Готовый промт новой сессии + цепочка скилов (executing-plans драйвер,
per-Task audit→TDD→systematic-debugging→verification, строгий sharp-edges после опасных
Task, гейт закрытия audit→sharp-edges→variant-analysis→regression→verification) +
квирки (vitest фильтры раздельно, гейты блокируют rm/git rm, tdd-real-test-verifier diff
требует expect, for-of не it.each) + отложенное в Фазу 8 (удаление powershell-destructive,
полная PS enumeration). Следующий шаг — Фаза 0 через writing-plans по команде владельца.
2026-06-08 09:40:25 +03:00
Дмитрий 8e56df3842 refactor(m7-floor): PS-content единый источник — matchPsHardBlacklist в shell-content-rules (variant-analysis)
Закрывающий variant-analysis-гейт Фазы 1 вскрыл класс P-1 для PowerShell: у
powershell-gate был СВОЙ PS_HARD_BLACKLIST (29 паттернов), а пол использовал
отдельный узкий psContentBlock (7) — подмножество, которое дрейфовало бы (та же
проблема, что P-1 для Bash). После Фазы 8 (увольнение powershell-gate) пол оказался
бы слабее гейта, который он заменяет. Решение владельца: исправить сейчас.

Зеркало P-1:
- PS_HARD_BLACKLIST + matchPsHardBlacklist перенесены в единый дом shell-content-rules;
  powershell-gate ре-экспортирует (тест single-source-identity: ссылка gate === SCR).
- +bare-egress (Invoke-WebRequest/iwr/irm/curl/wget bare — floor НЕ default-deny, нужен
  в blacklist, не только в whitelist гейта) +rmdir +rm (алиасы Remove-Item, которые гейт
  ловил whitelist'ом default-deny — полу нужны явно).
- psContentBlock стал ТОНКИМ делегатом над matchPsHardBlacklist (симметрия с
  bashIsContentBlock); пол через него видит ТОТ ЖЕ набор, что гейт. Дрейф невозможен.
- Следствие (осознанно): floor теперь блокирует все Set-Content/sc/$env/Az/… как гейт
  (симметрия с Bash-полом, блокирующим все cp/mv/redirect). Escapable. FP-толерантность
  унаследована от гейта (например `sc query`/`del.txt` — gate-aligned, fail-safe).

powershell-destructive.mjs физически не удалён (живые gate'ы блокируют rm/git rm) —
оставлен тонким делегатом (НЕ второй источник). Удаление — follow-up по git-approval.

Регрессия tools-only: 3044 passed + 2 skip (baseline 2843+2, 0 регрессий).
2026-06-08 09:34:23 +03:00
Дмитрий 473fd21136 feat(m7-floor): §12 content-floor инвариант — весь BASH_HARD_BLACKLIST floored (P-6)
Task 1.6 Фазы 1 М7. Тест-генератор: КАЖДАЯ запись BASH_HARD_BLACKLIST рубится полом
даже как валидный шаг плана. Итерация по экспортированному списку → полнота порта ПО
КОНСТРУКЦИИ: новый паттерн без сэмпла → красный (drift-детектор), floor не рубит →
красный. Анти-регресс «непробиваемости» закреплён за Фазой 1 (P-6), не за §9.2-smokes.
+C16 stderr-redirect + #34 injection (отдельные ветки matchBashHardBlacklist).

Полная регрессия tools-only: 2997 passed + 2 skip (baseline 2843+2, +154 Фазы 1,
0 регрессий). 99 floor-decide GREEN.
2026-06-08 09:16:04 +03:00
Дмитрий 6556f5ca0a test(m7-floor): инвариант escape снимает content-block (Bash+PS) + специфичность P-2
Task 1.5 Фазы 1 М7. Код уже escapable (1.3/1.4) — тесты фиксируют инвариант против
регресса. Покрыто: Bash node -e + PS Remove-Item + PS forge-write снимаются точным
грантом; P-2 специфичность (грант A не открывает команду B) для PS И Bash; кросс-shell
изоляция (Bash-грант не открывает PS-команду — разные canonicalAction-префиксы). 72 GREEN.
2026-06-08 09:13:55 +03:00
Дмитрий 183733835f harden(m7-floor): PS-алиасы forge+delete (sharp-edges после 1.4)
Строгий sharp-edges-гейт после Task 1.4 вскрыл обход через PowerShell-алиасы:
- forge P-3: `sc`/`cpi`/`ni` (алиасы Set-Content/Copy-Item/New-Item) писали в
  ~/.claude/runtime/.env мимо литеральных глаголов → подделка escape-гранта.
  Закрытие: PS_WRITE_VERB_RE += sc/ac/cpi/copy/mi/move/ni/tee (path-gated, FP только
  с протектед-путём; контроль `sc query` не over-блокируется).
- delete: `del`/`ri`/`rd`/`rmdir`/`erase` -Recurse -Force обходили литеральный
  Remove-Item. Закрытие: PS_CONTENT_BLOCK_RE алиасы для long+short флагов.

154 GREEN (floor + powershell-destructive + enforce-floor + supreme-gate).
Отложено в Фазу 8 §5 (карты покрытия): `&` call-operator, Invoke-Command (icm),
PS sub-expression $(), полная enumeration алиасов — coverage, не структурный класс.
2026-06-08 09:12:38 +03:00
Дмитрий ea83a714e4 feat(m7-floor): floor-decide ветка PowerShell content-block + forge-страж (V1-PS, P-3)
Task 1.4 Фазы 1 М7. Ветка PowerShell пола (после Bash, до OBSERVE_TOOLS): psContentBlock
(Remove-Item/-Recurse/iwr/iex/Start-Process/Out-File/redirect) ИЛИ psProtectedWrite
(P-3 forge-страж: PS-запись в ~/.claude/runtime / .env / секрет — иначе Set-Content
подделывает escape-грант). Escapable owner-санкцией. Реоткрытие v3.8 F1. 60 GREEN.

НАХОДКА РЕАЛИЗАЦИИ: plan-версия psProtectedWrite тестила whole-string SECRET_PATH_RE
с $-якорем → `.env` в позиции аргумента (-Path app/.env -Value …) терялся. Робастнее:
проверяем каждый токен (без кавычек, \→/) против anchored RUNTIME_RE/SECRET_PATH_RE —
runtime (forge-вектор) И secret-write закрыты, обычная запись не over-блокируется.
2026-06-08 09:09:18 +03:00
Дмитрий 7277584eaf harden(m7-floor): bashIsContentBlock рубит sub-shell как класс (sharp-edges после 1.3)
Строгий sharp-edges-гейт после Task 1.3 вскрыл класс обхода: подстрочный
matchBashHardBlacklist не де-обфусцирует command-substitution. Split-assembly
`$(echo no)$(echo de) -e x` и backtick `echo node` собирают интерпретатор только
при shell-eval → в сырой строке 'node' нет → content-block FALSE → пол пропускал.
router-gate ловит сейчас, но Фаза 8 (увольнение router-gate) открыла бы класс.

Закрытие: bashIsContentBlock проверяет detectSubshell(raw).found ($()/backtick/
process-subst/heredoc) → любой sub-shell = произвольное исполнение → content-block.
Независимо от parse-успеха. Escapable; router-gate тоже блокирует все sub-shell →
0 новых FP. Подтверждено: per-segment токенайзер де-обфусцирует n''ode/no\de.
114 GREEN (floor + enforce-floor + supreme-gate).
2026-06-08 09:06:23 +03:00
Дмитрий 89e9ca159e feat(m7-floor): floor-decide content-block ветка Bash (V1, escapable)
Task 1.3 Фазы 1 М7. bashIsContentBlock (whole+per-segment, паритет с bashIsFloor, P-4)
через единый matchBashHardBlacklist (P-1). floorDecide Bash-ветка зовёт content-block
ПЕРВЫМ (до bashIsFloor); escape снимает (owner-санкция). 44 GREEN.

НАХОДКА АУДИТА (задокументирована в коде+тесте): NB плана «echo "node -e foo" НЕ
over-блокируется» недостижим при подстрочном matchBashHardBlacklist (не отличает
опасную строку-аргумент echo от команды-интерпретатора). Решение — принять FP:
floor УЖЕ принял этот класс для `git push "--force"` (fail-safe, escapable);
under-block в полу страшнее over-block. Парсинг командной позиции НЕ вводим.
2026-06-08 09:01:04 +03:00
Дмитрий a5b62fbfad fix(m7-floor): canonicalAction +PowerShell — escape-привязка специфична (P-2)
Task 1.2b Фазы 1 М7 (КРИТ). canonicalAction получил ветку PowerShell:
`powershell:${normalizeCommand(command)}`. Без неё PS уходил в write-fallback,
пустой путь резолвился в cwd → один escape-грант разблокировал ЛЮБУЮ PS-команду
в окне (тест специфичности был зелёным ложно: a===b==='write:<cwd>').

Сквозной фикс: тот же canonicalAction зовут пол (Task 1.4), стена (enforce-supreme-gate)
и консьюмер. Bash/Write/mcp-ветки не задеты. 118 GREEN (escape-grant + 4 потребителя).
2026-06-08 08:56:44 +03:00
Дмитрий 91af38ca11 feat(m7-floor): powershell-destructive psContentBlock (V1-PS)
Task 1.2 Фазы 1 М7. Новый модуль tools/powershell-destructive.mjs — psContentBlock
для PowerShell-tool (PS-нативные глаголы Remove-Item/-Recurse/iwr/iex/Start-Process/
Out-File/redirect НЕ матчат unix-regex classify-destructive). Реоткрытие v3.8 F1:
PowerShell-tool был полностью вне scope content-floor. 16 GREEN.
2026-06-08 08:55:30 +03:00
Дмитрий 3847c863ca feat(m7-floor): classify-destructive +contentBlock (правило 8, V1 Bash)
Task 1.1 Фазы 1 М7. Поле contentBlock = matchBashHardBlacklist(cmd) !== null
(единый источник P-1, whole-string) рядом с floor/suspicious. suspicious ||=
contentBlock (P-5: голоса судьи М4 видят content-опасное). reason расширен.

Поле для видимости судьи; фактический блок пола — bashIsContentBlock
(whole+per-segment, Task 1.3). 71 GREEN (+38 content-block кейсов, incl.
P-1-пробелы env-prefix/--watch/2>file/cp/mv).
2026-06-08 08:53:53 +03:00
Дмитрий 1c251d2592 refactor(m7-floor): matchBashHardBlacklist -> shell-content-rules (единый дом content-правил, P-1)
Task 1.0.5 Фазы 1 М7. Перенос BASH_HARD_BLACKLIST + stderrRedirectBlock +
matchBashHardBlacklist из enforce-router-gate.mjs в постоянный дом
shell-content-rules.mjs (там уже живут hasInjection + matchAny). router-gate
ре-экспортирует их для обратной совместимости (тесты + тело гейта).

Единый источник правды устраняет port-дрейф content-floor (М5) по конструкции:
content-block пола (Task 1.1/1.3) импортирует ТОТ ЖЕ матчер, а не ручную копию.

Тесты: +describe single-source identity (router-gate BASH_HARD_BLACKLIST ===
shell-content-rules ссылка) + matchBashHardBlacklist hosted-in-SCR. 233 GREEN.
Чистый рефактор-перенос, 0 изменений семантики.
2026-06-08 08:51:31 +03:00
Дмитрий 15af97adae docs(m7): build-handoff #2 — состояние после фолжа разбора плана (HEAD 475d381e)
Готовый промт для новой сессии: подтвердить HEAD 475d381e, прочитать
handoff#2 + спеку §13 addendum + план Фазу 1 (Task 1.0.5-1.6), спросить
владельца, НИЧЕГО не делать самому. Заменяет handoff#1 (stale HEAD 8ba9a21c).
Карта правок P-1..P-8 (план↔спека). Код НЕ строили. commit-not-push.
2026-06-08 08:24:17 +03:00
Дмитрий 475d381e0c docs(m7): fold plan-critique findings P-1..P-8 into plan + spec
Independent critical review of the implementation plan (audit-context ->
sharp-edges -> variant-analysis -> systematic-debugging, grounded against
real M1-M6 code). 8 findings, all code-grounded, no ghosts:

- P-1 (CRIT): content-floor "full port" was a SUBSET of BASH_HARD_BLACKLIST
  (lost #21 env-prefix / #22 --watch / #34 echo-inj / C16 2>file /
  cp-mv-chmod-chown-chgrp; cp X ~/.claude/runtime = escape-grant forge)
  -> single source matchBashHardBlacklist relocated to shell-content-rules
  (Task 1.0.5) + §12 content-floor invariant proves completeness.
- P-2 (CRIT): canonicalAction has no PowerShell branch -> every PS command
  collapses to 'write:' -> one escape unlocks ALL PS commands; Task 1.5 test
  was spuriously green (both sides equally broken) -> Task 1.2b + specificity.
- P-3 (HIGH): PS floor branch returned block:false skipping runtime/secret
  guard (command field not parsed) -> Set-Content ~/.claude/runtime forge
  -> psProtectedWrite guard (Task 1.4).
- P-4 (MED): content-block whole-string only -> bashIsContentBlock whole+per
  -segment parity with bashIsFloor (Task 1.3).
- P-5 (MED): suspicious blind to content-danger -> suspicious |= contentBlock.
- P-6 (MED): §12 CI-invariants ownerless -> assigned per phase (phase rule).
- P-7 (LOW): Phase 0 fail-CLOSE "subset" unlisted -> explicit hook list.
- P-8 (LOW): plan = detailed Phase 1 + scoped skeleton -> honest framing.

Plan: Tasks 1.0.5/1.1/1.2b/1.3/1.4/1.5/1.6 + phase-transition rule + self-review.
Spec: §5 PowerShell row, §12 M5 line, §13 addendum.
No code built. commit-not-push.
2026-06-08 08:20:40 +03:00
Дмитрий eac1c45bbb docs(m7): build-handoff в новую сессию — состояние после критразбора + плана
Готовый промт для подхвата: HEAD 8ba9a21c, дизайн закрыт + критразбор/поправки
(b98b1885) + план (8ba9a21c). Next = сборка Фазы 1 (content-floor) инлайн TDD
по команде владельца. Квирки (vitest/git/junction/escape/грязь дерева) + жёсткие
правила (commit-not-push, субагенты запрещены, ничего не делать самому).
2026-06-08 07:56:45 +03:00
Дмитрий 8ba9a21c9c docs(m7): план реализации — мастер (фазы 0-8) + Фаза 1 (content-floor) детально
writing-plans по поправленной спеке М7. Scope-check: 9 подсистем →
dependency-ordered фазы 0-8. Фаза 1 (content-floor V1/V1-PS — критический
корень переезда, §10 запрещает увольнять router-gate до неё) в полной
bite-sized TDD-детализации (Task 1.0-1.6 с кодом); фазы 0,2-8 scoped,
разворачиваются в bite-sized при подходе. Self-review: spec coverage полон,
type-consistency проверена. Исполнение — инлайн (субагенты запрещены владельцем).
2026-06-08 07:53:03 +03:00
Дмитрий b98b18850a docs(m7): критический разбор спеки — поправки V1/V1-PS/SE-I/SE-C/SE-K/SE-D/SE-A/SE-B
Независимый критический разбор дизайна М7 (цепочка audit-context-building →
sharp-edges → variant-analysis → systematic-debugging). Фактология §2
подтверждена реальным кодом 8/8; sweep Класса 1 полон. Закрытия:

- V1/V1-PS (КРИТИЧНО): полный port BASH_HARD_BLACKLIST + PowerShell-набор в
  content-floor М5 (правило 8), блок независимо от плана. Иначе in-plan
  node -e / curl-exfil / npm install проскальзывали как шаг плана; node -e
  мог подделать escape-грант и подорвать машины изнутри.
- SE-I/L6: escape-survivability (правило 7) — тотальные canonicalAction/
  normalize, panic-ветка до per-tool-логики, чтение escape всеми остающимися
  fail-CLOSE-стражами (read-path-deny/mcp-classification/normative-content-rules).
- SE-C/SE-K: журнал-K2 честно ограничен skill:-каналом; +PostToolUse
  skill-журналер + seed-allow реактивных навыков (иначе seed/ad-hoc навыки
  в журнал не попадают).
- SE-D: граница КАРТА/ЗАКОН для .mjs через "покрыт ли правкой план-шаг"
  (build-loop не клинит, ad-hoc самомодификация требует escape).
- SE-A: §4.2 метки [Pre]/[Stop]. SE-B: манифест до полного набора М1-М6.
- §1 промис снабжён предпосылками П1/П2/П3 + 4-е структурное условие; §13 changelog.

Verify-item прошлого handoff закрыт фактом: enforce-parallel-session-lock = no-op.
Только спека (.md), код не трогался.
2026-06-08 07:48:13 +03:00
Дмитрий 9e7ca7ef19 docs(m7): дизайн Машины 7 — растворение зоопарка + непробиваемая дисциплина + полный переезд М1–М6
Design-doc (12 секций + само-аудит) + DONE-handoff дизайн-фазы.
Цепочка: audit-context-building + sharp-edges + variant-analysis + brainstorming.
Карта обходов дисциплины (6 классов + корень enforce-hook-helpers:7) → поглощение
в М1–М6 по 6 правилам (fail-CLOSE / PreToolUse / журнал-факт / escape-only / манифест / громко).
3 куска М7: зоопарк+дисциплина / normative-канал (карта свободно, закон — escape) / доска «кто на посту».
Реализация НЕ начата — ждёт ревью владельца → writing-plans. commit-not-push.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 07:06:00 +03:00
Дмитрий a0b98d227f docs(m6): DONE-handoff промт-блок — sync HEAD 8bfef418 + планка 2843+2 после пост-аудит фиксов
Промт для новой сессии теперь указывает актуальный HEAD и регрессионную планку; детали 4 фиксов — в разделе «Пост-аудит правки» + памяти.
2026-06-07 19:49:28 +03:00
Дмитрий 8bfef418c2 docs(m6): DONE-handoff — пост-аудит правки FIX-1..4 + DOC-1 (escape не сквозной через зоопарк)
Зафиксированы 4 аудит-фикса и caveat DOC-1: escape чтут только стена М2/пол М5/egress; router-gate / runtime-write-deny / судья М4 escape не знают → реальные разрушительные не пройдут до М7 (растворение зоопарка). Ожидаемо по §7.
2026-06-07 19:43:18 +03:00
Дмитрий d221ba499d fix(m6): аудит-правки — G-5 egress токен, единый findOpenGrant, escape-журнал, уникальный id снимка
Аудит М6 (audit-context-building + sharp-edges + корректность; комплекс М1–М6), 4 практичных фикса (TDD):
- FIX-1: enforce-mcp-classification печатает точный FLOOR-ESCAPE токен в egress/verdict-блоке (G-5 для egress).
- FIX-2: escape-grant.findOpenGrant — единый предикат свежести для open и consume (гасит ИМЕННО открывший грант; чинит утечку one-shot при дублях/future-ts).
- FIX-3: enforce-supreme-gate.runGate — best-effort журнал escape (escape:true), указатель не двигается, сбой журнала не блокирует.
- FIX-4: enforce-snapshot — уникальный дефолтный id снимка (ts-pid-счётчик) против ms-коллизии refs/floor-snapshots.

Регрессия tools-only 2843 passed + 2 skip (+9, 0 регрессий). FIX-5 (подпись гранта) сознательно не делали (нулевая защита без ключа; protected-path уже гарантирует).
2026-06-07 19:43:05 +03:00
Дмитрий 3ea34d42dd docs(router-mentor): M6 DONE-handoff — промт для новой сессии (пуш/активация/М7) 2026-06-07 19:14:43 +03:00
Дмитрий d0e0bd18c9 test(m6): сквозные инварианты escape + snapshot; регрессия зелёная (2834+2) 2026-06-07 19:04:57 +03:00
Дмитрий 28b92d90e6 feat(m6): enforce-snapshot — git-точка возврата перед разрушительным (fail-close) 2026-06-07 19:03:36 +03:00
Дмитрий a5b99eaa7e feat(m6): snapshot-decide — триггер снимка + чистое-дерево vs ошибка 2026-06-07 19:02:21 +03:00
Дмитрий 6e2d485f44 feat(m6): egress-escape — снятие egress-блока совпавшим floor_escape 2026-06-07 19:01:04 +03:00
Дмитрий b83cfc65b9 feat(m6): floor-escape-consume — одноразовое погашение пропуска (PostToolUse) 2026-06-07 18:58:32 +03:00
Дмитрий 4a10932cb6 feat(m6): G-1 сквозной escape в верховной стене М2 (allow без продвижения указателя) 2026-06-07 18:55:56 +03:00
Дмитрий d2109ac1dc feat(m6): пол — escape во всех ветках, замена approvalOpen 2026-06-07 18:52:34 +03:00
Дмитрий a9e8585767 feat(m6): писать floor_escape-пропуск из AskUser-ответа 2026-06-07 18:46:13 +03:00
Дмитрий 6b44e7afd8 feat(m6): toFloorEscapeRecord — распознавание escape-одобрения 2026-06-07 18:44:09 +03:00
Дмитрий 87d84a2e3f feat(m6): escape-grant pure core — canonicalAction + escapeGrantOpen + readers 2026-06-07 18:41:42 +03:00
Дмитрий 4f7b1fab09 docs(router-mentor): M6 build-handoff — промт для сессии реализации
Готовый промт для новой сессии: дерево/ветка, состояние (дизайн+план+аудит закрыты,
HEAD 20c85ede, регрессия 2789+2 skip, не запушено), что строим (escape сквозной
override + авто-снимок), порядок пакетов 1-9+4b, HARD-RULE скилов (executing-plans
инлайн, audit-context перед патчами, TDD, review, verification, regression),
жёсткие правила (commit-not-push), квирки (vitest/git-PowerShell/гейт/git restore не
в whitelist/tdd-gate/память-два-охранника/судья-нейтрально/coverage-verify/baseline 2789),
аудит уже сделан (G-1 α / G-2 / G-5 / G-6 / G-8 — не повторять), старт с Пакета 1.

Только handoff-артефакт, кода нет. Без push (commit-not-push).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:33:14 +03:00
Дмитрий 20c85ede09 docs(router-mentor): M6 аудит плана — G-1 α (escape сквозь стену М2) + G-2/G-5/G-6/G-8
Аудит плана реализации (writing-plans self-review + audit-context-building сквозь
М1–М5 + sharp-edges). Главная находка G-1: верховная стена М2 (enforce-supreme-gate
Δ7 + разговорный режим) блокирует разрушительное/мутаторы независимо от пола → floor_escape
(только пол) был no-op сквозь стену. Вариант α (решение владельца): escape — сквозной
override, чтимый стеной (allow без продвижения указателя), полом, egress.

Спек: §3 +enforce-supreme-gate, §4 блок G-1 (сквозной escape) + G-5/G-6/G-8, §9 +патч,
§11 аудит-таблица. План: новый Пакет 4b (стена М2, TDD), Пакет 4 +G-2 (переписать блок
двери) +G-5 (точный токен) +G-6 (запрет override), активация +supreme-gate, self-review.

Проверено ОК: экспорты совпадают, М1/М3/М4 не ломаются, общий канал askuser-decisions
фильтр по type. Только дизайн-артефакты, кода нет. Без push (commit-not-push).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:18:59 +03:00
Дмитрий 3fb7a0517f docs(router-mentor): M6 spec — синк floor-escape-consume из плана (one-shot PostToolUse)
При написании плана выяснилось: строгая одноразовость «погашение после реального
исполнения» (§4 F-S1) требует отдельного PostToolUse-консьюмера. Добавлены модули
floor-escape-consume.mjs (ядро) + enforce-floor-escape-consume.mjs (обёртка) в §3/§9,
уточнён §4 (погашение после исполнения → сбой снимка пропуск не тратит), §9 активация
+ PostToolUse, §11 поправка план→спек. Спек и план теперь совпадают.

Только дизайн-артефакт, кода нет. Без push (commit-not-push).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:01:05 +03:00
Дмитрий 719648cd08 docs(router-mentor): M6 implementation plan — escape + auto-snapshot (TDD пакеты 1-9)
План реализации Машины 6 по спеку 2026-06-07-router-mentor-machine-6-design.md.
9 пакетов, bite-sized TDD (RED→GREEN→commit), весь код в шагах, конвенции
(vitest абс-команда / commit через PowerShell / TDD-гейт / audit-context перед патчами).

Пакеты: 1 escape-grant ядро · 2 toFloorEscapeRecord · 3 писатель floor_escape ·
4 пол escape во всех ветках · 5 floor-escape-consume (one-shot, PostToolUse) ·
6 egress-escape · 7 snapshot-decide · 8 enforce-snapshot · 9 интеграция+регрессия.

NB: Пакет 5 вводит модуль floor-escape-consume, которого нет в инвентаре §9 спека —
операционализация одноразовости «погашение после исполнения»; отмечено в self-review,
к согласованию на ревью плана. Планка регрессии ≥ 2789 passed + 2 skip.

Только план-артефакт, кода нет. Без push (commit-not-push).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:57:01 +03:00
Дмитрий 47de8447dd docs(router-mentor): M6 design — факт-аудит кода, правки F1-F4/F-S1/F-S2/I1-I4
Аудит спека М6 тремя линзами (audit-context-building сверка с реальным кодом М5 +
sharp-edges; agentic-actions-auditor неприменим — CI-scope). Внесены правки:

F1  настоящий §4.5-парсер = askuser-answer-parser + enforce-askuser-answer-parser
    (не enforce-branch-switch).
F2  floor-набор разнесён на 3 локуса (Bash / Write-ветка / egress); escape на все три (B).
F3  binding = точное совпадение канонической строки (normalizeCommand / tool:pathNormalize
    / egress), не хеш над classifyDestructive (тот даёт булевы).
F4  toApprovalRecord git-only → migrate:fresh/db:wipe/.env были не одобряемы; floor_escape
    закрывает.
F-S1 escape = отдельный kind floor_escape + отдельный reader + one-shot консум
     (не переиспользование 5-мин approve_git_operation).
F-S2 снимок: чистое дерево → ref=HEAD (успех); fail-close только на реальную ошибку git.
I1-I4 переиспользование helper'ов / честный scope снимка / ясность runtime-записи хуком /
     фиксация неприменимости agentic-actions-auditor.

§11 — карта находка→правка. Только дизайн-артефакт, кода нет. Без push (commit-not-push).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:40:32 +03:00
Дмитрий d02932b053 docs(router-mentor): M6 design — escape + auto-snapshot (brainstorming)
Машина 6 (новая фаза дизайна эпика «роутер-наставник»): аварийный выход (escape)
+ авто-снимок (git-точка возврата). Достраивает безопасность пола М5.

Решения с владельцем: Р-М6-1 scope = escape + снимок (М7 = normative-канал /
растворение зоопарка / доска); Р-М6-2 escape = всплывающий вопрос (side-channel,
отпечаток-binding); Р-М6-3 escape на весь floor-список (B); Р-М6-4 снимок = git-
состояние (A); Р-М6-5 подход A (escape в floor-decide + отдельный enforce-snapshot).

Spec: docs/superpowers/specs/2026-06-07-router-mentor-machine-6-design.md.
Только дизайн-артефакт, кода нет. Без push (commit-not-push).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:15:20 +03:00
Дмитрий 32ffab621d docs(m6): handoff в новую сессию — М5 закрыта+переаудичена, М6 = новая фаза дизайна
Готовый промт: дерево/состояние (HEAD 849723bc, аудит М5 F-1/F-3/F-6 + F-2 память + F-4=C,
регрессия 2789+2 skip, не запушено) + шов М5<->М6 (spec §5 экспорт) + scope М6 (spec §6 YAGNI:
авто-снимок / портативный normative-канал / escape / растворение старых хуков; доска = М7) +
HARD-RULE скилов (brainstorming->writing-plans, инлайн, без суб-агентов) + квирки (vitest/git/
гейт/tdd/запись-в-память-два-охранника/судья-нейтральные-слова/baseline 2789) + хвосты М5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:48:45 +03:00
Дмитрий 849723bc04 fix(m5): F-6 — нижняя граница времени в floor-decide approvalOpen
approvalOpen считал свежим одобрение с будущим ts (now - ts < 0 <= window) — часовой
сдвиг/подлог открывал дверь владельца. Добавлена нижняя граница now - ts >= 0: свежесть =
ts в прошлом И в пределах окна.

Аудит Машины 5 (объектив корректность). TDD RED->GREEN. Регрессия tools-only 2789 + 2 skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:18:44 +03:00
Дмитрий 7dd3a16531 fix(m5): F-3 — манифест floor-manifest-check проверяет весь защитный контур
DEFAULT_REQUIRED_HOOKS проверял только enforce-floor — owner мог зарегистрировать пол,
забыть верховную стену / exfil-стражей и получить зелёный «protected». Расширено до
security-load-bearing набора: enforce-floor + enforce-supreme-gate + normative-content
+ read-path-deny + mcp-classification. «Пол подтверждён» теперь = весь контур. WARN-only
(Δ8 — сигнал, не блок); owner может передать иной requiredHooks.

Аудит Машины 5 (объектив sharp-edges). TDD RED->GREEN. Регрессия tools-only 2789 + 2 skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:18:26 +03:00
Дмитрий 9c3205ad7c fix(m5): F-1 — убрать мёртвый контент-скан в enforce-read-path-deny
decide() гейтил по content в ветке, недостижимой в проде: enforce-read-path-deny —
PreToolUse(Read)-хук, main() не передавал content, а контента до чтения нет. Ветка
+ импорт scanSecrets убраны — decide() гейтит строго по пути (path-deny). Реальный
exfil (исходящий payload) закрыт живым enforce-mcp-classification.scanEgress; чтение
секрета само по себе не вынос.

Аудит Машины 5 (объектив sharp-edges + agentic-actions-auditor). TDD RED->GREEN.
Регрессия tools-only 2789 passed + 2 skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:18:05 +03:00
Дмитрий 2925d063fb docs(m5): handoff session-final — Машина 5 закрыта, финализация + продолжение эпика
Готовый промт для новой сессии: дерево + состояние (Пакеты 5-8 закрыты, 14 коммитов
24ce7b39..5d350b69, регрессия 2788+2skip, не запушено) + что осталось (finishing-branch под
«пуш» / память direct:memory-sync / активация владельцем) + HARD-RULE алгоритма скилов (запрет
нарушения, суб-агенты запрещены) + квирки (vitest/git/гейт/tdd-хуки/baseline 2788).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:35:17 +03:00
Дмитрий 5d350b6997 feat(m5): Пакет 8 — Δ3 честный двухтакт reconcile (8.1 пред-запись + 8.2 реконсилер)
Δ3: убрано обещание «атомарно на исполнении» (PreToolUse не видит факт). Достижимый максимум —
два такта:
- 8.1 (runGate): пред-запись НАМЕРЕНИЯ в журнал ДО allow. Журнал вернул false ИЛИ бросил →
  стена НЕ разрешает (block), указатель не двигается («нет записи → нет действия», явно).
  Backward-compat: push → length (truthy) = успех; только явный false/throw → block.
- 8.2 (enforce-reconcile.mjs, новый): PostToolUse-сверка. reconcileAction — исполненное
  действие без пред-записи → action-without-record (возможен обход). findOrphanIntents —
  пред-записи без исполнения → record-without-action. WARN-уровень (не блок: PreToolUse-пол
  уже отработал, PostToolUse не отменяет исполненное). Чистые функции + fail-quiet I/O main.

+2 (supreme-gate runGate) +5 (reconcile) тестов. Полная tools-only регрессия 2788 + 2 skip
(0 регрессий). Машина 5 (Пакеты 5-8) собрана полностью.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:29:48 +03:00
Дмитрий 7b2a3d32aa feat(m5): 7.6 инвариант разделения дисциплина/защита (Пакет 7, Блок 4.6)
Структурный guard-инвариант: модули защиты-пола GREEN (criterion-green, floor-signer)
не несут override-вокабуляра — мутация P18 + неподделываемый по-критерийный GREEN (Пакет 5)
это ЗАЩИТА (без override). tdd-gate остаётся ДИСЦИПЛИНОЙ (fail-open + override) в ОТДЕЛЬНОМ
хуке. Перенос мутации/GREEN-пола под override упадёт здесь. +positive control (не вакуумен).
7.7 (неподделываемый GREEN/RED) — закрыт Пакетами 3+5.

Guard-тест (prod-код не пишется; концепт реализован Пакетом 5). +3 теста.
Полная tools-only регрессия 2781 passed + 2 skip (0 регрессий). Пакет 7 завершён.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:24:52 +03:00
Дмитрий a88a80ed0b feat(m5): 7.5 parallel-session-lock pid-liveness (Пакет 7, дыра 5, Блок 4.5)
Закрыта дыра 5: свежий (не-stale) лок МЁРТВОГО держателя блокировал на весь TTL.
- isProcessAlive(pid): process.kill(pid,0) — ESRCH→мёртв, EPERM→жив; невалидный pid→мёртв.
- acquire +isPidAlive (инъектируемый): перехват, если держатель не жив (помимо stale/same-session).
  Без isPidAlive — backward-compat (старое поведение).
- runAcquireDecision +isPidAlive (default isProcessAlive) → acquire. Живой держатель → блок;
  мёртвый → перехват. Хук остаётся fail-open (availability, не защитный пол).

+5 (pure) +1 (wrapper) тестов; существующий «fresh lock» тест уточнён на живого держателя.
parallel-session-lock + enforce 27/27.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:23:15 +03:00
Дмитрий 4cfca9cc55 feat(m5): 7.4 workflow-gate inline-script + keychain-паттерны (Пакет 7, C5, Блок 4.4)
Закрыта дыра C5 (inline `script` был вне охвата → полный обход workflow-гейта):
- decide: inline `script` гейтится как scriptPath — скан DANGEROUS_PATTERNS + одобрение по
  sha256 контента (approve_workflow_script, 5-мин окно). Без одобрения → block.
- DANGEROUS_PATTERNS +keytar +get/set/delete/findPassword|Credentials(Sync) — контроллер
  не напишет workflow, читающий ключ подписанта/судьи из OS-keychain (действует и для scriptPath).
- main: при inline берёт content/sha256 из tool_input.script напрямую.
NB: regex-детектор eval()/child_process — паттерн-матчер, не вызов (header-коммент L19-21).

+5 тестов. enforce-workflow-gate 10/10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:18:46 +03:00
Дмитрий ecca24f451 feat(m5): 7.3 mcp-classification +egress-скан исходящего (Пакет 7, Блок 4.3)
scanEgress(toolInput): даже разрешённый классификатором MCP-вызов не должен выносить
наружу — block при: секрет в аргументах (общий secret-scan, анти-дрейф с 7.1), data:base64
URI / file:// схема / IP-литерал URL (обход DNS/allowlist), раздутый payload (> maxBytes).
exfil-схемы узко-таргетированы (data:...;base64, и file://) — обычный текст не ловят.
decide: после allow-классификации запускает scanEgress → block с reason «egress: …».

+8 тестов. enforce-mcp-classification 10/10. Дыра «что уходит наружу» закрыта.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:15:49 +03:00
Дмитрий 7e61b9c367 feat(m5): 7.2 normative-content детерм-only + fail-CLOSE (Пакет 7, H3, Блок 4.2)
Закрыта дыра H3 (LLM fail-open / wrapper fail-quiet как escape):
- isProtectiveNormativePath: защитное подмножество CLAUDE.md/память/Pravila.
- decide +protectiveSubset: деградация судьи, который БЫЛ активен (budget_exhausted —
  вектор обхода: исчерпать бюджет чтобы пройти), → fail-CLOSE для защитного подмножества.
  Деградация no_api_key (судья выключен — дефолт) → детерм-only флор (fail-open), БЕЗ
  over-block легитимного claude-md-management в обычном режиме (reason-based различение).
- main() catch → fail-CLOSE (block) вместо fail-quiet; передаёт protectiveSubset.
- «Память = совет, НИКОГДА не разрешение» — детерм-слои (recovery/fake-rule/suspicious)
  держат всегда, до судьи.

Backward-compat: существующий fail-open-degraded тест (без protectiveSubset) сохранён.
+9 тестов. enforce-normative-content-rules 25/25.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:13:02 +03:00
Дмитрий acc9045016 feat(m5): 7.1 read-path-deny +контент-скан секретов + общий secret-scan (Пакет 7, Блок 4)
Новый общий secret-scan.mjs (анти-дрейф — один источник секрет-паттернов на 7.1 read-выдачу
и 7.3 egress): scanSecrets(text) → {found, hits}. Секрет-подмножество (не PII): PEM private
keys, токены провайдеров (AWS/GitHub/OpenAI/Slack/Sentry/Yandex/JWT/Bearer, regex согласованы
с observer-pii-filter), строки подключения с кредами (scheme://user:pass@). Чистая, без /g.

enforce-read-path-deny.decide расширен опциональным content: путь-деналист — грубый пре-фильтр;
если выдача Read содержит секрет (даже из не-protected пути) → block (fail-CLOSE). Активируется
PostToolUse-обёрткой (content); PreToolUse path-слой backward-compat не тронут.

+9 (secret-scan) +4 (read-path-deny) тестов. Дыра 6 (read без контента) закрыта.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:06:53 +03:00
Дмитрий 32b688e091 feat(m5): Блок 5 — сквозные инварианты N5/Δ9 + манифест-WARN Δ8 (Пакет 6)
Guard/инвариант-тесты над существующим кодом (новый prod-код только в 6.5) +
SessionStart-самопроверка регистрации. Все non-vacuous (positive controls).

6.1 (N5) — строгая проверка пола: поведенческие инварианты запирают фиксы аудита
  M1-M4 (finalGate снимает вето ТОЛЬКО на floorBlocked===false; runGateLadder не
  проходит на ok!==true / undefined / throw) + структурный grep по judge-orchestrator
  (центр «по всем полам разом»). Scope grep'а сужен до orchestrator — токенайзерные
  .ok в floor-decide НЕ floor-вердикты (избегаем ложного срабатывания).
6.2 (Δ9-а) — анти-усыхание floor-набора: снимок 11 команд, обязанных оставаться
  floor:true; удаление строки FLOOR_RE без ADR флипнёт одну → CI краснеет. +control.
6.3 (Δ9-б) — единственный источник DESTRUCTIVE_RE: уже в seed Пакета 1 (подтверждено).
6.4 (escape≠protection) — floor-хуки (enforce-floor/floor-decide) не ссылаются на
  override-вокабуляр; перенос защиты под override падает здесь. +control.
6.5 (Δ8) — floor-manifest-check.mjs (новый): SessionStart читает settings.json,
  cry-WARN при отсутствии регистрации пол-хука. НЕ блок (проблема черепах: «пол стоит»
  = сигнал, не гарантия). Чистое ядро collectHookCommands/checkManifest (settings
  инъектируется, битые секции не бросают) + fail-quiet I/O main (exit 0).

+27 тестов (21 инвариант + 6 манифест). Полная tools-only регрессия 2741 + 2 skip
(0 регрессий). Активация манифест-хука в settings.json — шаг владельца.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:01:25 +03:00
Дмитрий 0875cd24ab feat(m5): criterion-green — по-критерийный производитель GREEN + мутация P18 (Пакет 5, 5.6)
Новый чистый модуль criterion-green.mjs. produceGreen эмитит подписанный подписантом
green ТОЛЬКО при настоящем зелёном прогоне: оба условия обязательны —
- testPassed (тест шага реально прошёл);
- mutationKilled (P18: сломали код → тест обязан покраснеть; выжил → не проверяет → не green).
Иначе green:false с причиной (test-not-passed / mutation-survived / no-signer-key) — fail-CLOSE.
Подпись — над тройкой {criterion_id, code_fingerprint, occurrence}; green/coverage — поля.
codeFingerprint(fileContents) — Δ2 детерминированный отпечаток изменённых файлов+тестов
(canonicalJson сортит ключи; правка файла → другой отпечаток → green аннулируется).
Чистый: факты прогона + ключ инъектируются (fs/исполнение тестов — живая обёртка владельца).

+7 тестов (вкл. интеграцию: произведённый green проходит весь критерий-гейт 5.5).
Полная tools-only регрессия 2714 passed + 2 skip (0 регрессий).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:54:22 +03:00
Дмитрий f0f7128cc3 feat(m5): критерий-гейт = лесенка из 4 шагов (Пакет 5, 5.5, Δ6)
Δ6: НЕ плодим criterionFullyProven — критерий-гейт = РОВНО 4 проверки поверх
существующей runGateLadder. criterionGateSteps(input) → [criteria-from-sealed-plan,
criteria-green-matched, fingerprint-fresh, green-signatures-valid] (И-семантика:
id ∈ печать → green-присутствие → свежесть отпечатка Δ2 → подпись подписанта Δ5).
runCriterionGate гоняет их через runGateLadder (короткое замыкание на первом провале).
Структурный тест «ровно эти 4 шага в порядке» — лекарство «забыл шаг». End-to-end:
валидный сценарий (signGreen) проходит; по одному провалу на каждый шаг.

+7 тестов (2 структура + 5 И-семантика). Полная tools-only регрессия 2707 + 2 skip
(0 регрессий; import judge-gate-floor, цикла нет).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:51:11 +03:00
Дмитрий 7110d0fa37 feat(m5): greenSignaturesValid — подпись подписанта на green (Пакет 5, 5.4, Δ5)
Δ5: подлинность green = подпись ПОДПИСАНТА, не совпадение id (id = целостность).
greenSignaturesValid реконструирует подписанную тройку {criterion_id,
code_fingerprint, occurrence} из green-run и проверяет verifyGreen (floor-signer).
Синергия с 5.3: подмена отпечатка для прохода свежести ломает подпись здесь
(отпечаток входит в подписанную тройку). Чистая, fail-CLOSE (нет ключа/sig →
unsigned). Красные прогоны подписи не требуют (их ловит criteriaGreenMatched).

По авторитетному Δ6 — ОТДЕЛЬНЫЙ шаг 4 лесенки критерий-гейта.

+5 тестов. judge-gate-floor 37/37 (аддитивно; import floor-signer, цикла нет).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:48:13 +03:00
Дмитрий ea0dee23e2 feat(m5): fingerprintFresh — свежесть отпечатка green (Пакет 5, 5.3, Δ2)
Δ2: зелёный прогон засчитывается только если его code_fingerprint совпадает с
текущим (изменённые файлы шага + тесты; currentFingerprints инъектируется живым
гейтом). Правка файла после прогона → расхождение → green аннулирован (stale).
Чистая функция, fail-CLOSE (нет текущего отпечатка ≠ записанному → stale). Красные
прогоны не проверяются (их «не-зелёность» ловит criteriaGreenMatched).

По авторитетному Δ6 — ОТДЕЛЬНЫЙ шаг лесенки критерий-гейта (не внутрь
criteriaGreenMatched, которая остаётся «green-присутствием», Δ6 шаг 2).

+5 тестов. judge-gate-floor 32/32 (чисто аддитивно).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:45:12 +03:00
Дмитрий cab7ffc3cf feat(m5): механическая значимость шага в k5CriterionCheck (Пакет 5, 5.2, C3/F18)
Значимость шага больше НЕ из контроллер-флагов trivial/significant (их подделка
разжаловала значимый шаг и обнуляла K5). Новый isSignificantStep(step):
- object разрушителен (classifyDestructive.suspicious) → значим, перевешивает op
  (анти-обход «op:Read, object: rm -rf»);
- op НЕ из READONLY_OPS (Read/Grep/Glob/LS/NotebookRead) → значим (мутирующий/
  неизвестный/пустой op → сомнение → значим, fail-CLOSE);
- чистое чтение → не значим, критерий не требуется.
k5CriterionCheck вызывает isSignificantStep вместо trivial/significant-скипов.

+7 тестов. Полная tools-only регрессия 2690 passed + 2 skip (0 регрессий).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:42:36 +03:00
Дмитрий 54f0974ba5 feat(m5): sealed criterion_id в plan-lock.freezePlan (Пакет 5, 5.1, Δ5)
Каждый шаг получает детерминированный criterion_id = sha256(canonicalJson
содержимого шага без criterion_id) ДО planId/печати → id запечатан подписью плана.
- stepCriterionId (чистая, идемпотентная: criterion_id исключён из хеша).
- sealedCriterionIds(frozenPlan) — вход для criteriaFromSealedPlan (Гейт-2, F3/F9).
- id = ЦЕЛОСТНОСТЬ, не подлинность (Δ5): подмена object/op меняет id; подмена id
  после печати ломает verifyFrozenPlan; подлинность зелёного даёт подпись подписанта (5.4).

+7 тестов. Полная tools-only регрессия 2683 passed + 2 skip (0 регрессий).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:38:59 +03:00
Дмитрий 24ce7b39f1 feat(m5): запрет env-fallback ключа + сужение node-whitelist (Пакет 4, Блок 2 RISK, Δ4)
4.1 env-fallback УБРАН (receipt-key-config + judge-key-config):
- resolveReceiptKey/resolveJudgeKey читают ТОЛЬКО keychain; ROUTER_MENTOR_*_KEY env
  больше не читается (анти-казуальная утечка через env/логи; шов под HSM). `env` убран
  из сигнатуры (extra-проп игнорируется); KEY_ENV сохранён в _internals (domain-тест R-31).
- Критерий: env задан, keychain пуст → null.

4.2 node-whitelist сужен (enforce-router-gate, RISK):
- широкое `node <любой-файл>` правило удалено из SAFE_EXACT; новая ветвь classifyWhitelist
  через nodeScriptAllowed: allow только плоский tools/<file>.{mjs,cjs,js} + vitest-runner
  + node --version/-v; иначе default-deny. Инспекция ТОКЕНА пути (shell-quote сохраняет
  бэкслеши Windows-пути — инвариант запиннен тестом), нормализация \\ → /.
- verify (живой): после правки `node vitest.mjs` отработал по всем 147 тест-файлам —
  тул-чейн не сломан (canary). Гейт жив в сессии (powershell+router фаеры подтвердили).

Δ4 честно (§7): сужение — анти-казуальная мера, НЕ полный close. Остаток «контроллер
commit'ит разрешённый tools-скрипт и запускает» принят; file-watcher блокирует
session-edited до commit. Полная изоляция ключа — только HSM/YubiKey (v4.2).

TDD RED-GREEN per задача; tools-only регрессия 2677 passed + 2 skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:19:02 +03:00
Дмитрий 8a754e9a19 feat(m5): floor-signer + RECEIPT_DOMAINS.M5_GREEN + occurrence (Пакет 3, Блок 2)
- receipt-sign.mjs: +домен M5_GREEN (отдельный, не default; cross-domain тест)
- floor-signer.mjs (новый): signGreen/verifyGreen/acceptGreen — подпись по-критерийного
  GREEN в домене M5_GREEN, occurrence монотонный (анти-пере-зачёт одного green на 2
  критерия), подмена criterion_id/occurrence ломает HMAC → bad-signature.
- Δ4 честно: подписант = анти-казуальная утечка ключа + шов под HSM, НЕ защита ключа
  от мотивированного контроллера (node tools/X.mjs до Пакета 4); см. spec §7/§10.
- Чистые функции (ключ-аргумент), как receipt-sign.mjs; процесс-обёртка/резолв ключа —
  Пакет 4+; потребитель держит lastOccurrence — Пакет 5.
- TDD RED-GREEN; tools-only регрессия 2666 passed + 2 skip (+17).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 11:59:56 +03:00
Дмитрий 5d96e89d5c docs(m5): handoff в новую сессию после Пакетов 1-2
Готовый промт-эстафета для продолжения Машины 5 с Пакета 3: дерево (worktree+ветка),
источники (spec §3/§10 + план R1 + этот handoff), план использования скилов с
HARD-запретом на нарушение алгоритма (audit-context только 4/5/7; net-new 3/6/8 skip;
TDD RED-first; focus-линза; systematic-debugging на непредвиденный красный; инлайн без
суб-агентов; commit-not-push), квирки (vitest absolute / git PowerShell / tdd-real-test-
verifier не принимает it.each + coverage-ссылка в Edit), состояние (Пакет 1 22b84fbb +
Пакет 2 b6d06ede, baseline 2649+2), вскрытые аудитом расхождения план↔код (writer
unsigned / F5 мнимая / force-push door мут / router-gate default-deny).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 11:49:37 +03:00
Дмитрий b6d06ede87 feat(m5): Пакет 2 — несущий пол (floor-decide + enforce-floor + дверь Δ1 + Δ7 + C4)
Блок 1 Машины 5: вето-до-плана на необратимое, независимо от членства в плане.

- tools/floor-decide.mjs — чистое ядро: Bash floor (classifyDestructive whole-string +
  посегментно tokenizeBash — кавычки/chaining нейтрализованы) + tool-agnostic запись
  (P10-a: .env/ключ/cert + ~/.claude/runtime, fail-CLOSED на normalize-throw).
- Дверь владельца Δ1 — read-only approve_git_operation (exact+5мин окно, НЕ consume).
- tools/enforce-floor.mjs — обёртка matcher '*' (регистрация — шаг владельца, ОТДЕЛЬНО
  от стены М2), loadApprovedGitOps read-only, fail-CLOSED, НЕ импортирует plan-lock.
- C4: migrate:fresh/refresh/reset убраны из router-gate whitelist → default-deny даже
  без floor-хука (SPOF-защита); bare migrate + migrate:rollback остаются.
- Δ7: enforce-supreme-gate.decide на allow-пути зовёт classifyDestructive(...).floor —
  разрушительный in-plan шаг НЕ продвигает указатель (стена не благословляет снос).
- Атака-линза: закрыт P10-a-пробел (MCP-writer в .env floor бы пропустил).

Audit-context вскрыл расхождения план↔код (задокументированы в floor-decide JSDoc):
writer approval НЕ подписывает (интегрити = protected-path side-channel, не HMAC);
F5-гонка мнимая (loadApprovedGitOps read-only+window, не consume); force-push доп-блок
shell-content GIT_HARD (дверь для него мут — защита-в-глубину). Дверь = шов под М6.

Регрессия tools-only: 2649 passed + 2 skip (+41). Residual: node-whitelist hole
для записи в runtime (Пакет 4 сужает); base64-обфускация floor (~0.5%, М6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 11:42:58 +03:00
Дмитрий 22b84fbb2e feat(m5): classifyDestructive двухуровневый + rewire a2CaseSelect/detectHighRisk (§4, N1)
Пакет 1 Машины 5 (роутер-наставник, пол). Единый источник разрушительности
classify-destructive.mjs: floor (точный необратимый набор, hard-block) + suspicious
(грубый набор для голосов судьи), инвариант floor => suspicious.

- N1: голый migrate/migrate:rollback/migrate --force => suspicious, НЕ floor (деплой не ломается).
- rewire a2CaseSelect (M4) и detectHighRisk (M3) на classifyDestructive.suspicious;
  оба локальных DESTRUCTIVE_RE удалены (Δ9-б — единственный источник).
- Δ9(б) seed CI-инвариант m5-floor-invariants.test.mjs (positive-control, не вакуумный).
- sharp-edges (Step 1.9): floor force-push выровнен с каноном shell-content — закрыт
  обход кавычками git push "--force" (длинные флаги без обязательного \s; -f/+ с \s).
- parity к двум прежним regex сохранён (format/db:wipe/force-push-литерал).

Регрессия tools-only: 2608 passed + 2 skip (+48). Residuals (chaining/reset-quote)
переданы Пакету 2 (tokenizeBash посегментно).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 11:21:38 +03:00
Дмитрий 23f5936d0d docs(m5): handoff-промт для переезда в новую сессию
Готовый промт: дерево (worktree+ветка), источники (spec §3/§10 +
план R1), план использования скилов с hard-запретом на нарушение
алгоритма (audit-context-building только 1/2/4/5/7; TDD RED-first;
focus-линза; systematic-debugging на непредвиденный красный; инлайн
без суб-агентов; commit-not-push), квирки окружения, старт с Пакета 1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:57:32 +03:00
Дмитрий 15829b8eb7 docs(m5): план R1 — 7 правок факт-аудита по реальному коду
F1 (+format в suspicious — терял rewire detectHighRisk),
F2 (длинно-флаговый rm --recursive --force теперь floor),
F3 (Step 1.7 — только строка router-engine:21),
F4 (удалить router-engine:12 DESTRUCTIVE_RE — иначе Δ9-б падёт),
F5 (floor читает одобрение read-only, не consume — гонка с
existing enforce-branch-switch),
§2-точность (floor инспектирует и Write file_path),
скил-правки (audit-context-building не на net-new 3/6;
+systematic-debugging; линзы focus+по риску).

Все находки проверены по коду M1-M5 (3 разных DESTRUCTIVE_RE,
6 файлов хуков Пакета 7 подтверждены). Без призраков.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:53:24 +03:00
Дмитрий 89bc392ef4 docs(m5): план реализации — 8 пакетов TDD + цепочка скилов
Master-план М5: фундамент (Пакет 1 classifyDestructive двухуровневый)
расписан бит-сайз с реальным кодом; пакеты 2-8 — задачи+критерии+
линзы (бит-сайз just-in-time, RED-first — без пред-написания призраков).
Покрывает §3 блоки + §10 амендмент (Δ1-9, N1, N5). Self-review поймал
GAP по Δ3 reconcile → добавлен Пакет 8. Инлайн-исполнение (без суб-
агентов), commit-not-push, node-whitelist как отдельный risk-пакет 4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:39:35 +03:00
Дмитрий 89f4d1a011 docs(m5): self-review — явная пометка о замене §3 Блок 2
Δ4 опровергает фразу §3 Блок 2 «нельзя прочесть ключ в своём
процессе» (читается через node tools/X.mjs) — добавлен явный
маркер замены, чтобы спек не противоречил сам себе.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:30:48 +03:00
Дмитрий 930630955e docs(m5): амендмент 2026-06-07 — фаза 4 brainstorming + факт-аудит
3 развилки (дверь владельца в М5 / отпечаток «по делу»+мутация /
честный двухтакт reconcile) + переписаны призраки: Δ4 (подписант =
анти-казуальная утечка + шов под HSM, НЕ защита ключа от контроллера)
и Δ6 (используем существующую runGateLadder, не новую функцию) +
N1 (classifyDestructive двухуровневый — голый migrate не блокировать,
иначе пол ломает деплой) + N5 (тест-инвариант строгой проверки пола).
Каждый claim проверен по реальному коду M1-M5 (audit-context-building).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:29:46 +03:00
Дмитрий 5dc6785940 docs(router-mentor): Machine 5 floor — design spec (audit-driven)
М5 «укрепление защиты-пола» — дизайн после корреляционного аудита
М5 ↔ М1/М2/М3/М4 (Фазы 1-2: построчный контекст audit-context-building
+ 3 адверсариальные линзы — грабли/корректность/атака — по всему периметру).

Главная находка: стена М2 = соответствие плану, НЕ пол → легитимно
запечатанный необратимый шаг (force-push/migrate:fresh/секрет/runtime)
проходит, пол не вызывается. 5 блоков: вето-до-плана / изоляция ключа
(отдельный процесс-подписант) / критерий-носитель / 7 усилений /
сквозные fail-close инварианты. Карта закрытия W1/C1-C5/H1/H3/F-серия.

Решения: полный объём; отдельный процесс-подписант; граница М5↔М6 =
только шов. Design-only — не построено.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 09:53:43 +03:00
Дмитрий 622ac4df28 fix(router-mentor): third audit (correctness lens) — close 3 M4 fail-open/crash holes
Третий построчный аудит машин 1-4 свежим объективом (корректность логики /
реальные баги — НЕ понимание, НЕ грабли; это были два прошлых прохода).
4 читающих под-агента code-analyzer. M1/M2/M3 — багов ядра нет (подтверждено).
M4 (судья, инертен; код должен быть верен и при включении): 3 реальные дыры по TDD.

M4:
- judge-engine.mjs runJudge: (raw.objections||[]).filter((o)=>o.verdict) падал на
  objections=[null] (o.verdict на null) и на не-массиве (.filter is not a function).
  || гасит только falsy. Краш ломал вердикт; в инертной обёртке выброс уходил в
  catch→block:false = fail-open. Fix: Array.isArray(...)?...:[] + (o && o.verdict).
- judge-verdict-slots.mjs: String(raw).trim().length скрывал не-строки — слот {}
  давал '[object Object]' (длина 15) и проходил как содержательный (мусорный
  объект/массив штамповал форму вердикта). Fix: слот обязан быть строкой
  (typeof raw !== 'string' → trivial). Мягкий fail-open формы закрыт.
- judge-orchestrator.mjs runGateLadder: step.run() без try/catch пробрасывал
  исключение упавшего шага пола вместо «пол не пройден» → решение неопределённо
  (в обёртке catch→block:false = fail-open). Fix: бросок шага = passed:false
  (fail-closed → блок), последующие не запускаются. Чистый модуль теперь сам
  гарантирует безопасную сторону, не полагаясь на обёртку.

Регрессия tools-only 2560 passed + 2 skip (+5 TDD-тестов, 0 регрессий).

Осознанно НЕ менялось (без призраков):
- M1 verifyChain без 3-го арг = нарушение контракта вызова, не валидный вход.
- M2 node-в-цепочке = то же разрешение, что одиночный node (контракт, тест L53);
  readonly-git-в-цепочке блок = осознанный default-deny (fail-safe).
- M3 defer уже защищён G-фиксом (if e.status!=='pending' return e — ДО defer);
  N3 stale-комментарий (код строже докстринга).
- M4-C DESTRUCTIVE_RE иллюстративен (divergence всё равно судится; разрушительный
  bash режется полом M2/M5 до судьи); M4-D slop-counter↔logVerdict — live-wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:11:12 +03:00
Дмитрий 69e20099db fix(router-mentor): sharp-edges audit M1-M4 — close 8 misuse-resistance holes
Второй аудит машин 1-4 другим объективом (sharp-edges: устойчивость к
неправильному применению / мягкие умолчания / совпадение по пустоте-подстроке).
Криптоядра здоровы (подтверждено). 8 реальных дыр закрыты по TDD:

M3:
- coverage-machine F-1: покрытие считалось по двусторонней ПОДСТРОКЕ — produces
  "a" покрывал запрос "audit-rls-policy" (ложное «всё покрыто»). Новый tokensCover:
  точное равенство ИЛИ подмножество слов по границам. coveringSkill + coverageRegistry.
- router-engine F-8: confidence не проверялся на диапазон — 5/Infinity проходили как
  «уверен» (обход воздержания 5.2), -3 как принуд. abstain. validateTrace: [0,1] finite.
- round-control C: пустой roundKey="" активировал managed-режим (!= null) → все сессии
  делили один счётчик-бакет. Теперь managed требует непустую строку.
- router-learning-queue G: повторное approve уже-решённого id повторно клало запись в
  фонд (дубль). applyApprovalBatch: переводит только status==='pending'.

M2:
- plan-lock F5: шаг с пустым object был джокером (object:'' матчил действие, чей путь
  не извлёкся → object''). actionMatchesStep: пустой object шага не матчит ничего.

M4 (инертна; чистые fail-closed правки кода, корректны и при включении):
- judge-slop-counter H: битый/null вердикт в списке ронял счёт (v.missing на null).
  Теперь не крашит, считается халтурой (безопасная сторона).
- judge-engine J: consensusDecision на пустом/битом списке дрейфовал к GO. Теперь GO
  только если есть голоса И каждый чистый GO; иначе NO-GO (fail-closed для hard-risk).
- judge-orchestrator K: finalGate снимал вето пола на любой falsy floorBlocked
  (undefined от упавшей проверки = fail-open). Теперь снять может только явный false.

Регрессия tools-only 2555 passed + 2 skip (+15 TDD-тестов, 0 регрессий).

Осознанно НЕ менялось (без призраков):
- M1 receipt-sign domain default '' / разделитель пробел — backward-compat контракт
  (тест 18-19), инъективен на enum-доменах без пробелов.
- M1 action-journal атомарность записи головы + битая .jsonl строка — fail-closed
  (битьё → verifyChain ok:false → стена блокирует); чистого behavioral-теста нет.
- M3 round-control requiredSkills=[] — контракт вызывающего (пустой = не требуется).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 06:24:21 +03:00
Дмитрий e1a6f26c06 fix(router-mentor): close sessionId path-traversal class across M1-M4
Аудит M1-M4 (audit-context-building) нашёл непоследовательность guard формы
sessionId: N3-фикс защитил только action-journal.paths() (M1), а 4 sibling-
строителя пути из event.session_id (недоверенный источник) остались без проверки.

Единый экспорт assertSafeSessionId (action-journal.mjs, переиспользует SESSION_ID_RE
N3) применён во всех точках машин:
- M1 action-journal.paths() — рефактор на общий guard (поведение N3 сохранено)
- M4 judge-subrun-journal.paths() — guard добавлен (канал прилежности судьи F1)
- M2 plan-lock.planPath + artifactPath — guard добавлен
- M2 enforce-supreme-gate — экспортируемый guarded stepStatePath, применён в main()

TDD RED-GREEN на каждом файле. Регрессия tools-only 2540 passed + 2 skip (+10).
Серьёзность класса — низкая / защита-в-глубину (sessionId harness-controlled),
закрыт ради консистентности (был 1 из 5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 05:41:07 +03:00
Дмитрий 2b8ad760be fix(m1-foundation): verifyChain fail-closed на битой записи + sessionId path-guard
Аудит Машины 1 (audit-context-building), 2 находки low/defense-in-depth:
N2 — verifyChain больше не бросает TypeError на структурно-битой записи
(null / не-объект / массив, приходит из порченого .jsonl через loadJournal):
guard !e||typeof!=='object'||Array → {ok:false, brokenAt:null}.
N3 — paths() валидирует sessionId (/^[A-Za-z0-9_-]+/) до склейки пути
журнала → throw на ../ / \ . : закрывает path-traversal (fail-closed,
supreme-gate ловит внешним try/catch → block).

N1 (keytar getPasswordSync inert) и N4 (verifyChain/анти-откат — контракт M4)
не трогались: N1 — верное зеркало judge-key-config/llm-judge-config (env —
рабочий путь, fail-closed цел); N4 — deferred межмашинный дизайн.

TDD RED→GREEN, +7 тестов. Регрессия tools-only 2530 passed + 2 skip, 0 регрессий.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 05:03:08 +03:00
Дмитрий 1881501b81 fix(m2): close supreme-wall holes F-A/F-B/F-C (audit 2026-06-07)
F-A (HIGH): Bash green-pass via reading-chain reason collapse — chain
  <reader> && <whitelisted-mutator> (composer pint / php artisan migrate:fresh
  / pest / npm test / node <script>) bypassed the wall. isObserveOnly now
  re-tokenizes and requires EVERY segment be a true reader (READING_CMDS) or
  a single readonly-git, not trusting the collapsed 'reading' reason.
F-B (minor): observe-only no longer choked when plan present but artifact
  missing/invalid (decideMode honors isObserveOnly; finding-9 invariant).
F-C (low): closed-door (C-5) ref-check moved out of the artifact_id guard —
  a step with ref must resolve in a sealed artifact even if plan has no
  artifact_id. TDD: RED proven per fix; full tools regression 2523 GREEN.
2026-06-07 04:33:17 +03:00
Дмитрий 0220ca5802 feat(m2): export READING_CMDS as single source of true readers (F-A prep) 2026-06-07 04:32:56 +03:00
Дмитрий 119ff1f230 fix(m3): learning-queue — reject > approve при конфликте id (hard-rule без явного да НИКАК)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 04:00:06 +03:00
Дмитрий c47155ec91 fix(m3): skill-contract — neutrality сканирует все строковые поля inherent+skill (аудит F4)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 03:56:16 +03:00
Дмитрий b39431d661 fix(m3): coverage-machine — пустой токен не покрывает запрос (аудит F3)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 03:56:10 +03:00
Дмитрий fd61515d20 fix(m3): round-control — managed-терминатор по roundKey, авто-инкремент круга (аудит F2)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 03:56:03 +03:00
Дмитрий 02ff19d08b fix(m3): router-engine — chosen обязан быть среди candidates (аудит F1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 03:55:52 +03:00
Дмитрий b6e59353c1 fix(m4): audit closures — goal anchor + card guard + gate-1 coverage gate + sealed criteria + A2 router/packaging + reversibility doubt-blocks 2026-06-07 03:15:56 +03:00
Дмитрий d925c61651 docs(m4): Machine 4 judge — implementation record (4-A..4-G) 2026-06-06 04:36:38 +03:00
Дмитрий 079adfd184 feat(m4): judge — discipline floor + seal channel + gate floor + engine + orchestration + postfactum evaluator + inert hook wrapper 2026-06-06 03:07:38 +03:00
Дмитрий afb01219cb docs(router-mentor): Машина 4 (судья) — полный дизайн + само-аудит F1-F10 + проверка 26 хуков
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:53:22 +03:00
Дмитрий 06a37cb486 fix(m3): закрытие находок аудита — гард R4 + терминатор + footguns; F2/F3 как честный остаток 2026-06-05 17:32:45 +03:00
Дмитрий a9208a9393 feat(m3): граф зависимостей решений (#2) + дисциплина доменного навыка (#3) 2026-06-05 17:10:21 +03:00
Дмитрий c2d6e6e130 feat(m3): разговорная фаза 2026-06-05 — вход роутера + контроль + выходная верность + честные контракты 2026-06-05 14:52:45 +03:00
Дмитрий 0ee7874c88 docs(router-mentor): conversational-phase router design (M3) + K7 pre-mortem contract (M4) + P16-e revision
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:39:24 +03:00
Дмитрий 923e8ff825 docs: реестр — R-19/R-28/R-31 закрыты, исправлено 13, кодовых задач в открытых нет 2026-06-05 06:25:05 +03:00
Дмитрий 6dbe4374de fix(m2): указатель шага подписан (HMAC step-ptr) — подмена ptr → сброс (R-19) 2026-06-05 06:23:35 +03:00
Дмитрий fe5cf99fc9 fix(brain): доменное разделение подписи — план/артефакт/журнал/расписка не взаимозаменяемы (R-31/J6) 2026-06-05 06:21:19 +03:00
Дмитрий 2ef5504710 fix(m2): session_id из stdin-события, не из env — сессии больше не слипаются (R-28/J3) 2026-06-05 06:09:45 +03:00
Дмитрий 7947569f38 docs: реестр — R-27 закрыт (указатель ↔ план), исправлено 10, открытых багов кода нет 2026-06-05 04:22:11 +03:00
Дмитрий 522531bbd2 fix(m2): указатель шага привязан к plan_id — перепечать сбрасывает указатель (R-27/J2) 2026-06-05 04:21:05 +03:00
Дмитрий 3addf4353b docs: единый реестр замечаний по реинжинирингу мозга v2 (R-01..R-31) 2026-06-05 04:18:07 +03:00
Дмитрий 4963c1187f fix(m2): decide самодостаточно проверяет печать артефакта (защита-в-глубину) (F2) 2026-06-05 04:05:22 +03:00
Дмитрий 133b29df58 fix(m2): actionOf — поля объекта выровнены с B4 (ловит MCP filename/uri/destination) (F1) 2026-06-05 04:04:09 +03:00
Дмитрий 200b5b2da7 docs(m2): аудит Машины 2 — куча (T1-T9 хвосты + F1-F3 замечания + фикс-сет) 2026-06-05 04:03:03 +03:00
Дмитрий e991027793 docs(m4): K6 анти-откат high-water-mark — несрываемый контракт судьи (B3 переадресован M2→M4) 2026-06-05 03:59:19 +03:00
Дмитрий 2907e3f25f fix(m1): extractPath — расширены path-поля (ловит MCP filename/uri/destination) (B4) 2026-06-05 03:42:40 +03:00
Дмитрий 7b578cd391 fix(m1): seq+ts входят в chain_hash журнала — подмена метаданных ломает цепь (B2) 2026-06-05 03:41:38 +03:00
Дмитрий 35a569d370 docs(m1): аудит Машины 1 — хвосты (A1-A4) + замечания (B1-B4) + фикс-сет 2026-06-05 03:40:13 +03:00
Дмитрий 1d676a5616 docs(m3): итоговая сводка отложенного по Машине 3 (9 пунктов, ждут Машину 4/шаг владельца) 2026-06-05 03:29:44 +03:00
Дмитрий b94f7d244c feat(m3-d): контракты + look-ahead в промпт роутера + проброс runRouter (фикс-2) 2026-06-05 03:24:49 +03:00
Дмитрий 92ba55bc0f feat(m3-d): нюх 5.3 + интервьюер 4.4 в промпт роутера (фикс-3) 2026-06-05 03:23:11 +03:00
Дмитрий 58f3a65800 feat(m3-a): checkContractNeutrality — G1 страж нейтральности этикетки (фикс-5, опц.) 2026-06-05 03:22:07 +03:00
Дмитрий eb3f4c4ed1 feat(m3-a): dispatchContract — G3 детерминированная диспетчеризация точно|мягко (фикс-4) 2026-06-05 03:20:55 +03:00
Дмитрий 003bd3d86b fix(m3-b): resolveNode заземляет skill-ref по префиксу (superpowers:X -> #19) — фикс-1 2026-06-05 03:18:26 +03:00
Дмитрий 14230814b0 docs(m3): аудит-сверка 2026-06-05 + фикс-сет 1-5 (G1-G6 расклад, заземление, look-ahead, нюх/интервьюер) 2026-06-05 03:17:20 +03:00
Дмитрий 0f198b6e33 docs(m3): build summary + follow-up list in questions log (Машина 3 complete) 2026-06-04 19:52:30 +03:00
Дмитрий a27a848d7c test(m3-e): learning hard-rule invariants + plan — Машина 3 собрана
Машина 3-E «Очередь одобрений + ручка разведки» собрана (TDD): router-learning-queue.mjs
(propose-only + owner batch approval + render/signal/persist; hard-rule «без да — никак»)
+ router-exploration.mjs (ручка %разведки=0 default, проба=вопрос владельцу, риск-гард).
19 новых тестов. Финальная регрессия tools-only 2212 GREEN.

МАШИНА 3 (Роутер-наставник) собрана целиком: 3-A контракты / 3-B граф узлов /
3-C машина охвата / 3-D движок роутера / 3-E очередь обучения. Доставка в живую
инфру (STATUS/brain-retro), K4-поправка, live-wiring, перенос волн — follow-up
после Машины 4 (журнал вопросов).
2026-06-04 19:51:45 +03:00
Дмитрий dcf772bac5 feat(m3-e): exploration knob (#3) — probe = owner question, off by default + risk-guard 2026-06-04 19:50:31 +03:00
Дмитрий 4cb17fc4d5 feat(m3-e): learning queue — propose-only + owner batch approval + render/signal/persist (hard-rule no auto-fill) 2026-06-04 19:49:32 +03:00
Дмитрий ed89028b1d test(m3-d): router-engine invariants on real graph + plan + questions log
Машина 3-D «Движок роутера» собрана (TDD): router-engine.mjs (detectHighRisk 6.1
детерминированный / validateLevelSkip 6.2 / cheaperOf / validateTrace 5.1 /
groundTrace ОВ-Д2 / buildRouterPrompt+parse+runRouter, llmCall мокается как
router-classifier) + step-pointer.mjs (дерево-указатель волн D6/OQ1, стендово).
35 новых тестов, регрессия tools-only 2193 GREEN. K4-поправка к стене + live-wiring
+ перенос волн в живой main — ОТЛОЖЕНО до Машины 4 (журнал вопросов).
2026-06-04 19:46:09 +03:00
Дмитрий 28b129ed9c feat(m3-d): step-pointer tree (waves D6/OQ1) — standalone, not yet wired into M2 2026-06-04 19:45:04 +03:00
Дмитрий 3a80bdde5c feat(m3-d): router-engine — risk(6.1)/skip(6.2)/price + trace 5.1 + grounding(ОВ-Д2) + buildRouterPrompt/parse/runRouter (llmCall injected, mocked) 2026-06-04 19:43:57 +03:00
Дмитрий 80ebec9e82 test(m3-c): coverage-machine invariants on 3-A contracts + plan
Машина 3-C «Машина охвата A/B/C/D» собрана (TDD): coverage-machine.mjs —
A граф зависимостей (buildDependencyGraph/topoOrder/findHoles/decompositionGroups),
B реестр нужды↔решения (coverageRegistry: дыры+сироты), C requestsChecklist,
D ограничения как нужды (effectiveNeeds), хребет readinessChecklist (4 галочки + §).
Независимый верификатор охвата (рычаг E §6.3). 19 новых тестов, регрессия 2158 GREEN.
2026-06-04 19:26:21 +03:00
Дмитрий 8df8d05612 feat(m3-c): coverage-machine A/B/C/D + readinessChecklist (C-14, set/graph ops) 2026-06-04 19:23:02 +03:00
Дмитрий 699da97dc2 test(m3-b): node-graph invariants on real registry + plan
Машина 3-B «Граф узлов из реестра» собрана (TDD): node-graph.mjs поверх
loadRegistry — buildNodeGraph/resolveNode (ОВ-Д2 заземление) + twinsOf
(subcategory) / hintLinksOf (chains) / conflictsOf (явные) + checkGraphFreshness
(3.6). 20 новых тестов, регрессия tools-only 2139 GREEN.
2026-06-04 19:19:00 +03:00
Дмитрий 750f406cbd feat(m3-b): node-graph from registry — buildNodeGraph/resolveNode (ОВ-Д2) + twins/hints/conflicts + freshness 3.6 2026-06-04 19:17:50 +03:00
Дмитрий 53db0ee2b3 test(m3-a): contract fixtures (own+external) + 3-A invariants + plan + questions log
Машина 3-A «Контракты скилов» собрана (TDD): skill-contract.mjs (схема C-13/L
+ form validator + normalize/accessors + G4 drift-guard) + skill-contract-registry.mjs
(buildRegistry/loadRegistry). 28 новых тестов, регрессия tools-only 2119 GREEN.
Образцы own (writing-plans) + external (operations:process-doc). Журнал вопросов заведён.
2026-06-04 19:12:47 +03:00
Дмитрий a905abd1b4 feat(m3-a): skill-contract-registry — buildRegistry (validate/dedupe/drift) + loadRegistry (disk) 2026-06-04 19:11:28 +03:00
Дмитрий f82cefaead feat(m3-a): skill-contract schema + form validator + normalize/accessors + G4 drift-guard (C-13/L) 2026-06-04 19:10:24 +03:00
Дмитрий 77d2b9be16 docs(router-mentor): sync 26-point coverage map with M2 build + M3 canon
Сверка 2026-06-04: все 26 назначений «пункт → машина» актуальны и
непротиворечивы (собранное в M2 подтверждено по коду). Внесены 6 пометок
дельты, назначения по машинам не менялись:
- п.15: default-deny уточнён зелёным проходом (finding 9) + узкое Write-
  исключение K4 (Вариант А, реализуется в 3-D)
- п.23: D29 как отдельный сверщик растворён → роль у артефакта + закрытой
  двери (C-7); якорь «сырая просьба» сохранён в P16-e (M3)
- п.24: добавлен контракт K5 (судья судит план как будущее, «проверено» за
  факт не берёт; реальное проверено = рантайм-сентинел M5)
- п.26: routing-tag ещё живой, редизайн escape отложен в M6
- мастер-карта: K5 добавлен в аварийный блок Машины 4 (рядом с K1/K2)
- чертёж M2: условие В синхронизировано с каноном K4 (читаемый .md через
  узкое исключение; печать seal — только каналом одобрения)
2026-06-04 18:59:52 +03:00
Дмитрий 8e342be430 docs(router-mentor-m3): M3 shape + K4 resolution (Variant A) + router discipline (§6) + K5 judge-rule-2 contract
Brainstorm 2026-06-04: K4 artifact-write narrow exemption; 5 sub-plans (3-A..3-E); router-discipline levers A-G; 3-layer=triage+delegate-to-real-nodes; skill-imitation closure (234/236); criterion-not-verified judge rule (K5, loud block for Machine 4). All 6 review findings closed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 18:43:43 +03:00
Дмитрий ec4733f77a test(m2): supreme-wall invariants — default-deny/seal/seed/step-match (Task 9)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:49:29 +03:00
Дмитрий cfbfd9c6b4 feat(m2): door-coverage — auditDoors (forgotten-channel) + auditExempt (green-pass safety) (Tasks 7,14)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:49:10 +03:00
Дмитрий 8d9ca65cf3 feat(m2): supreme-gate — seeds/decide/runGate/decideMode/observe-only/closed-door (Tasks 4-6,10,13,12)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:48:49 +03:00
Дмитрий 599dca15ec feat(m2): plan-lock — freeze/verify/match/persist/reconcile/2nd-seal/closed-door (Tasks 1-3,8,11,12)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:48:30 +03:00
Дмитрий 8a790e6f14 docs(router-mentor-m2): actualize blueprint with C/L + fresh-eyes revision + cross-machine contracts
- Машина 2 чертёж: вшита C/L-надстройка (два плана, две печати, закрытая дверь, контракт скила), решения A-K
- Fresh-eyes ревизия 2026-06-04: findings 1-5 (закрытая дверь починена: ref+artifact_id+версия+persist+who-seals), 6-7 (D29 поглощена слоем, D33 → два режима), 8 (растворён призмой нет-болтовни), 9 (зелёный проход = нет долговременного/исходящего эффекта + условия А/Б/В), мелочи 10-12
- Аварийный блок межмашинных контрактов K1-K4 (М4/М5/М3) — без них зелёный проход и снятие D29 = дыры
- Мастер-карта: НАДСТРОЙКА C/L, Машина 1 собрана, красные маркеры K1-K4 в разделах М3/М4/М5
- Дизайн-спека: D33 ревизия (нет болтовни → два режима) + баннер секции

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:37:26 +03:00
Дмитрий de530a130d docs(router-mentor): M2 review 2026-06-04 — C-1..C-14, L-series, M1 confirmed no-rework
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 12:27:12 +03:00
Дмитрий 1cc5431b23 docs(router-mentor): handoff для новой сессии — решения Машины 2 A/B/заморозка + остаток C-G 2026-06-04 06:02:32 +03:00
Дмитрий b83cea2e73 test(m1): foundation cross-invariants (journal tamper / unsigned receipt / runtime deny) 2026-06-04 04:05:10 +03:00
Дмитрий 7af68d62c8 feat(m1): runtime-write-deny — block any path-bearing tool (P10-a all channels) 2026-06-04 04:04:25 +03:00
Дмитрий 56da7faba9 feat(m1): pathNormalize NFC normalization (P10-b unicode evasion) 2026-06-04 04:01:17 +03:00
Дмитрий 3af57e180a feat(m1): signed askuser approval records (P10-c HMAC receipts) 2026-06-04 03:59:38 +03:00
Дмитрий 325c1f4984 plan: детальный TDD-чертёж Машины 2 (Замок плана + Верховный хук)
Стоит на фундаменте Машины 1. 9 задач: freeze/seal плана (HMAC), детерминированный
матч действие-шаг (op+object, без LLM), персист, семена D12/D13, default-deny decide,
runGate+fail-CLOSED, авто-аудит дверей P15-b, сверка план-след P25-d, инварианты.
Проектные решения A-G помечены явно для ревью владельцем.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 19:43:56 +03:00
Дмитрий e3da14a7fc feat(m1): action-journal — append-only hash chain + HMAC head anchor + JSONL persist 2026-06-03 19:18:08 +03:00
Дмитрий 9bd45ce510 feat(m1): receipt-sign — canonicalJson + HMAC signPayload/verifyReceipt (fail-closed) 2026-06-03 19:16:28 +03:00
Дмитрий d7dc03271a feat(m1): receipt-key-config — HMAC key resolution (keychain -> env -> null) 2026-06-03 19:15:25 +03:00
Дмитрий 9309b3590e plan: детальный TDD-чертёж Машины 1 (Фундамент) — журнал S1, подпись расписок, защита-пол
10 задач TDD: receipt-key-config (keychain) + receipt-sign (HMAC) + action-journal
(хеш-цепочка + подпись головы) + усиление runtime-write-deny/path-norm/read-path-deny
+ подписанные askuser-расписки + сквозной self-verify. Заземлён в реальный код.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 19:10:16 +03:00
Дмитрий d612ef2e90 plan: проход правок мастер-карты (счёт несущих, покрытие D/OQ, зависимости, теневой режим) 2026-06-03 18:39:05 +03:00
Дмитрий 9a090bfdac plan: мастер-карта стройки роутер-наставник (7 машин, порядок, покрытие)
Master coordination roadmap из готового дизайна (судья A-I + роутер-наставник +
пред-спека хуков). 7 машин с порядком сборки, инвариантами, точками стыковки и
коверидж-таблицей (26 пунктов хуков + 12 to-build + 7 несущих усилителей).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:43:28 +03:00
Дмитрий 6a8c8494a6 docs(router-mentor): 3.4/3.5 закрыты — обучение роутера только по одобрению владельца 2026-06-03 17:10:20 +03:00
Дмитрий 940070685a docs(router-mentor): аудит хуков, 26-пунктная карта, два прохода усилений, чистовой список 2026-06-03 17:02:39 +03:00
Дмитрий 3b2ffecab4 docs(router-mentor): судья пройден полностью A-I + полный граф узлов + риск-фильтр роутера
Дизайн «роутер-наставник» (brainstorm-стадия, не канон):
- Полный граф+каталог узлов 100% роутеру и судье (отменено код-сужение; кэш, обновление на добавление узла в 4 местах)
- Риск-фильтр у роутера (бывш. W1+W2): тройка где-сломается/больно/откатимо, чинит сам, без блока
- Судья B (вход) / C (граница по обратимости) / D (Sonnet на воротах + код-сверка на исполнении)
- Качество плана и скилов = мерило + совет; дисциплина судьи; H (реакция владельца)
- Дыры I-1..I-4 + 3 призрака разобраны (I-2 закрыт, остальное аут/остаток)

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
2026-06-02 12:56:11 +03:00
Дмитрий 97a0490be1 docs(router-discipline): critical review - 7 holes + pre-spec discipline question
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
2026-06-01 19:20:11 +03:00
Дмитрий 9c82cb0218 docs(router-discipline): D29-D36 + router equipment catalog (6 groups)
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
2026-06-01 16:28:09 +03:00
Дмитрий 9689a6e5b8 feat(router): max_tokens 1500->15000 + task_type rasinhron fix + design notes (router-mentor)
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
2026-06-01 11:50:17 +03:00
Дмитрий c55e14b626 feat(brain): surface router-gate v4 signals into episode + factor axes
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
2026-05-31 19:05:20 +03:00
Дмитрий 2f59541d4b docs(observer): add brain data catalog
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
2026-05-31 18:18:47 +03:00
360 changed files with 35559 additions and 440 deletions
+168
View File
@@ -0,0 +1,168 @@
# Каталог данных «мозга» Лидерры
Полный перечень всего, что новый «мозг» (наблюдатель + защиты router-gate v4) **способен фиксировать**: журнал эпизодов, числовые параметры/счётчики и все оси для факторного анализа.
**Статус проверки:** разделы A–E сверены по исходному коду `tools/` (31.05.2026). Раздел F сверен по коду хуков. Все цитаты — `file:line` от корня репозитория.
---
## A. Эпизод журнала — `docs/observer/episodes-YYYY-MM.jsonl` (схема v4.4, schema_minor 4)
Один эпизод = один цикл «промпт заказчика → ответ Claude». Append-only, по строке на эпизод. ПДн вырезаются до записи (`observer-pii-filter.mjs`). Сборка — `observer-transcript-parser.mjs:888` (`parseTranscript`).
### A.1. Идентификация и время
| Поле | Тип / значения | Смысл |
|---|---|---|
| `schema_version` | `4` | версия схемы |
| `schema_minor` | `3` | подверсия |
| `task_id` / `task_ref` | string (sessionId) | привязка к сессии |
| `timestamps.started_at` / `ended_at` | ISO | начало/конец хода |
### A.2. Кто и что выбрал
| Поле | Значения | Смысл |
|---|---|---|
| `path_type` | `regulated` / `improvised` | был ли вызван навык superpowers |
| `decision_provenance.kind` | `autonomous` / `user_directed_method` / `user_chose_from_options` | кто выбрал маршрут — Claude сам / навязан заказчиком / заказчик выбрал из предложенного |
| `decision_provenance.claude_would_have_chosen` | string / null | контрфактуал — что выбрал бы Claude сам |
### A.3. Исход
| Поле | Значения | Смысл |
|---|---|---|
| `outcome` | при записи `unknown` | исход (выводится позже, см. C) |
| `outcome_reviewed` | null → метка | исход по ревью /brain-retro |
| `outcome_reviewed_source` | null / source | кто проставил ревью |
### A.4. Сигнал заказчика
| Поле | Значения | Смысл |
|---|---|---|
| `prompt_signal` | `correction` / `approval` / `new_task` / `neutral` | тон следующего/текущего промпта |
| `prompt_embedding_base64` | null → вектор | смысловой вектор первого промпта (дозаполняется асинхронно) |
### A.5. Обстановка (`environment`)
| Поле | Значения | Смысл |
|---|---|---|
| `economy_level` | 0 / 5 / 100 / null | режим экономии |
| `model` | имя модели | модель контроллера |
| `post_compaction` | bool | был ли сжат контекст до хода |
| `session_turn` | int | номер хода после последнего сжатия |
| `parallel_session` | bool | признак второй активной сессии |
| `classifier_model` | имя / null | модель LLM-классификатора |
### A.6. Размер (`task_size`)
`tool_calls` (всего вызовов инструментов) · `files_touched` (уникальных файлов) · `files[]` (список путей). — `observer-transcript-parser.mjs:423`.
### A.7. Стоимость и токены (`task_cost`)
`observer-transcript-parser.mjs:472`. Базовые: `input_tokens` · `output_tokens` · `cache_read_input_tokens` · `cache_creation_input_tokens` · `web_search_requests` · `web_fetch_requests` · `iterations` (детектор extended-thinking).
Слой LLM-агентов (дозаполняется): `classifier_input_tokens` · `classifier_output_tokens` · `self_assessment_input_tokens` · `self_assessment_output_tokens` · `reviewer_input_tokens` · `reviewer_output_tokens` · `reviewer_subagent_usd` · `reviewer_direct_fallback_usd` · `judge_spend_usd` (✅ NEW — `judge_calls × JUDGE_PER_CALL_USD`).
### A.8. Мета (`task_meta`)
`prompt_length_chars` · `mcp_servers_used[]` · `file_type_distribution` по корзинам `src / test / config / spec / norm / data / other` (`classifyFilePath``observer-transcript-parser.mjs:358`).
### A.9. Классификатор (`classifier_output`) + `degraded_mode`
`observer-state-enricher.mjs:52`. `task_type` · `recommended_node` · `recommended_chain` · `recommended_chain_id` · `no_skill_found` · `source` (llm/regex/prefilter/cache) · `reasoning` (≤600 симв.) · `confidence` · `latency_ms` · `retry_count_internal` · `llm_error` · `alternatives_considered[]` (топ-3). Отдельно `degraded_mode` (bool, LLM→regex fallback).
### A.10. Рассуждение маршрута (`primary_rationale`)
`step` · `node_chosen` · `chain_ref` · `triggers_matched[]` · `candidates_considered[]` · `boundaries_applied[]` · `hard_floor{invoked, rules[]}` · `task_classification` · `recommended_node` · `recommended_chain` · `chain_progress` · `chain_completed`.
**`task_classification` — 12 значений** (`observer-transcript-parser.mjs:208`): `memory-sync`, `regulatory-bump`, `planning`, `release`, `refactor`, `bugfix`, `feature`, `docs`, `analysis`, `cleanup`, `monitoring`, `question`, `other`.
### A.11. События (`events[]`) — 11 видов
`skill_invoked` (skill) · `tool_summary` (counts) · `error` (tool, summary) · `hook_fired` (counts, scripts, errors) · `interrupt` · `retry` · `time_burn` (ход > 15 мин) · `parse_gap` (> 10% битых строк) · `unrecovered_error` (ход кончился на ошибке) · `ask_user_question` (question_count, answer_kind: `option`/`custom`/`no_answer`) · `subagent_invoked` (subagent_type, model, description).
### A.12. Сигналы защит router-gate v4 (`v4_signals`) — ✅ NEW (schema_minor 4)
Поднимаются в эпизод ридером `observer-v4-signals.mjs` по окну хода `[started_at, ended_at]`: `rationalization_flag_count` (число пойманных самооправданий в окне) · `judge_verdict` (`YES`/`NO`/`block`/`null` — последний вердикт судьи в окне) · `safe_baseline_action` (`allow`/`soft_flag`/`hard_block`/`null` — худшее действие safe-baseline в окне) · `judge_calls` (кумулятивно за сессию из бюджета судьи).
---
## B. Факторные оси — `FACTOR_FNS` (`brain-retro-analyzer.mjs:326`) — 27 осей + `chain_ref`
Каждая ось раскладывает эпизоды по корзинам и строит матрицу «значение фактора × распределение исходов» (`buildFactorMatrix`, `brain-retro-analyzer.mjs:384`).
**Pass 0 (из ядра эпизода):** `decision_provenance` · `economy_level` · `model` · `post_compaction` · `session_segment_turn` · `parallel_session` · `task_size` · `node_chosen` · `task_classification` · `recommended_node_for_direct`.
**Pass 1 (дешёвые оси из v4):** `prompt_signal` · `classifier_source` · `degraded_mode` · `path_type` · `retry_count` · `error_count` · `hard_floor_invoked` · `iterations_bucket`.
**Pass 2 (метрики классификатора):** `latency_bucket` (fast<500 / medium<2000 / slow<10000 / very_slow) · `error_type`.
**Pass 3 (динамика):** `prompt_length_bucket` (short<100 / medium<1000 / long<2500 / huge) · `time_of_day_bucket` (night/morning/afternoon/evening, UTC) · `day_of_week` · `inter_prompt_gap_bucket` (<1m / 1-10m / 10-60m / 60m+) · `mcp_server_used` (any/none) · `file_type_main` · `skill_invocations_bucket` (0/1/2+) · `subagent_spawns_bucket` (0/1/2+).
**Pass 4 (семантика):** `similar_past_outcome_majority` (исход большинства соседей по смысловому вектору; `no_neighbors` если вектора нет).
**Pass 5 (✅ NEW — сигналы router-gate v4):** `rationalization_flag_count` (0/1/2+) · `judge_verdict` (YES/NO/block/null) · `safe_baseline_action` (allow/soft_flag/hard_block/null) · `judge_calls_bucket` (0 / 1-9 / 10+).
**Отдельно:** `chain_ref` — мульти-значный (эпизод считается по разу на каждую цепочку).
---
## C. Вывод исхода — `inferOutcome` (`brain-retro-analyzer.mjs:95`) — 5 меток
- `blocked` — ход кончился на неисправленной ошибке (`unrecovered_error`).
- `rework` — следующий эпизод имеет `prompt_signal = correction`.
- `success` — следующий = `approval` или `new_task`.
- `soft_success` — следующий = `neutral` (тихий успех).
- `unknown` — следующего эпизода ещё нет.
---
## D. Аналитические срезы (Cuts) `/brain-retro` (`brain-retro-analyzer.mjs`)
| Срез | Функция | Что агрегирует |
|---|---|---|
| Факторная матрица | `buildFactorMatrix` | 27 осей × распределение исходов |
| Cut 8 — Class × canon coverage | `buildClassCanonCoverage:448` | по классу задачи: count, канон-узлы, как часто роутер рекомендовал, что взял Claude, попало ли в канон, rework |
| Cut 9 — Router vs Opus | `buildRouterVsOpus:498` | расхождение роутера и Opus-ревьюера (3 секции) |
| Cut 10 — Chain-ignore breakdown | `buildChainIgnoreBreakdown:559` | % игнора цепочек + rework-rate по длине (1 / 2 / 3+) |
| Cut 11 — Chain-hook effectiveness | `analyzeChainHookEffectiveness:36` + `buildChainHookEffectiveness:59` | исходы срабатывания chain-хука по ledger |
| Router-gate hook effectiveness | `buildRouterGateHookEffectiveness:617` | эффективность router-gate |
| Self-fabrication signals | `buildSelfFabricationSignals:643` | признаки фейковых результатов |
---
## E. Числовые счётчики и деньги (вне эпизода)
### E.1. `~/.claude/runtime/cost-daily.json` (per-date)
`cost-aggregator.mjs:13`. Поля: `classifier_usd` · `self_assessment_usd` · `reviewer_subagent_usd` · `reviewer_direct_fallback_usd` · `self_retrospect_usd` · `total_usd` · `episode_count`.
### E.2. `PRICING` (`cost-pricing.mjs`)
Sonnet — $3/Mtok вход, $15/Mtok выход. Opus — $15/Mtok вход, $75/Mtok выход.
### E.3. `~/.claude/runtime/hook-outcomes.jsonl`
6 корзин исхода хука (`classifyOutcome`): `blocked` / `passed-with-skill` / `passed-inline-override` / `passed-global-override` / `passed-short-chain` / `passed-no-mutating`. Строка: rule, outcome, sessionId, timestamp.
### E.4. `~/.claude/runtime/override-usage.jsonl`
Лог override-фраз. Порог 5/день на фразу (6-я блокируется `enforce-override-limit`). Байпас — фраза «лимит снят».
### E.5. Контролёр C5 (`observer-coverage-checker.mjs`) — warn-only
`coverage` (хук зарегистрирован, но 0 эпизодов за месяц) · `registration` (Stop-хук + post-commit на месте) · `missed.totalMissed` (промахи активации узлов, `missed-activations.mjs`).
### E.6. Три счётчика в `docs/observer/`
`.pii-counters.json` (сколько ПДн вырезано) · `.read-counter.json` (когда читали файлы наблюдателя; алерт через 54 недели тишины) · `.self-retrospect-counter.json` (`episodes_since_last`, порог 50 → предложить саморазбор).
---
## F. Сигналы новых защит router-gate v4
> **Обновление 31.05.2026:** F.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`).
@@ -0,0 +1,18 @@
# Shadow-replay отчёт
## М5 пол — GREEN
events: 954 | over-block: 0 | real-catch: 5 | allow: 949 | miss: 0
## М6 снимок — GREEN
events: 951 | over-block: 0 | real-catch: 2 | allow: 949 | miss: 0
## М2 стена — GREEN
events: 5 | over-block: 0 | real-catch: 2 | allow: 3 | miss: 0
## М4 судья — GREEN
events: 5 | over-block: 0 | real-catch: 2 | allow: 3 | miss: 0
## М3 роутер — расхождение (реальные эпизоды)
total: 884 | followed: 2 | diverged: 88 | no-rec: 794
> М1 журнал: вне холостого прогона (runtime read-protected); целостность — unit-тест (пин 5782ede3).
@@ -0,0 +1,12 @@
{
"skill": "21st-magic",
"kind": "external",
"needs": ["намерение UI-шаблона (компонент / лейаут / форма)"],
"produces": ["стартовый UI-шаблон (LLM-сгенерированный)"],
"constraints": ["генератор шаблонов через PSR_v1 R14.4 pipeline", "Pa11y проверка обязательна после генерации", "стек-фильтр R6.0"],
"preview-form": "mockup",
"defaults": ["после генерации — обязательный Pa11y"],
"key-decisions": ["какой шаблон; адаптация под Vue+Vuetify"],
"acceptance-criteria": ["шаблон в стеке Vue+Vuetify, Pa11y 0 нарушений"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "adr-kit",
"kind": "external",
"needs": ["архитектурное решение для фиксации/проверки"],
"produces": ["ADR в docs/adr/ + enforcement через adr-judge"],
"constraints": ["ADR + adr-judge lefthook job 9 (без LLM)", "НЕ диаграммы (mermaid)", "НЕ справочник паттернов (architecture-patterns)"],
"preview-form": "outline",
"defaults": ["adr-judge декларативно, без --llm"],
"key-decisions": ["что фиксировать как ADR; статус решения"],
"acceptance-criteria": ["решение зафиксировано ADR, adr-judge проходит"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "architecture-patterns",
"kind": "external",
"needs": ["вопрос выбора архитектурного паттерна"],
"produces": ["описание паттернов + критерии выбора (Clean/Hexagonal/DDD/CQRS/...)"],
"constraints": ["справочник паттернов (knowledge)", "НЕ фиксация решения (adr-kit)"],
"preview-form": "none",
"defaults": ["давать критерии выбора, не догму"],
"key-decisions": ["какой паттерн под контекст"],
"acceptance-criteria": ["паттерн обоснован под контекст задачи"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,11 @@
{
"skill": "billing-audit",
"kind": "own",
"needs": ["код/диф биллинга для аудита денежной корректности"],
"produces": ["отчёт о money-инвариантах биллинга"],
"constraints": ["self-authored; аудит кода биллинга (bcmath/идемпотентность/tier/charge_source)", "ADR-012: НЕ налоги (ru-tax), НЕ процесс (process-*), НЕ security (D3)"],
"preview-form": "outline",
"defaults": ["проверять потерю копеек, двойное списание, tier-резолюцию, дрейф reconcile"],
"key-decisions": ["какие денежные инварианты в scope"],
"acceptance-criteria": ["каждый денежный путь проверен на потерю копеек и двойное списание"]
}
@@ -0,0 +1,12 @@
{
"skill": "boost",
"kind": "external",
"needs": ["SQL/Eloquent-запрос к dev-БД или вопрос по docs Laravel/пакета"],
"produces": ["результат SQL/Eloquent или релевантная документация Laravel"],
"constraints": ["MCP к dev-БД через Roster auto-detect", "НЕ подключать к production DB", "заменил PostgreSQL MCP (#1)"],
"preview-form": "none",
"defaults": ["read-first; serverить только установленные пакеты (Roster)"],
"key-decisions": ["источник: только dev-БД, не прод"],
"acceptance-criteria": ["запрос/доки отданы из правильного (dev) источника"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "brand-voice",
"kind": "external",
"needs": ["текст/коммуникация для проверки голоса бренда"],
"produces": ["вербальные brand guidelines / проверка тональности"],
"constraints": ["вербальный бренд (ADR-015 MKT6) НЕ Brandbook визуальный", "взаимодополняют"],
"preview-form": "outline",
"defaults": ["единый тон коммуникации Лидерры"],
"key-decisions": ["тональность под канал/аудиторию"],
"acceptance-criteria": ["текст соответствует голосу бренда"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "ccpm",
"kind": "external",
"needs": ["идея фичи для управления (PRD→эпик→issue→код)"],
"produces": ["PRD/эпики/issues в .claude/prds/ + .claude/epics/"],
"constraints": ["вендоренный скил, 14 bash-скриптов без хуков", "GitHub-issues через GitHub MCP", "НЕ продуктовые церемонии (product-management)"],
"preview-form": "outline",
"defaults": ["PRD → эпик → issues → код"],
"key-decisions": ["декомпозиция эпика на issues"],
"acceptance-criteria": ["фича прослежена PRD→issue→код"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "claude-code-setup",
"kind": "external",
"needs": ["паттерны использования Claude Code для рекомендаций"],
"produces": ["рекомендации автоматизаций (hooks/permissions/settings)"],
"constraints": ["READ-ONLY рекомендатель, не меняет конфиг"],
"preview-form": "outline",
"defaults": ["предлагает, не применяет"],
"key-decisions": ["какие автоматизации предложить"],
"acceptance-criteria": ["рекомендации релевантны паттернам использования"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "claude-md-management",
"kind": "external",
"needs": ["намерение правки CLAUDE.md (audit / capture learnings)"],
"produces": ["обновлённый CLAUDE.md (через claude-md-improver / revise-claude-md)"],
"constraints": ["единственный разрешённый канал правки CLAUDE.md (§5 п.10)", "синхронизировать Pravila + Tooling (п.7)"],
"preview-form": "none",
"defaults": ["claude-md-improver для структурных, revise для learnings"],
"key-decisions": ["audit-правка vs захват learnings"],
"acceptance-criteria": ["CLAUDE.md обновлён через плагин, нормативка синхронна"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "context7",
"kind": "external",
"needs": ["вопрос по API/доке конкретной библиотеки/SDK"],
"produces": ["актуальная документация библиотеки/SDK"],
"constraints": ["первый выбор для доков известной библиотеки", "WebFetch — fallback на URL; WebSearch — без знания библиотеки"],
"preview-form": "none",
"defaults": ["resolve-library-id → query-docs"],
"key-decisions": ["какая библиотека/тема"],
"acceptance-criteria": ["актуальная дока по API получена"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "cspell",
"kind": "external",
"needs": ["Markdown-текст для проверки орфографии", "пользовательский словарь проекта"],
"produces": ["отчёт о неизвестных словах"],
"constraints": ["ru/en орфография .md с пользовательским словарём", "НЕ стиль (markdownlint)", "НЕ грамматика"],
"preview-form": "none",
"defaults": ["учитывать cspell-words.txt"],
"key-decisions": ["новый валидный термин → в словарь, а не подавлять находку"],
"acceptance-criteria": ["0 неизвестных слов вне словаря"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "data-scientist",
"kind": "external",
"needs": ["датасет + ML-задача (классификация/регрессия/анализ)"],
"produces": ["ML-результат: модель, метрики, визуализация"],
"constraints": ["вендоренный скил; классический ML-воркфлоу", "требует датасета, не доменной БД портала"],
"preview-form": "sample",
"defaults": ["загрузка → feature engineering → обучение → оценка"],
"key-decisions": ["алгоритм и метрики под задачу"],
"acceptance-criteria": ["модель оценена метриками, результат воспроизводим"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "dataforseo-mcp",
"kind": "external",
"needs": ["SEO-вопрос по РФ (SERP/ключевые/бэклинки)"],
"produces": ["SEO-данные РФ: SERP-позиции, бэклинки, конкурентный анализ"],
"constraints": ["DEFERRED — платный, pending Б-1", "комплементарен Wordstat #79"],
"preview-form": "none",
"defaults": ["при платном аккаунте"],
"key-decisions": ["какие SEO-данные нужны"],
"acceptance-criteria": ["SEO-данные РФ получены (при доступе)"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "dependabot",
"kind": "external",
"needs": ["Composer/npm-зависимости"],
"produces": ["auto-PR при обнаружении CVE в зависимости"],
"constraints": ["авто-PR через .github/dependabot.yml", "НЕ блок install (Roave)"],
"preview-form": "none",
"defaults": ["настройка через .github/dependabot.yml"],
"key-decisions": ["принять/отклонить предложенное обновление"],
"acceptance-criteria": ["CVE-зависимости имеют открытый update-PR"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "deptrac",
"kind": "external",
"needs": ["PHP-слои для проверки направления зависимостей"],
"produces": ["отчёт о нарушениях границ слоёв"],
"constraints": ["граф слоёв по app/deptrac.yaml; lefthook job 10", "НЕ типовой анализ (Larastan)", "НЕ декларативный ADR (adr-judge)"],
"preview-form": "none",
"defaults": ["конфиг 13 слоёв, консервативный ruleset"],
"key-decisions": ["допустимые направления зависимостей"],
"acceptance-criteria": ["0 нарушений границ слоёв"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "design-plugin",
"kind": "external",
"needs": ["дизайн-вопрос pre-code (критика / UX-копирайт / research synthesis)"],
"produces": ["дизайн-критика / UX-копирайт / research synthesis"],
"constraints": ["pre-code; a11y-принципы дизайн-уровня", "технический a11y SoT — Pa11y (#9)"],
"preview-form": "none",
"defaults": ["до написания кода"],
"key-decisions": ["критика, копирайт или synthesis"],
"acceptance-criteria": ["дизайн-замечания/копирайт учтены до кода"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,11 @@
{
"skill": "discovery-interview",
"kind": "own",
"needs": ["задача до проектирования: фича (интервью заказчика) или ориентация в системе"],
"produces": ["discovery-brief (FEATURE) или snapshot мета-слоя (SYSTEM)"],
"constraints": ["self-authored; FEATURE=JTBD-интервью человека, SYSTEM=ориентация по мета-слою", "ADR-009 граница с process-analysis (#53): человек vs код"],
"preview-form": "outline",
"defaults": ["FEATURE → discovery-brief в brainstorming; SYSTEM → snapshot в docs/discovery/"],
"key-decisions": ["режим FEATURE или SYSTEM"],
"acceptance-criteria": ["проблема/контекст вскрыты до проектирования"]
}
@@ -0,0 +1,12 @@
{
"skill": "eslint-prettier",
"kind": "external",
"needs": ["JS/Vue-код для линта+форматирования"],
"produces": ["исправленный/проверенный JS/Vue по ESLint+Prettier"],
"constraints": ["связка lint+format JS/Vue", "НЕ CSS (Stylelint)", "НЕ типы (vue-tsc)"],
"preview-form": "none",
"defaults": ["flat-config + plugin-vue; config-prettier разводит конфликты"],
"key-decisions": ["scope: staged-файлы vs весь js/vue"],
"acceptance-criteria": ["0 ESLint-ошибок, формат единообразен"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "figma-mcp",
"kind": "external",
"needs": ["Figma-файл с дизайн-токенами/компонентами"],
"produces": ["извлечённые дизайн-токены/компоненты/стили"],
"constraints": ["DEFERRED — нет Figma-аккаунта; источник — статический handoff Платона", "extract-only, code-gen за Frontend Design (#30)"],
"preview-form": "none",
"defaults": ["на поддерживаемом источнике: extract-only"],
"key-decisions": ["какие токены извлекать"],
"acceptance-criteria": ["токены извлечены из Figma-источника (когда доступен)"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "finance-plugin",
"kind": "external",
"needs": ["финансовая задача (сверка/variance/проводки/отчётность)"],
"produces": ["сверка / variance-анализ / проводки / финотчётность"],
"constraints": ["US-GAAP-ориентирован, частично применим РФ; SOX not-applicable", "РФ-налоги — за ru-tax-accounting (#63); ADR-012 граница C6/C7", "warehouse-MCP DEFERRED"],
"preview-form": "outline",
"defaults": ["reconciliation/variance применимы; US-GAAP-скилы с осторожностью"],
"key-decisions": ["применим ли скил для РФ-контекста"],
"acceptance-criteria": ["финансовая операция корректна, РФ-ограничения учтены"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "frontend-design",
"kind": "external",
"needs": ["UI-задача: компонент / паттерн / состояние / a11y-принцип"],
"produces": ["доменное решение UI/UX (компоненты, паттерны, a11y-принципы)"],
"constraints": ["доменная база UI для Vue+Vuetify; обязательный стек-фильтр R6.0", "paired со #19", "технический a11y — за Pa11y, не здесь"],
"preview-form": "mockup",
"defaults": ["срезать React/Tailwind/shadcn → Vue 3 + Vuetify 3; палитра Forest из Brandbook"],
"key-decisions": ["компонент/паттерн под задачу в рамках стека"],
"acceptance-criteria": ["решение в стеке Vue+Vuetify, бренд Forest соблюдён"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "github-mcp",
"kind": "external",
"needs": ["ссылка на repo/issue/PR", "намерение операции"],
"produces": ["результат чтения или записи issue/PR/комментария"],
"constraints": ["внешний MCP — операции через GitHub API", "НЕ локальный git-флоу (это git/PowerShell)"],
"preview-form": "none",
"defaults": ["read-first перед мутацией"],
"key-decisions": ["scope операции: чтение или запись"],
"acceptance-criteria": ["операция отражена в GitHub и подтверждена ответом API"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "gitleaks",
"kind": "external",
"needs": ["git diff или история репозитория"],
"produces": ["находки утечек секретов (ключи/токены/пароли/DSN)"],
"constraints": ["сканирует секреты в diff/истории через lefthook pre-commit/pre-push", "НЕ SAST-уязвимости кода (Semgrep)"],
"preview-form": "none",
"defaults": ["protect --staged на pre-commit; полная история на pre-push"],
"key-decisions": ["реальный секрет vs тестовая фикстура (false-positive)"],
"acceptance-criteria": ["0 утечек секретов в diff/истории"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "graphifyy",
"kind": "external",
"needs": ["структурный/cross-layer вопрос по проекту (docs+code)"],
"produces": ["ответ из knowledge-graph портала (узлы/рёбра/source_location)"],
"constraints": ["user-level CLI; backend GEMINI/GOOGLE key ИЛИ Claude subagent", "ADR-017: KG1 НЕ context7 #60 (внутренний vs внешний), KG2 НЕ Boost #10 (static vs runtime), KG3 НЕ openapi #47, KG4 НЕ Sentry #34, KG5 НЕ adr-kit/mermaid (auto vs manual)", "артефакты graphify-out*/ gitignored; только manual --update"],
"preview-form": "none",
"defaults": ["query/explain/path read-only; перед открытым codebase-вопросом сначала graphify"],
"key-decisions": ["структурный вопрос vs известный путь (Read/Grep)"],
"acceptance-criteria": ["структурный вопрос отвечен с source_location-цитатами"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "histoire",
"kind": "external",
"needs": ["Vue-компонент для каталога stories"],
"produces": ["визуальный каталог stories/variants"],
"constraints": ["каталог компонентов (не Storybook)", "Vuetify через setupFile"],
"preview-form": "sample",
"defaults": ["npm run story"],
"key-decisions": ["какие компоненты/variants в каталоге"],
"acceptance-criteria": ["stories собираются и рендерятся"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "hookify",
"kind": "external",
"needs": ["явный /hookify + поведение для предотвращения"],
"produces": ["сгенерированный Claude Code хук (PreToolUse/PostToolUse/Stop/...)"],
"constraints": ["только по явному /hookify", "HK1 pre-check коллизий с существующими хуками (ADR-010)"],
"preview-form": "outline",
"defaults": ["HK1 pre-check до генерации"],
"key-decisions": ["тип события хука и условие"],
"acceptance-criteria": ["хук генерируется без коллизий с существующей архитектурой"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "ide-helper",
"kind": "external",
"needs": ["Laravel-проект (facades/модели/хелперы)"],
"produces": ["IDE-заглушки (@mixin IdeHelper*) для autocomplete/type-inference"],
"constraints": ["генерация stubs для IDE", "НЕ влияет на рантайм"],
"preview-form": "none",
"defaults": ["ide-helper:generate + models -W -M -N"],
"key-decisions": ["когда перегенерировать (после изменения моделей)"],
"acceptance-criteria": ["autocomplete/type-inference покрывают facades+модели"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "jupyter-mcp",
"kind": "external",
"needs": ["Jupyter-ноутбук для исполнения"],
"produces": ["результат исполнения ячеек ноутбука"],
"constraints": ["DEFERRED — нет Python ML-окружения на native-Windows"],
"preview-form": "none",
"defaults": ["на поддерживаемой среде с Python ML"],
"key-decisions": ["какие ячейки исполнять"],
"acceptance-criteria": ["ноутбук исполнен, результаты получены (на поддерживаемой среде)"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "larastan",
"kind": "external",
"needs": ["PHP-код для статанализа типов"],
"produces": ["отчёт об ошибках типов/сигнатур/undefined-переменных"],
"constraints": ["типовой анализ PHPStan+Laravel", "НЕ стиль (Pint)", "НЕ граф слоёв (deptrac)"],
"preview-form": "none",
"defaults": ["уровень + baseline проекта"],
"key-decisions": ["новая ошибка vs baseline-долг"],
"acceptance-criteria": ["0 новых ошибок типов сверх baseline"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,11 @@
{
"skill": "laravel-backend-patterns",
"kind": "own",
"needs": ["backend-задача Лидерры (controller/service/job/RLS/деньги)"],
"produces": ["проектные backend-конвенции (слоистость, RLS-aware, bcmath, идемпотентность, partition-aware)"],
"constraints": ["self-authored справочник проектных конвенций", "ADR-013: BT5 НЕ architecture-patterns #38 (проектные vs generic), BT6 НЕ billing-audit #62"],
"preview-form": "outline",
"defaults": ["controller→service→job; RLS-aware; деньги через bcmath/LedgerService"],
"key-decisions": ["паттерн под слой задачи"],
"acceptance-criteria": ["backend-код следует конвенциям Лидерры"]
}
@@ -0,0 +1,12 @@
{
"skill": "lychee",
"kind": "external",
"needs": ["Markdown-файлы со ссылками"],
"produces": ["отчёт о битых URL и якорях"],
"constraints": ["внутренние + внешние ссылки и якоря .md", "НЕ стиль/орфография"],
"preview-form": "none",
"defaults": ["проверять и внутренние якоря, и внешние URL"],
"key-decisions": ["внешний временно недоступный vs реально битый — различать"],
"acceptance-criteria": ["0 битых ссылок/якорей"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "markdownlint",
"kind": "external",
"needs": ["Markdown-файлы для линта"],
"produces": ["отчёт о нарушениях стиля Markdown"],
"constraints": ["внешний CLI — стиль .md (заголовки/таблицы/пробелы/переносы)", "НЕ орфография (cspell)", "НЕ ссылки (lychee)"],
"preview-form": "none",
"defaults": ["авто-fix где возможно (--fix), кроме корневого CLAUDE.md"],
"key-decisions": ["scope: какие .md в проверке"],
"acceptance-criteria": ["0 нарушений стиля по правилам markdownlint"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "marketing-plugin",
"kind": "external",
"needs": ["маркетинговая задача (контент/кампания/SEO/email/бриф)"],
"produces": ["маркетинговый артефакт (контент/email-цепочка/SEO-аудит/бриф/отчёт)"],
"constraints": ["первичный resolver C1 (8 скилов)", "ADR-015: MKT3 решатель, материал — marketingskills #75; MKT2 НЕ product-management #42"],
"preview-form": "outline",
"defaults": ["скил под тип задачи C1"],
"key-decisions": ["какой маркетинг-скил под задачу"],
"acceptance-criteria": ["маркетинговый артефакт готов и таргетирован"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,11 @@
{
"skill": "marketing-ru",
"kind": "own",
"needs": ["маркетинговая задача для РФ-рынка (Директ/ВК/Telegram/лендинг/152-ФЗ)"],
"produces": ["РФ-маркетинг: каналы, конверсия лендинга, 152-ФЗ согласия"],
"constraints": ["self-authored; РФ-специфика (ADR-015 MKT9, 152-ФЗ cross-ref #71)", "НЕ generic marketing #74"],
"preview-form": "outline",
"defaults": ["РФ-каналы: Яндекс.Директ/ВК/Telegram; согласия по 152-ФЗ"],
"key-decisions": ["канал и РФ-ограничения"],
"acceptance-criteria": ["маркетинг учитывает РФ-каналы и 152-ФЗ"]
}
@@ -0,0 +1,12 @@
{
"skill": "marketingskills",
"kind": "external",
"needs": ["маркетинговая задача, требующая фреймворка (AIDA/PAS/CRO/...)"],
"produces": ["маркетинговые фреймворки как материал (40 шт)"],
"constraints": ["материал/резерв-библиотека, не решатель (ADR-015 MKT3)", "решатель — marketing #74"],
"preview-form": "none",
"defaults": ["материал отбирается, решение за marketing #74"],
"key-decisions": ["какой фреймворк под задачу"],
"acceptance-criteria": ["фреймворк отобран как материал для решателя"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "mermaid",
"kind": "external",
"needs": ["требование к диаграмме (C4 / flow / sequence / ...)"],
"produces": ["диаграмма в нотации Mermaid/C4"],
"constraints": ["вендоренный скил; диаграммы в docs/architecture/", "НЕ фиксация решения (adr-kit)"],
"preview-form": "diagram",
"defaults": ["C4: context/container/component"],
"key-decisions": ["тип и уровень диаграммы"],
"acceptance-criteria": ["диаграмма рендерится и отражает систему"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "n8n-mcp",
"kind": "external",
"needs": ["workflow для движка n8n"],
"produces": ["автоматизация процесса через n8n"],
"constraints": ["DEFERRED — n8n не в стеке; движок процессов = очередь Laravel", "принятие n8n — отдельное архитектурное решение"],
"preview-form": "none",
"defaults": ["на среде с n8n"],
"key-decisions": ["принятие n8n как движка — отдельный ADR"],
"acceptance-criteria": ["workflow исполняется в n8n (когда принят)"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "nightowl",
"kind": "external",
"needs": ["сквозная корреляция request/job/query трассировок"],
"produces": ["коррелированный runtime-трейс (self-hosted)"],
"constraints": ["DEFERRED — нет pcntl/posix на native-Windows; pending Б-1/Linux", "ADR-013 BT7 НЕ Sentry #34 (трейс vs ошибки), BT8 НЕ Pail/Boost"],
"preview-form": "none",
"defaults": ["на Linux-среде с pcntl/posix"],
"key-decisions": ["scope корреляции трассировок"],
"acceptance-criteria": ["request/job/query скоррелированы в трейс (на поддерживаемой среде)"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,11 @@
{
"skill": "normative-sync",
"kind": "own",
"needs": ["завершённая интеграция/ADR/brain-артефакт для синка нормативки"],
"produces": ["синхронизированные Pravila/PSR_v1/Tooling/CLAUDE.md (version bumps, §0 cross-refs, footer, §9)"],
"constraints": ["project-агент; звать после закрытия крупной off-phase интеграции/ADR (Pravila §2.4)", "парный с #85; не в Tooling-каноне счётчиков"],
"preview-form": "outline",
"defaults": ["синк 4 нормативных файлов с version bumps"],
"key-decisions": ["какие version bumps/cross-refs нужны"],
"acceptance-criteria": ["4 нормативных документа синхронны, cross-refs сходятся"]
}
@@ -0,0 +1,12 @@
{
"skill": "nuclei",
"kind": "external",
"needs": ["работающий хост для скана по шаблонам"],
"produces": ["находки известных уязвимостей (CVE/экспозиция/слабый TLS/misconfig)"],
"constraints": ["CLI bin/nuclei.exe; цель 127.0.0.1 не localhost", "ADR-014 IS2 НЕ ZAP #68 (широта vs глубина)"],
"preview-form": "none",
"defaults": ["низкий rate-limit для dev"],
"key-decisions": ["набор шаблонов"],
"acceptance-criteria": ["0 известных уязвимостей по шаблонам"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "openapi-mcp",
"kind": "external",
"needs": ["OpenAPI/REST-спецификация интеграции"],
"produces": ["эндпоинты/схемы/параметры как MCP-ресурсы (чтение)"],
"constraints": ["READ-ONLY интроспекция спеки", "НЕ генерация кода"],
"preview-form": "none",
"defaults": ["read-only через .mcp.json"],
"key-decisions": ["какой эндпоинт/схему смотреть"],
"acceptance-criteria": ["структура API понята из спеки"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "operations:process-doc",
"kind": "external",
"needs": ["as-is process description"],
"produces": ["structured process documentation"],
"constraints": ["marketplace skill — outputs doc only"],
"preview-form": "none",
"defaults": ["follow operations plugin process-doc template"],
"key-decisions": ["scope of the process being documented"],
"acceptance-criteria": ["doc covers all process steps and owners"],
"source": { "version": "1.2.0", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "operations",
"kind": "external",
"needs": ["задача по бизнес-процессу (документ/runbook/риск/capacity)"],
"produces": ["процессный артефакт (process-doc/runbook/risk/capacity/...)"],
"constraints": ["зонтик 9 скилов, 0 lifecycle-хуков", "process-doc → Mermaid-исходник рендерит mermaid (#37)", "НЕ as-is анализ из кода (process-analysis)"],
"preview-form": "outline",
"defaults": ["скил под тип артефакта"],
"key-decisions": ["какой из 9 скилов под задачу"],
"acceptance-criteria": ["процессный артефакт полон (шаги/владельцы)"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "owasp-zap",
"kind": "external",
"needs": ["работающий веб-портал для DAST"],
"produces": ["DAST-отчёт (инъекции/XSS/обход входа/IDOR)"],
"constraints": ["активное динамическое тестирование; цель 127.0.0.1", "ADR-014: IS1 НЕ Semgrep #25 (динамика vs статика), IS2 НЕ Nuclei #69 (глубина vs широта)"],
"preview-form": "none",
"defaults": ["цель 127.0.0.1, не localhost"],
"key-decisions": ["scope активного скана"],
"acceptance-criteria": ["DAST не нашёл критичных уязвимостей"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "pa11y",
"kind": "external",
"needs": ["отрендеренная веб-страница / URL"],
"produces": ["отчёт о нарушениях доступности WCAG 2.1 AA"],
"constraints": ["единственный технический SoT a11y в проекте", "НЕ через Lighthouse", "НЕ визуальный смок (Playwright)"],
"preview-form": "none",
"defaults": ["проверять контраст / alt / роли / фокус-порядок по WCAG 2.1 AA"],
"key-decisions": ["какие страницы/URL в a11y-прогоне"],
"acceptance-criteria": ["0 нарушений WCAG 2.1 AA на проверяемых страницах"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,11 @@
{
"skill": "pdn-152fz-audit",
"kind": "own",
"needs": ["задача аудита ПДн / 152-ФЗ"],
"produces": ["аудит ПДн: инвентаризация, согласия, маскирование, логи доступа, pd_subject_request"],
"constraints": ["self-authored; режим техника + закон", "ADR-014: IS4 НЕ pg_anonymizer #29 (аудит vs инструмент), IS5 НЕ D2 (техника vs юр.оформление)"],
"preview-form": "outline",
"defaults": ["инвентаризация ПДн в схеме/коде → проверка соответствия"],
"key-decisions": ["режим: техника или закон"],
"acceptance-criteria": ["ПДн инвентаризированы, соответствие 152-ФЗ проверено"]
}
@@ -0,0 +1,12 @@
{
"skill": "pest",
"kind": "external",
"needs": ["PHP-тесты (unit/feature/RLS)"],
"produces": ["результат прогона тестов (pass/fail/assertions)"],
"constraints": ["Pest 4: unit/feature/RLS/parallel/browser/stress/mutation", "НЕ Vue-тесты (Vitest)"],
"preview-form": "none",
"defaults": ["composer test; --parallel в CI"],
"key-decisions": ["scope: какие тесты, parallel vs serial"],
"acceptance-criteria": ["все тесты зелёные, без регрессий"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "pg-anonymizer",
"kind": "external",
"needs": ["дамп БД с персональными данными"],
"produces": ["маскированный дамп (телефоны/имена/email)"],
"constraints": ["загрузка по требованию LOAD 'anon' (не db-wide preload)", "на проде liderra.ru"],
"preview-form": "sample",
"defaults": ["маски по требованию для выгрузок"],
"key-decisions": ["какие поля маскировать"],
"acceptance-criteria": ["ПДн в выгрузке замаскированы"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "pg-audit",
"kind": "external",
"needs": ["DDL/DML/DCL операции БД"],
"produces": ["аудит-журнал операций на уровне БД"],
"constraints": ["pgaudit.log=ddl,role,write; log_parameter=off (ПДн не логируются)", "на проде liderra.ru; закрывает 152-ФЗ"],
"preview-form": "none",
"defaults": ["журнал в /var/log/postgresql"],
"key-decisions": ["scope логируемых операций"],
"acceptance-criteria": ["аудит-журнал БД ведётся, ПДн не в логах"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "pg-formatter",
"kind": "external",
"needs": ["SQL-файл для форматирования"],
"produces": ["отформатированный SQL (отступы/регистр/выравнивание)"],
"constraints": ["форматирование SQL по хуку на db/schema.sql", "НЕ линт опасных паттернов (squawk)"],
"preview-form": "none",
"defaults": ["стандарт pgFormatter"],
"key-decisions": ["scope: какой SQL форматировать"],
"acceptance-criteria": ["SQL приведён к стандарту pgFormatter"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "pg-partman",
"kind": "external",
"needs": ["расписание партиционирования таблиц"],
"produces": ["авто-создание/удаление partition-таблиц по расписанию"],
"constraints": ["dormant — недоступно на native-Windows PG", "заменён Artisan partitions:create-months"],
"preview-form": "none",
"defaults": ["на поддерживаемой среде — по расписанию; на dev заменён ручным cron'ом"],
"key-decisions": ["окно/гранулярность партиций"],
"acceptance-criteria": ["партиции созданы/удалены по расписанию (на поддерживаемой среде)"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "php-insights",
"kind": "external",
"needs": ["PHP-код для метрик качества"],
"produces": ["метрики: сложность, архитектура, code style score"],
"constraints": ["on-demand/CI, не блокирует; пороги 78/79/73", "ADR-013 BT4 НЕ Pint/Larastan; уникум — оси complexity+architecture"],
"preview-form": "none",
"defaults": ["composer insights; baseline-пороги"],
"key-decisions": ["какие оси/пороги важны"],
"acceptance-criteria": ["метрики не ниже baseline-порогов"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "pint",
"kind": "external",
"needs": ["PHP-код для форматирования"],
"produces": ["отформатированный PHP по PSR-12 + Laravel style"],
"constraints": ["только форматирование стиля", "НЕ статанализ типов (Larastan)"],
"preview-form": "none",
"defaults": ["применять Laravel preset"],
"key-decisions": ["scope: staged-файлы vs весь php"],
"acceptance-criteria": ["0 расхождений со стилем Pint"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "playwright-mcp",
"kind": "external",
"needs": ["URL или HTML-файл для управления", "намерение: скриншот / взаимодействие / сетевой трейс"],
"produces": ["скриншот / результат взаимодействия / снимок console+network"],
"constraints": ["внешний MCP — управляет headless-браузером", "НЕ a11y-проверка (это Pa11y)", "НЕ замена unit/e2e-тестам"],
"preview-form": "sample",
"defaults": ["read-first: снять снимок/состояние страницы до действия"],
"key-decisions": ["что проверяем: визуал, взаимодействие или сетевой трейс"],
"acceptance-criteria": ["ожидаемое состояние страницы подтверждено снимком/снапшотом"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "plugin-dev",
"kind": "external",
"needs": ["намерение разработать marketplace Claude-плагин"],
"produces": ["scaffold плагина (plugin.json, MCP, хуки, доки, публикация)"],
"constraints": ["8 sub-skills + 3 агента", "НЕ standalone-скилы (skill-creator)"],
"preview-form": "outline",
"defaults": ["plugin.json + структура компонентов"],
"key-decisions": ["состав компонентов плагина"],
"acceptance-criteria": ["плагин валиден и публикуем"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "postgres-mcp",
"kind": "external",
"needs": ["исторический SQL-запрос к dev-БД"],
"produces": ["результат SQL (исторически)"],
"constraints": ["historic — заменён Laravel Boost (#10); не используется"],
"preview-form": "none",
"defaults": ["заменён Boost #10"],
"key-decisions": ["использовать Boost #10 вместо этого узла"],
"acceptance-criteria": ["SQL-запросы идут через Boost #10, не через этот узел"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "postiz",
"kind": "external",
"needs": ["контент-календарь / план публикаций в соцсети"],
"produces": ["запланированные публикации в 30+ соцсетях (VK/Telegram)"],
"constraints": ["self-host AGPL-3.0 без дистрибуции (ADR-015 MKT7)", "покрывает VK-постинг"],
"preview-form": "outline",
"defaults": ["контент-календарь, self-host"],
"key-decisions": ["каналы и расписание публикаций"],
"acceptance-criteria": ["публикации запланированы по календарю"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,11 @@
{
"skill": "process-analysis",
"kind": "own",
"needs": ["as-is процесс для discovery из кода Laravel"],
"produces": ["реконструкция as-is процесса + узкие места"],
"constraints": ["self-authored project skill; discovery из app-кода (routes/controllers/jobs)", "ADR-009 граница с discovery-interview (#55): код vs человек", "НЕ моделирование to-be (process-modeling)"],
"preview-form": "outline",
"defaults": ["discovery из маршрутов/контроллеров/джобов/очередей"],
"key-decisions": ["scope процесса для реконструкции"],
"acceptance-criteria": ["as-is процесс восстановлен, узкие места выявлены"]
}
@@ -0,0 +1,11 @@
{
"skill": "process-modeling",
"kind": "own",
"needs": ["to-be бизнес-процесс для моделирования"],
"produces": ["BPMN 2.0 модель (swimlane/события/шлюзы) в docs/process/"],
"constraints": ["self-authored project skill; нотация BPMN", "НЕ as-is анализ из кода (process-analysis)", "рендер диаграмм — через свой вывод/mermaid"],
"preview-form": "diagram",
"defaults": ["BPMN 2.0; результаты в docs/process/"],
"key-decisions": ["границы и дорожки процесса"],
"acceptance-criteria": ["to-be модель покрывает поток управления и роли"]
}
@@ -0,0 +1,11 @@
{
"skill": "prod-deploy-validator",
"kind": "own",
"needs": ["намерение выката на боевой liderra.ru"],
"produces": ["вердикт GO/NO-GO + результаты 8 SSH pre-flight проверок"],
"constraints": ["project-агент; READ-ONLY по дизайну; звать перед любым выкатом (Pravila §2.4)", "парный с #84; не в Tooling-каноне"],
"preview-form": "outline",
"defaults": ["8 READ-ONLY SSH-проверок: конфиг/сервисы/БД/очереди"],
"key-decisions": ["go/no-go по результатам проверок"],
"acceptance-criteria": ["вердикт GO/NO-GO обоснован pre-flight проверками"]
}
@@ -0,0 +1,12 @@
{
"skill": "product-management",
"kind": "external",
"needs": ["продуктовая церемония (спека / роадмап / метрики)"],
"produces": ["спецификация / роадмап / анализ метрик / конкурентный бриф"],
"constraints": ["продуктовые церемонии (write-spec/roadmap/metrics)", "НЕ dev-issues (CCPM)"],
"preview-form": "outline",
"defaults": ["/write-spec, /roadmap-update, /metrics-review"],
"key-decisions": ["какая церемония под запрос"],
"acceptance-criteria": ["артефакт церемонии готов и обоснован"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "promptfoo",
"kind": "external",
"needs": ["LLM-промпт + тест-кейсы для eval"],
"produces": ["результат eval/регрессии промпта (ассерты/judge/red-team)"],
"constraints": ["только вручную/CI, никогда в хук (платные вызовы)", "НЕ SAST (Semgrep)"],
"preview-form": "none",
"defaults": ["запуск вручную или в CI"],
"key-decisions": ["метрики/ассерты eval"],
"acceptance-criteria": ["промпт прошёл регрессию/eval по критериям"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "rector",
"kind": "external",
"needs": ["PHP-код для авто-рефакторинга/version-upgrade"],
"produces": ["трансформированный PHP (upgrade/dead-code/modernization)"],
"constraints": ["вручную/CI, не блокирует коммит", "ADR-013: BT1 НЕ Pint (трансформация vs формат), BT2 комплементарен Larastan, BT3 НЕ deptrac"],
"preview-form": "dry-run",
"defaults": ["dry-run baseline; conservative ruleset"],
"key-decisions": ["какие правила трансформации"],
"acceptance-criteria": ["код трансформирован, тесты зелёные"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "redis-mcp",
"kind": "external",
"needs": ["вопрос о состоянии Redis/Memurai (ключи/очереди/TTL)"],
"produces": ["состояние Redis (чтение): ключи, очереди, TTL, паттерны"],
"constraints": ["READ-ONLY MCP к Redis/Memurai", "НЕ prod-ошибки (Sentry MCP)"],
"preview-form": "none",
"defaults": ["read-only инспекция кэша/очередей"],
"key-decisions": ["что инспектировать: кэш, очередь, race"],
"acceptance-criteria": ["состояние кэша/очереди/race локализовано"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "roave-security",
"kind": "external",
"needs": ["composer install/update"],
"produces": ["блок установки пакета с известным CVE"],
"constraints": ["conflict-список CVE на install/update", "НЕ SAST кода (Semgrep)"],
"preview-form": "none",
"defaults": ["срабатывает автоматически на composer"],
"key-decisions": ["нет ручного выбора — автоматический conflict-гейт"],
"acceptance-criteria": ["установка с известным CVE заблокирована"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,11 @@
{
"skill": "ru-tax-accounting",
"kind": "own",
"needs": ["вопрос по РСБУ/НК РФ (НДС/УСН/налоговая база/проводки)"],
"produces": ["РСБУ/налоговый контекст: расчёты, проводки ДТ/КТ, выгрузки бухгалтеру"],
"constraints": ["self-authored; закрывает РФ-gap finance-plugin (#61)", "ADR-012: НЕ billing-audit #62 (код), НЕ D1/D2 (право)"],
"preview-form": "outline",
"defaults": ["НДС/УСН по НК РФ; проводки по РСБУ"],
"key-decisions": ["налоговый режим и база"],
"acceptance-criteria": ["налоговый расчёт/проводка корректны по НК РФ/РСБУ"]
}
@@ -0,0 +1,11 @@
{
"skill": "security-go-live",
"kind": "own",
"needs": ["портал перед выходом в интернет (go/no-go)"],
"produces": ["вердикт GO/NO-GO + сводка проверок безопасности"],
"constraints": ["self-authored оркестратор #68-72 + D3", "ADR-014 IS7 НЕ audit-portal (только security+go-live vs полный 14-фазный аудит)"],
"preview-form": "outline",
"defaults": ["оркеструет ZAP/Nuclei/Ward/pdn-152fz/threat-model + Semgrep"],
"key-decisions": ["go/no-go порог"],
"acceptance-criteria": ["вердикт GO/NO-GO обоснован результатами проверок"]
}
@@ -0,0 +1,12 @@
{
"skill": "security-guidance",
"kind": "external",
"needs": ["правка файла с потенциально уязвимым паттерном"],
"produces": ["inline-предупреждение + блок правки (sys.exit 2)"],
"constraints": ["блокирующий PreToolUse-хук, speed-bump per файл+правило", "НЕ глубокий аудит (Trail of Bits)"],
"preview-form": "none",
"defaults": ["одноразовый блок, retry проходит"],
"key-decisions": ["реальная уязвимость vs false-positive"],
"acceptance-criteria": ["уязвимый паттерн замечен до коммита"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "semgrep",
"kind": "external",
"needs": ["PHP/JS/Vue-код для SAST"],
"produces": ["отчёт об уязвимостях кода (инъекции, небезопасная конфигурация, XSS)"],
"constraints": ["SAST бинарь + MCP", "НЕ секреты в diff (gitleaks)", "НЕ глубокий on-demand аудит (Trail of Bits)"],
"preview-form": "none",
"defaults": ["npm run sast"],
"key-decisions": ["scope скана; реальная уязвимость vs false-positive"],
"acceptance-criteria": ["0 уязвимостей высокого риска"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "sentry-mcp",
"kind": "external",
"needs": ["production runtime ошибка для диагностики"],
"produces": ["события/ошибки/трассировки из Sentry (чтение)"],
"constraints": ["READ-ONLY MCP к self-hosted Sentry", "pending активации Б-1", "НЕ Redis-состояние (Redis MCP)"],
"preview-form": "none",
"defaults": ["read-only диагностика"],
"key-decisions": ["какое событие/трассировку смотреть"],
"acceptance-criteria": ["причина prod-ошибки локализована по событию Sentry"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "skill-creator",
"kind": "external",
"needs": ["намерение создать standalone Claude-скил"],
"produces": ["scaffold SKILL.md + evals.json + references/"],
"constraints": ["конструктор скилов с eval-набором", "НЕ плагины (plugin-dev)", "НЕ хуки (hookify)"],
"preview-form": "outline",
"defaults": ["скил с eval-набором для проверки точности"],
"key-decisions": ["scope и триггеры скила"],
"acceptance-criteria": ["скил оформлен, eval проходит"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "squawk",
"kind": "external",
"needs": ["SQL-миграция PostgreSQL"],
"produces": ["отчёт об опасных паттернах миграции (блокировки, без CONCURRENTLY, ненадёжный DEFAULT)"],
"constraints": ["линт миграций в pre-commit для database/migrations", "НЕ форматирование (pgFormatter)"],
"preview-form": "none",
"defaults": ["прогон на staged миграциях"],
"key-decisions": ["блокирующая операция vs допустимая"],
"acceptance-criteria": ["0 опасных паттернов в миграции"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "stylelint",
"kind": "external",
"needs": ["CSS-код в .vue SFC или .css-файлах"],
"produces": ["отчёт о нарушениях стиля CSS"],
"constraints": ["CSS в .vue SFC + css-файлы", "НЕ JS/TS (ESLint)", "НЕ a11y (Pa11y)"],
"preview-form": "none",
"defaults": ["порядок свойств + именование по конфигу проекта"],
"key-decisions": ["scope: staged-файлы vs весь css"],
"acceptance-criteria": ["0 нарушений стиля CSS"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,11 @@
{
"skill": "superpowers",
"kind": "own",
"needs": ["задача разработки, требующая процесса (TDD/debug/plan/review/brainstorm/worktree)"],
"produces": ["дисциплинированный процесс: тест-первый, план, ревью, верификация"],
"constraints": ["мета-процесс (зонтик 14 sub-skills), не доменный решатель", "hard-floor источник (ADR-011)", "paired со #30 Frontend Design"],
"preview-form": "outline",
"defaults": ["skill инвоцируется ПЕРВЫМ для подходящей задачи"],
"key-decisions": ["какой sub-skill под задачу (tdd/debugging/writing-plans/brainstorming/...)"],
"acceptance-criteria": ["процесс-скил применён ДО реализации, дисциплина соблюдена"]
}
@@ -0,0 +1,12 @@
{
"skill": "telegram-mcp",
"kind": "external",
"needs": ["пост/действие в Telegram-канале"],
"produces": ["публикация/редактирование/аналитика Telegram-канала"],
"constraints": ["выделенный аккаунт через SESSION_STRING (только .env)", "ADR-015 MKT8"],
"preview-form": "none",
"defaults": ["выделенный аккаунт, SESSION_STRING в .env"],
"key-decisions": ["канал и контент поста"],
"acceptance-criteria": ["пост опубликован / аналитика получена"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,11 @@
{
"skill": "threat-model",
"kind": "own",
"needs": ["портал для моделирования угроз перед публикацией"],
"produces": ["STRIDE-модель: attack surface + приоритеты защиты"],
"constraints": ["self-authored; STRIDE going-public", "ADR-014 IS6 НЕ Trail of Bits #39 (портал+STRIDE vs generic deep-audit)"],
"preview-form": "outline",
"defaults": ["карта точек входа → приоритизация по STRIDE"],
"key-decisions": ["что защищать первым перед публикацией"],
"acceptance-criteria": ["attack surface картирован, защита приоритизирована"]
}
@@ -0,0 +1,12 @@
{
"skill": "trail-of-bits",
"kind": "external",
"needs": ["diff/код для глубокого security-аудита"],
"produces": ["глубокий аудит-отчёт (diff-review / supply-chain / variant / static)"],
"constraints": ["on-demand глубокий аудит (8 скилов)", "НЕ inline-блок (Security Guidance)", "НЕ быстрый SAST (Semgrep)"],
"preview-form": "outline",
"defaults": ["on-demand кампания, не в хуке"],
"key-decisions": ["какой аудит-скил под задачу"],
"acceptance-criteria": ["аудит покрыл diff/supply-chain/варианты"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "trivy",
"kind": "external",
"needs": ["Docker-образ для скана"],
"produces": ["отчёт о CVE в OS-пакетах и зависимостях образа"],
"constraints": ["скан Docker-образов в CI перед push в registry", "НЕ скан кода (Semgrep)"],
"preview-form": "none",
"defaults": ["trivy image перед push в Yandex Container Registry"],
"key-decisions": ["порог severity для блока"],
"acceptance-criteria": ["0 критичных CVE в образе"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "ui-ux-pro-max",
"kind": "external",
"needs": ["UI-задача, требующая материала (стиль / палитра / UX-гайд / график)"],
"produces": ["UI-материалы: стили, палитры, UX-гайдлайны, паттерны графиков"],
"constraints": ["материал, не решатель; только через PSR_v1 R14.3 pipeline", "не параллельно с Frontend Design (#30)"],
"preview-form": "none",
"defaults": ["активация только через R14.3 pipeline"],
"key-decisions": ["какой материал нужен под задачу"],
"acceptance-criteria": ["материал отобран, решение принимает Frontend Design"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "unisender-go-mcp",
"kind": "external",
"needs": ["массовая email-рассылка через Unisender Go"],
"produces": ["отправка маркетинговой email-рассылки"],
"constraints": ["DEFERRED — нет upstream MCP, нужна своя обёртка", "ADR-015 MKT5: маркетинговые рассылки НЕ транзакционный email; 152-ФЗ согласия cross-ref #77/#71"],
"preview-form": "none",
"defaults": ["при наличии обёртки"],
"key-decisions": ["сегмент и согласия рассылки"],
"acceptance-criteria": ["рассылка отправлена с соблюдением 152-ФЗ согласий (при наличии обёртки)"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "universal-icons-mcp",
"kind": "external",
"needs": ["потребность в SVG-иконке (не-Lucide коллекция)"],
"produces": ["SVG-иконка из коллекции (Material/Tabler/Phosphor/...)"],
"constraints": ["только не-Lucide коллекции (ADR-006: Lucide → lucide-vue-next)", "raw-SVG, не компонент"],
"preview-form": "sample",
"defaults": ["для Lucide использовать lucide-vue-next, не этот MCP"],
"key-decisions": ["коллекция и конкретная иконка"],
"acceptance-criteria": ["иконка вставлена из не-Lucide коллекции корректно"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "vitest",
"kind": "external",
"needs": ["Vue-компоненты/модули для unit/component-тестов"],
"produces": ["результат прогона Vitest (pass/fail)"],
"constraints": ["тесты Vue (jsdom, @vue/test-utils, Pinia)", "НЕ PHP-тесты (Pest)"],
"preview-form": "none",
"defaults": ["npm run test:vue"],
"key-decisions": ["scope тестов"],
"acceptance-criteria": ["все Vue-тесты зелёные"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "volar",
"kind": "external",
"needs": ["открытый .vue-файл в VSCode"],
"produces": ["IntelliSense / go-to-definition / hover / диагностика типов в редакторе"],
"constraints": ["VSCode-расширение (редактор-only)", "НЕ CI type-check (vue-tsc)"],
"preview-form": "none",
"defaults": ["работает в редакторе автоматически"],
"key-decisions": ["нет ручного выбора — редакторная служба"],
"acceptance-criteria": ["навигация/диагностика по .vue работают в редакторе"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "vue-tsc",
"kind": "external",
"needs": [".vue-компоненты для проверки типов"],
"produces": ["отчёт о несоответствиях типов в шаблонах/script"],
"constraints": ["полный type-check только в CI", "НЕ редакторная служба (Volar)"],
"preview-form": "none",
"defaults": ["прогон в CI"],
"key-decisions": ["scope проверки типов"],
"acceptance-criteria": ["0 ошибок типов vue-tsc"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "ward",
"kind": "external",
"needs": ["Laravel-проект (.env/config/cookie/secrets/deps)"],
"produces": ["аудит-отчёт безопасности настроек Laravel"],
"constraints": ["Go CLI bin/ward.exe; заменил Enlightn (abandoned)", "ADR-014 IS3 НЕ Larastan #12/Semgrep #25"],
"preview-form": "none",
"defaults": ["скан .env/config/заголовков/secrets"],
"key-decisions": ["какие настройки критичны"],
"acceptance-criteria": ["настройки Laravel безопасны (нет High-находок)"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,11 @@
{
"skill": "writing-plans",
"kind": "own",
"needs": ["spec or requirements", "file structure decisions"],
"produces": ["implementation-plan with bite-sized TDD tasks"],
"constraints": ["only writes the plan file", "no code, no reads beyond plan authoring"],
"preview-form": "outline",
"defaults": ["one action per step (2-5 min)", "test->RED->code->GREEN->commit"],
"key-decisions": ["file structure / decomposition", "task granularity"],
"acceptance-criteria": ["every step has concrete content (no placeholders)", "types consistent across tasks"]
}
@@ -0,0 +1,12 @@
{
"skill": "yandex-metrika-mcp",
"kind": "external",
"needs": ["вопрос веб-аналитики лендинга"],
"produces": ["данные Яндекс.Метрики (визиты/источники/гео/демография/поведение)"],
"constraints": ["READ-ONLY MCP; активен при живом лендинге (Б-1)", "НЕ подбор ключевых слов (Wordstat #79)"],
"preview-form": "none",
"defaults": ["read-only чтение метрик"],
"key-decisions": ["какие метрики смотреть"],
"acceptance-criteria": ["аналитика лендинга получена из Метрики"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "yandex-wordstat-mcp",
"kind": "external",
"needs": ["тема для подбора ключевых слов"],
"produces": ["частотность запросов РФ (Wordstat): сезонность, связанные фразы"],
"constraints": ["только Wordstat (5 read-only tools); Direct-мутации отключены (ADR-015)", "НЕ веб-аналитика (Метрика #78)"],
"preview-form": "none",
"defaults": ["только Wordstat-модуль"],
"key-decisions": ["набор ключевых фраз"],
"acceptance-criteria": ["ключевые слова подобраны с частотностью РФ"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
+5
View File
@@ -158,6 +158,7 @@ nodes:
attributes:
tooling_section: "§3.5 #10"
install: "composer require laravel/boost --dev"
conflicts_with: ["postgres-mcp"]
- id: "#11"
name: "Laravel Pint"
@@ -333,6 +334,7 @@ nodes:
chain_membership: []
attributes:
tooling_section: "§3.1 #1 (historic)"
conflicts_with: ["boost"]
- id: "#20"
name: "Volar"
@@ -523,6 +525,7 @@ nodes:
attributes:
tooling_section: "§4.4 #30"
install: "enabledPlugins.frontend-design@anthropics-claude-plugins в ~/.claude/settings.json"
conflicts_with: ["ui-ux-pro-max", "21st-magic"]
- id: "#31"
name: "UI UX Pro Max"
@@ -543,6 +546,7 @@ nodes:
chain_membership: []
attributes:
tooling_section: "§4.5 #31"
conflicts_with: ["frontend-design", "21st-magic"]
- id: "#32"
name: "21st.dev Magic MCP"
@@ -559,6 +563,7 @@ nodes:
chain_membership: []
attributes:
tooling_section: "§4.6 #32"
conflicts_with: ["frontend-design", "ui-ux-pro-max"]
- id: "#33"
name: "claude-md-management"
@@ -0,0 +1,144 @@
# Аудит готовности М7 Фазы 8 «Растворение зоопарка + переезд» — находки
**Дата:** 2026-06-09 · **Ветка:** `worktree-brainrepo` · **HEAD на старте:** `4ea4806f`
**Метод:** read-only, инлайн (субагенты запрещены). Скилы: `audit-context-building` (+`sharp-edges` по швам).
**Источники:** spec `docs/superpowers/specs/2026-06-08-router-mentor-machine-7-design.md` (§5/§9/§10/§12) + handoff `2026-06-09-phase8-migration-handoff.md` + loose-ends `2026-06-08-router-mentor-loose-ends-registry.md` + код `tools/*.mjs`.
**Принцип:** память `*_done.md` НЕ источник истины — всё верифицируется ПО КОДУ.
**Кодовая фраза:** «роутер-наставник».
---
## Вердикт
Оборонный контур М1–М6 собран на **~95%**. Блокёр №1 (content-floor port, §10 V1/V1-PS) **закрыт в коде**.
Остаток несущего кода Claude **мал**: один настоящий gap (**G1** — verify→Гейт-3 не проведён живьём) + одно
решение с мелким кодом (**G2** — манифест 9→11). Всё прочее — owner-активация (A-блок) или осознанно отложено.
---
## Confirmed-by-code (собрано)
| ID | Шов / пункт §12 | Статус | Доказательство |
|---|---|---|---|
| **C-1** | Seam #1 content-floor port (БЛОКЁР §10 V1/V1-PS, правило 8) | ✅ ПОЛНЫЙ | `shell-content-rules.matchBashHardBlacklist` — весь P-1 набор (rm/mv/cp/chmod/chown/chgrp + node -e/-r/--import/fs.* #4 + python/bash/sh -c + eval + composer/npm/yarn/pnpm install + npx claude-* + curl -X + env-prefix #21 + --watch #22 + wget G7 + nc/ncat/netcat/socat G8 + echo-inj #34 + stderr-redirect C16). `matchPsHardBlacklist` — весь V1-PS (Remove/Move/Copy/Set/Add-Content/Out-File + redirect + iex + egress IWR/IRM/curl/wget + Start-Process + [IO.File]/[IO.Directory] + Stop-Process + service + ExecutionPolicy + ItemProperty + Get-Credential + Restart/Stop-Computer + ScheduledTask + Set-Acl/icacls + New-Item + G10 env/cloud). `floor-decide.floorDecide`: Bash (`bashIsContentBlock` whole+per-segment P-4 + `detectSubshell`) и PS (`psContentBlock` + `psProtectedWrite` P-3 forge-страж) рубят **независимо от плана**, escapable. tool-agnostic write-guard (MCP-writer) + fail-CLOSED на бросок normalize. |
| **C-2** | `canonicalAction` PS-ветка (P-2) | ✅ | `escape-grant.canonicalAction`: `if (name==='PowerShell') return powershell:${normalizeCommand(input.command)}` — escape специфичен per PS-команда (не схлопывается в `write:`). |
| **C-3** | Проводка пола (enforce-floor) | ✅ | `enforce-floor.decide` зовёт `floorDecide` ПЕРВЫМ, fail-CLOSED (любая ошибка → block), panic-ветка (правило 7б): если `floorDecide` бросит ДО своего escape-чека — `escapeAllowsEvent` всё равно оценивается (не кирпич мимо escape). |
| **C-4** | Seam #2 escape-survivability (правило 7/L6) | ✅ ПОДТВЕРЖДЁН | `enforce-read-path-deny` (Read+escapeGrantOpen), `enforce-mcp-classification` (mcp+egress ветки + escaped()), `enforce-normative-content-rules` (escapeAction + escaped law-edit §6) — все читают грант. Пол тоже. `canonicalAction` тотальна. |
| **C-5** | Seam #3 транспорт судьи М4 | ✅ ЕСТЬ (owner-активация остаётся) | `enforce-judge-gate.callJudgeModel` → реальный `callAnthropicAPI` (Sonnet за `ROUTER_LLM_KEY`); `runJudgeGate` async + degraded-allow на unavailable (VA-FIX-1, не фабрикует NO-GO); `runJudgeTurn` 3 режима inert/shadow/live-block; `decide``finalGate` (пол перевешивает). Рубильник `judge-gate-config.judgeActive` = флаг `ROUTER_MENTOR_JUDGE_ENABLED=1` + `resolveJudgeKey`. `judgeGateMode` = inert/shadow/live-block (`ROUTER_MENTOR_JUDGE_MODE=block`). |
| **C-6** | Seam #4 skill-журналер (SE-K) | ✅ | `enforce-skill-journaler` (PostToolUse Skill) пишет каждый Skill в action-journal (op:'Skill') + дедуп vs wall-pre-write; мост `extractSkillCalls`→K2. |
| **C-7** | Seam #4 доска «кто на посту» | ✅ (live-источник событий — follow-up) | `status-md-generator.computeGuardBoardBlock` рендерит манифест (`checkManifest`→registered/missing→⚠️ ПОСТ ПУСТОЙ) + режим судьи (`judgeGateMode()` inert/shadow/floor-only/live-block). recentEscapes/recentBlocks = `[]` (детализация follow-up, см. D-3). |
| **C-8** | Seam #4 normative-канал КАРТА/ЗАКОН (SE-D) | ✅ | `enforce-normative-content-rules`: `DISCIPLINE_SOURCE_RE` (tools/enforce-/judge-/floor-/escape-grant/action-journal/receipt-/shell-content-rules/plan-lock/classify-destructive/path-normalization) + `sealedPlanCoversEdit` (build-loop: LAW под планом → CARD) + escaped owner law-edit (§6) снимает гейт. |
| **C-9** | Манифест полный (SE-B) | ✅ | `floor-manifest-check.DEFAULT_REQUIRED_HOOKS` = все 9 хуков М1–М6 (floor/supreme-gate/normative-content-rules/read-path-deny/mcp-classification/judge-gate/snapshot/floor-escape-consume/skill-journaler). WARN-only (Δ8). |
| **C-10** | Поглощённые coverage/todowrite → журнал-K2 (Фаза 4a) | ✅ переписаны in-place, fail-CLOSE | `enforce-coverage-verify.decide` судит `skill:`-канал по `skillTakenByJournal` (K2) ∩ turn-scope (`turnToolUses`), НЕ по строке coverage (Класс 1 закрыт). `direct/node/chain/hook/agent` приняты на этом слое (судит живой М4, §1 П3). `enforce-todowrite-skill-verifier` тоже через `extractSkillCalls`. `domain-skill-discipline` (K2). |
| **C-11** | Стена М2 + волны R-08 | ✅ | `enforce-supreme-gate`: `verifyFrozenPlan`/`actionMatchesStep`/`treeLeafAt`/`validatePlanTree` (tree-aware волны), подписанные расписки (`receipt-sign`), журналирование op:Skill на plan-step (substrate K2). |
---
## 🔴 Остаток несущего кода (Claude)
### G1 — НЕСУЩИЙ: verify→Гейт-3 не проведён живьём (блокирует увольнение `verify-before-push`)
`enforce-verify-before-push`**старый** zoo-хук (sentinel-артефакт + override-фраза + `RULE_KEY_PUSH`), §10 его увольняет.
Замена по §5 = «М5 подписанный GREEN + М4 Гейт-3». `judge-engine` **имеет** линзы `gate3: [goal_achieved, premortem_whole, behavior_vs_goal]` (`requiredLensesFor`),
**но хук судьи (`enforce-judge-gate`) бьёт только Гейт-2** (`extractGate2Product` = Write плана `docs/superpowers/plans/*.md`).
Гейт-3 ни на каком живом триггере не висит; нет PreToolUse-хука, требующего подписанный GREEN перед `git commit/push`.
**Увольнять `verify-before-push` без G1 = потерять pre-push verify-дисциплину.** NO-GO на это увольнение до G1.
**Достроить:** живой Гейт-3 / подписанный-GREEN PreToolUse на `git commit/push` (М4 Гейт-3 + М5 signer-расписка).
### G2 — решение + мелкий код: журнал-K2 coverage/todowrite не в манифесте
`enforce-coverage-verify` + `enforce-todowrite-skill-verifier` переписаны в журнал-K2 fail-CLOSE (Фаза 4a),
но **отсутствуют в `DEFAULT_REQUIRED_HOOKS` (9)** и §10 числит `coverage-verify` к увольнению.
**Решение:** оставить+зарегистрировать журнал-K2-версии (расширить манифест 9→11) **vs** уволить (судья coverage не судит — Гейт-2 only).
Рекомендация: **оставить+зарегистрировать** (это и есть миграция дисциплины; иначе coverage-дисциплина теряется).
Код: расширить `DEFAULT_REQUIRED_HOOKS` + runbook-регистрация.
---
## 🟥 Owner-активация (кода Claude нет — A-блок loose-ends)
A1-half (флаг `ROUTER_MENTOR_JUDGE_ENABLED=1` + ключ судьи keychain + регистрация хука + shadow→block) · A2 Фаза 8
(атомарно 9 хуков + увольнение зоопарка + `sealedPlanCoversEdit` live) · A3 пол · A4 escape/снимок · A5 стена matcher `*` + HMAC ·
A6 M1 matcher · A7 M3 живой Sonnet · A8 правила судьи в protected-файл · A9 push.
## ⚪ Отложено осознанно (owner-scoped / контракты)
R-10 G6-адаптер (НЕ построен) · M4 think-layer 4 пункта · M6 FIX-5 подпись floor_escape-гранта (spec §6 «не обязательно») ·
доска recentEscapes/Blocks live-источник (сейчас `[]`) · observer Фаза 2 (авто-правка нормативки — owner-scope).
## 🟨 Doc-drift (косметика, не блок)
**D-1:** шапка `enforce-judge-gate` стр.12-14 устарела — пишет «реальный llmCall-транспорт подключит владелец», но `callJudgeModel` уже использует `callAnthropicAPI`. Транспорт проведён A1; остаётся лишь флаг/ключ/режим.
**D-3:** доска recentEscapes/recentBlocks = `[]` (live-источник — follow-up B-блока R-09/R-30, не E).
---
## §10 GO/NO-GO на увольнение
| Зоопарк-хук | Вердикт | Условие |
|---|---|---|
| `router-gate` / `powershell-gate` | ✅ **GO** | content-floor port C-1 закрыт (блокёр снят) |
| `runtime-write-deny` | ✅ GO | пол RUNTIME_RE + escape |
| `verify-before-push` / `verify-record` | 🔴 **NO-GO до G1** | Гейт-3 не проведён живьём |
| `coverage-verify` / `todowrite-skill-verifier` | 🟡 GO при G2 | оставить журнал-K2 + манифест 9→11 |
| `branch-switch` | ✅ GO | М2 plan-step + M6 escape (маркеры удалены — подтвердить в Pass B) |
| `llm-judge-per-tool` / `-response-scan` (L4) | ✅ GO по A1 | после активации судьи М4 |
| `router-prehook` / `router-tool-gate` / `router-stop-gate` / `router-embedding-warmup` | ✅ GO по A7 | после живого М3 |
| `memory-coverage` / `tdd-gate` / `tdd-real-test-verifier` / `self-debrief-detector` / `rationalization-audit` / `prompt-injection`(reminder) | ✅ GO | поглощены §4.2 (см. Pass B детально) |
| `decomposition-detector` / `safe-baseline-metering` / `parallel-session-lock` | ✅ GO | no-op (ничего не теряют) |
| `subagent-return-scanner` / `subagent-prompt-prefix` | ✅ GO | субагенты запрещены |
| dead-vocab `findOverride`/`findOverrideAttempt` + врущие сообщения | ✅ GO | чистка Класса 6 |
---
## Pass B — построчный аудит ядер M1/M3/M6 (добор «ничего не пропускай»)
Швы M2/M4/M5 покрыты выше (C-1..C-11). Здесь — построчный добор ядер M1/M3/M6.
### M1 — Фундамент (журнал + ключ). Вердикт: ✅ **здоров, дефектов нет**
- `escape-grant.canonicalAction`**тотальна** (try/catch → `'unknown:'` на любой throw; вход всегда доходит до escape-чека, SE-I/L6). Ветки Bash/**PowerShell** (P-2)/mcp/write. `findOpenGrant`**единый** предикат свежести (action-match + `ts∈[0,5мин]` + не-погашен) для open-проверки И consume → гасится ИМЕННО открывший грант, без дрейфа. Гранты читаются read-only из `askuser-decisions-<sess>.jsonl` (type `floor_escape`), пишет среда (`enforce-askuser-answer-parser`), контроллер канал не пишет (runtime protected).
- `action-journal` — хеш-цепочка `sha256(prev_hash + canonicalJson({seq,ts,payload}))` (**seq+ts в хеше**, B2 — подмена метаданных задним числом ломает цепь); голова HMAC-подписана (`JOURNAL_HEAD`); `verifyChain` пересчитывает каждый хеш + linkage + подпись головы, структурно-битая запись → `{ok:false}` **значением**, не throw (N2, fail-closed). `assertSafeSessionId` — path-injection guard (N3, единый для всех строителей пути). **Неатомарность** дозаписи (append jsonl + write head — 2 fs-вызова) остаётся по конструкции, но **запинена** характеризационным тестом (`5782ede3`): обрыв → `verifyChain {ok:false, brokenAt}` (fail-closed); полная починка = иной дизайн персиста, owner-scope.
- `receipt-sign``canonicalJson` (сорт ключей, стабильно); `signPayload` HMAC-SHA256(`domain + ' ' + canonicalJson`), без ключа → null; `verifyReceipt` — формат sig (64 hex) + пересчёт + `timingSafeEqual` (constant-time), без ключа/sig → false (fail-closed). **Domain-separation (R-31)** заморожен: JOURNAL_HEAD/FROZEN_PLAN/FROZEN_ARTIFACT/APPROVAL/STEP_PTR/**M5_GREEN** — подпись одного типа не принимается за другой. **NB:** домен `M5_GREEN` существует → примитив подписанного GREEN для G1 **готов**; не хватает лишь хук-триггера (см. G1).
### M6 — Ключ+снимок (escape + откат). Вердикт: ✅ **здоров, дефектов нет**
- `enforce-snapshot` (PreToolUse после пола): перед разрушительным — `git stash create` + `update-ref refs/floor-snapshots/<id>` + `restore-points.jsonl`. Чистое дерево → ref=HEAD; **git-ошибка → fail-CLOSE (block)**; уникальный id (FIX-4: ts+pid+монотонный счётчик — два разрушительных в одну мс не клоббернут точку возврата). Инфра-ошибка самого хука (parse/stdin) → fail-open allow, но это не дыра: пол (`enforce-floor`) рубит разрушительное отдельно и fail-CLOSE; снимок = страховка отката, не гейт.
- `floor-escape-consume` + `enforce-floor-escape-consume` (PostToolUse): one-shot погашение через тот же `findOpenGrant`. **Порядок верен:** снимок-блок (PreToolUse) → действие не исполнилось → PostToolUse не сработал → грант НЕ сгорел (escape переживает снимок-блок). fail-open (PostToolUse не бросает) — приемлемо (не-погашение = мелкий over-permit в 5-мин окне, не дыра; блокировать PostToolUse не может).
- **FIX-5 (подпись floor_escape-гранта) осознанно отложена** (spec §6 «не обязательно»): целостность гранта держит **защита записи в runtime** (пол RUNTIME_RE + read-path-deny + content-floor #4 node-inline-fs рубят forge), а не подпись. Forge через `node -e fs.*` / PS-запись закрыт content-floor (C-1). Остаточный риск нулевой пока content-floor стоит; owner-noted.
### M3 — Роутер (каким навыком, по журналу). Вердикт: ✅ **классификатор жив; «не тот навык» по K2 — модуль готов; живой Sonnet = A7 owner**
- `router-classifier` **жив** (выдаёт `task_type`/`recommended_chain` — виден в §17-хук-контексте каждый ход). Аудит ядра + 5 TDD-фиксов — память `project_m3_audit_and_fixes` (chosen∈candidates / managed-терминатор / token-cover / neutrality).
- «Не тот навык по журналу» — `domain-skill-discipline` (`uncalledDomainSkills`/`domainCallDiscipline`, K2: рекомендованный доменный навык не вызван по журналу → not ok). Чистый модуль; **хук-проводка enforce — часть переезда/A7** (не отдельный enforce-хук сейчас). routing-tag-обход (`router-tool-gate`) увольняется §10.
- Живой Sonnet вместо мока для роутинг-решений = **A7 (owner)**.
### M2/M4/M5 — сводка (детали C-1..C-11)
- **M2 стена** `enforce-supreme-gate`: действие=шаг запечатанного плана, tree-aware волны (R-08), подписанные расписки, журналирование op:Skill (substrate K2). ✅
- **M4 судья** — механический пол `judge-gate-floor` (K2/K5) + думающий `judge-engine` (линзы gate1/gate2/**gate3**) + транспорт `callAnthropicAPI`; хук бьёт **только Гейт-2** (G1). ✅ транспорт / 🔴 Гейт-3 wiring.
- **M5 пол** `floor-decide`: content-floor (Bash+PS, P-1/V1-PS) + write-guard + критерий/мутация (прежние аудиты). ✅
- `branch-switch` — увольняется целиком §10 (замена = M2 plan-step + M6 escape); проверять marker-removal не нужно (хук уходит).
---
## Итог аудита (Шаг 1 завершён)
**Контур М1–М6 собран и аудирован по коду. Криптоядра M1/M6 здоровы, швы M2/M4/M5 закрыты, блокёр №1 (content-floor port) полон.**
**Несущий остаток Claude = G1 (Гейт-3/подписанный-GREEN wiring) + G2 (манифест 9→11, решение).** Примитив подписи для G1 (`M5_GREEN`) готов.
Всё прочее — owner-активация (A-блок) и осознанно отложенное. §10-увольнение безопасно при G1 (для verify-before-push) и G2 (для coverage/todowrite); router-gate/powershell-gate — GO уже сейчас.
---
## G1 (Level A) — ПОСТРОЕН (2026-06-09, commit-not-push)
Цепочка скилов отработана полностью: `audit-context-building``sharp-edges``variant-analysis``writing-plans``executing-plans` (инлайн TDD, наблюдаемый RED) → `verification-before-completion`. План: `docs/superpowers/plans/2026-06-09-g1-gate3-verify-signed-green.md`.
**7 task-коммитов `1857773f..b92afad9`** на `worktree-brainrepo` (HEAD `b92afad9`), origin НЕ тронут:
- `1857773f` task1 — `RECEIPT_DOMAINS.VERIFY_PASS` (receipt-sign).
- `10885990` task2 — `verify-receipt.mjs` (`signVerifyReceipt`/`verifyReceiptSig`/`acceptVerifyReceipt`: sig + occurrence>0 + fingerprint-match).
- `0f20c944` task3 — `verify-gate-config.mjs` рубильник (`ROUTER_MENTOR_VERIFY_ENABLED`: OFF=inert$0 / ON+key=enforce / ON+no-key=fail-CLOSE).
- `93e516e6` task4 — producer pure `buildVerifyReceipt`.
- `9ce6041b` task5 — producer staged-fingerprint + I/O main (сам прогоняет сюиту, SE-G1-5).
- `2bd7efbe` task6 — consumer `enforce-verify-gate.mjs` (PreToolUse, fail-CLOSE + escape M6 + docs-only, fingerprint из реального `git diff --staged`).
- `b92afad9` task7 — `enforce-verify-gate` в `DEFAULT_REQUIRED_HOOKS` (9→10).
**Верификация:** vitest tools regression **3350 passed / 2 skipped / 0 failed** (167 файлов; свежий прогон 10:25:55), выше планки §9.2 ≥2843+2skip. +27 G1-тестов к baseline 3323.
**Решение реализации (deviation, обосновано):** verify-receipt пишется в ЕДИНЫЙ `~/.claude/runtime/verify-receipt.json` (НЕ session-scoped) — producer-CLI не имеет session_id из stdin как consumer-хук; свежесть держит fingerprint. Escape-гранты остаются session-scoped.
**§10 разблокировано:** `verify-before-push`/`verify-record` теперь увольняемы (замена = `enforce-verify-gate`). **GO/NO-GO таблица выше:** verify-before-push 🔴NO-GO→✅GO.
**Остаётся (owner / follow-on):** G2 (манифест coverage/todowrite 10→12 — строкой в A2); owner-активация A-блок (ключ подписанта + `ROUTER_MENTOR_VERIFY_ENABLED=1` + регистрация хуков + увольнение зоопарка §10 + shadow→block + «пуш»); Level B (per-criterion + мутация P18 через готовый `runCriterionGate`).
@@ -0,0 +1,624 @@
# Расширение факторного анализа «мозга» — кандидаты раздела F
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Поднять сигналы новых защит router-gate v4 (rationalization-флаги, safe-baseline action, вердикт LLM-судьи, активность судьи) в эпизод журнала наблюдателя и добавить их как новые оси факторного анализа.
**Architecture:** Сигналы уже пишутся хуками на диск в `~/.claude/runtime/`, но (1) часть не логируется в нужной форме — добавляем точечную инструментовку (verdict-log, action-log); (2) парсер транскрипта их не читает — добавляем чистый модуль-ридер `observer-v4-signals.mjs`, который `parseTranscript` зовёт и кладёт результат в новые поля эпизода; (3) анализатор не раскладывает их — добавляем 4 оси в `FACTOR_FNS`. Принцип: эпизод остаётся единственным входом факторной матрицы (хуки → runtime-файлы → парсер → эпизод → `FACTOR_FNS`).
**Tech Stack:** Node ESM (`.mjs`), vitest (tools-only). Деньги — `bcmath`-free, float USD как в `cost-aggregator.mjs`.
**Канонический прогон тестов tools** (per memory `feedback_vitest_sentinel_recipe`): `npm run test:tools` сломан keytar-установкой в `app/node_modules` → запускать `npx vitest run --root app --config vitest.config.tools.mjs <путь>`.
**Базовые факты, на которых стоит план (сверены по коду 31.05.2026):**
- Эпизод собирается в `tools/observer-transcript-parser.mjs:888` (`parseTranscript`), окно хода — `turn = entries.slice(start)`, `started_at`/`ended_at` из `timestamps`.
- rationalization-флаги: `tools/enforce-hook-helpers.mjs:280` пишет `{ts, kind, evidence}` в `rationalization-flags-<sess>.jsonl`; читает `:291`.
- safe-baseline: `tools/enforce-safe-baseline-metering.mjs` пишет ledger `safe-baseline-ledger-<sess>.json` (`saveLedger:180`) и логирует ТОЛЬКО `soft_flag` в `safe-baseline-flags-<sess>.jsonl` (`logFlag:186`, строка `{ts, tool, reason}`); `hard_block` нигде не логируется. Ledger-state (`newCounterState` `safe-baseline-metering.mjs:60`) содержит `counts`/`skill_match_within_task`, но `warnings_issued`/`hard_blocks_issued` живой `runLiveDecision` НЕ заполняет.
- LLM-судья: `tools/enforce-llm-judge-per-tool.mjs:112` `runPerTool` зовёт `decide()` → вердикт `{block, verdict, degraded}`; бюджет `tools/llm-judge.mjs:121` `readJudgeBudget`/`:127` `bumpJudgeBudget` хранит `{calls}` в `llm-judge-budget-<sess>.json`; вердикт по-инструментно НЕ логируется. `JUDGE_SESSION_BUDGET = 200` (`llm-judge.mjs:67`).
- Цена: `tools/cost-pricing.mjs` `PRICING.sonnet = {input:3e-6, output:15e-6}`; агрегатор `tools/cost-aggregator.mjs:13` `episodeUsd` / `:40` `aggregateDay` (5 компонент + `total_usd` + `episode_count`); Stop-хук `tools/cost-stop-hook.mjs` пишет `cost-daily.json`.
- Факторные оси: `tools/brain-retro-analyzer.mjs:326` `FACTOR_FNS`; `analyze()` `:657` штампует `episode._inferredOutcome` и строит матрицу `buildFactorMatrix:384`.
---
## File Structure
| Файл | Ответственность | Действие |
|---|---|---|
| `tools/enforce-llm-judge-per-tool.mjs` | вызов судьи per-tool | Modify — логировать вердикт |
| `tools/enforce-hook-helpers.mjs` | общий I/O хуков | Modify — `logJudgeVerdict` + `readJudgeVerdicts` + `logSafeBaselineAction` + `readSafeBaselineActions` |
| `tools/enforce-safe-baseline-metering.mjs` | safe-baseline метеринг | Modify — логировать ЛЮБОЙ action (incl hard_block) |
| `tools/observer-v4-signals.mjs` | **новый** чистый ридер runtime-сигналов в окне хода | Create |
| `tools/observer-transcript-parser.mjs` | сборка эпизода | Modify — позвать ридер, добавить поля эпизода |
| `tools/cost-pricing.mjs` | ставки | Modify — `JUDGE_PER_CALL_USD` |
| `tools/cost-aggregator.mjs` | стоимость per-episode/day | Modify — компонент `judge_spend_usd` |
| `tools/brain-retro-analyzer.mjs` | факторные оси | Modify — 4 новые `FACTOR_FNS` |
| `docs/observer/brain-data-catalog.md` | каталог данных мозга | Modify — перенести сигналы из F в A/B/E |
| `tools/*.test.mjs` | тесты | Create/Modify per task |
**Новые поля эпизода (schema_minor bump 3 → 4):**
- `task_cost.judge_spend_usd` (number, default 0)
- `v4_signals.rationalization_flag_count` (int)
- `v4_signals.safe_baseline_action` (`allow`/`soft_flag`/`hard_block`/`null`)
- `v4_signals.judge_verdict` (`YES`/`NO`/`block`/`disabled`/`null`)
- `v4_signals.judge_calls` (int)
---
## Task 1: Логирование вердикта судьи
**Files:**
- Modify: `tools/enforce-hook-helpers.mjs` (после `readRationalizationFlags`, ~:299)
- Modify: `tools/enforce-llm-judge-per-tool.mjs` (`runPerTool`, :112)
- Test: `tools/enforce-hook-helpers.test.mjs`
- [ ] **Step 1: Написать падающий тест на `logJudgeVerdict`/`readJudgeVerdicts`**
```js
// tools/enforce-hook-helpers.test.mjs — добавить describe-блок
import { tmpdir } from 'node:os';
import { mkdtempSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { logJudgeVerdict, readJudgeVerdicts } from './enforce-hook-helpers.mjs';
describe('judge verdict log', () => {
it('appends and reads back verdict records', () => {
const dir = mkdtempSync(join(tmpdir(), 'jv-'));
logJudgeVerdict('sess1', { tool: 'Edit', verdict: 'YES' }, { baseDir: dir });
logJudgeVerdict('sess1', { tool: 'Bash', verdict: 'NO' }, { baseDir: dir });
const recs = readJudgeVerdicts('sess1', { baseDir: dir });
expect(recs.length).toBe(2);
expect(recs[0]).toMatchObject({ tool: 'Edit', verdict: 'YES' });
expect(typeof recs[0].ts).toBe('string');
rmSync(dir, { recursive: true, force: true });
});
it('returns [] when no file', () => {
expect(readJudgeVerdicts('nope', { baseDir: tmpdir() })).toEqual([]);
});
});
```
- [ ] **Step 2: Прогнать — убедиться, что падает**
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/enforce-hook-helpers.test.mjs`
Expected: FAIL — `logJudgeVerdict is not a function`.
- [ ] **Step 3: Реализовать функции в `enforce-hook-helpers.mjs`** (вставить после `readRationalizationFlags`, перед `readRouterState`)
```js
export function logJudgeVerdict(sessionId, { tool, verdict }, opts = {}) {
try {
const dir = opts.baseDir || runtimeDir();
const f = join(dir, `llm-judge-verdicts-${sessionId || 'unknown'}.jsonl`);
appendFileSync(f, JSON.stringify({
ts: new Date().toISOString(),
tool: tool || 'unknown',
verdict: verdict == null ? null : String(verdict),
}) + '\n');
} catch { /* ignore */ }
}
export function readJudgeVerdicts(sessionId, opts = {}) {
try {
const dir = opts.baseDir || runtimeDir();
const f = join(dir, `llm-judge-verdicts-${sessionId || 'unknown'}.jsonl`);
if (!existsSync(f)) return [];
return readFileSync(f, 'utf-8').split('\n').filter(Boolean).map((l) => {
try { return JSON.parse(l); } catch { return null; }
}).filter(Boolean);
} catch { return []; }
}
```
(`runtimeDir`, `appendFileSync`, `readFileSync`, `existsSync`, `join` уже импортированы в файле — проверить шапку, при отсутствии `existsSync` добавить в существующий `import { ... } from 'node:fs'`.)
- [ ] **Step 4: Прогнать тест — PASS**
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/enforce-hook-helpers.test.mjs`
Expected: PASS.
- [ ] **Step 5: Вызвать логгер из судьи.** В `enforce-llm-judge-per-tool.mjs:112` `runPerTool`, после строки `if (result.verdict !== undefined) bumpBudgetImpl({ sessionId, by: 1 });` добавить:
```js
if (result.verdict !== undefined) {
bumpBudgetImpl({ sessionId, by: 1 });
logVerdictImpl({ sessionId, tool: event && event.tool_name, verdict: result.block ? 'block' : (result.verdict ?? 'YES') });
}
```
В сигнатуру `runPerTool({...})` добавить параметр `logVerdictImpl = () => {}` (no-op по умолчанию — тесты не пишут на диск). В `main()` (:153) передать `logVerdictImpl: ({ sessionId, tool, verdict }) => logJudgeVerdict(sessionId, { tool, verdict })` и добавить `logJudgeVerdict` в импорт из `./enforce-hook-helpers.mjs`.
- [ ] **Step 6: Тест на проброс `logVerdictImpl`**
```js
// tools/enforce-llm-judge-per-tool.test.mjs — добавить
it('runPerTool logs verdict only when a real judge call happened', async () => {
const logged = [];
const r = await runPerTool({
event: { tool_name: 'Edit', session_id: 's', tool_input: {} },
judgeConfig: { enabled: true, apiKey: 'k' },
readDeclaredTaskImpl: () => ({ task_summary: 't' }),
readBudgetImpl: () => 0,
bumpBudgetImpl: () => {},
llmJudgeCallImpl: async () => 'YES',
logVerdictImpl: (rec) => logged.push(rec),
});
expect(r.block).toBe(false);
expect(logged).toEqual([{ sessionId: 's', tool: 'Edit', verdict: 'YES' }]);
});
```
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/enforce-llm-judge-per-tool.test.mjs`
Expected: PASS.
- [ ] **Step 7: Commit**
```bash
git add tools/enforce-hook-helpers.mjs tools/enforce-hook-helpers.test.mjs tools/enforce-llm-judge-per-tool.mjs tools/enforce-llm-judge-per-tool.test.mjs
git commit -m "feat(brain): log per-tool judge verdicts to runtime jsonl"
```
---
## Task 2: Логирование любого safe-baseline action (incl hard_block)
**Files:**
- Modify: `tools/enforce-hook-helpers.mjs`
- Modify: `tools/enforce-safe-baseline-metering.mjs` (`runMain`, :195)
- Test: `tools/enforce-hook-helpers.test.mjs`, `tools/enforce-safe-baseline-metering.test.mjs`
- [ ] **Step 1: Тест на `logSafeBaselineAction`/`readSafeBaselineActions`**
```js
// tools/enforce-hook-helpers.test.mjs
import { logSafeBaselineAction, readSafeBaselineActions } from './enforce-hook-helpers.mjs';
describe('safe-baseline action log', () => {
it('appends and reads action records', () => {
const dir = mkdtempSync(join(tmpdir(), 'sb-'));
logSafeBaselineAction('s', { tool: 'Edit', action: 'hard_block' }, { baseDir: dir });
const recs = readSafeBaselineActions('s', { baseDir: dir });
expect(recs[0]).toMatchObject({ tool: 'Edit', action: 'hard_block' });
rmSync(dir, { recursive: true, force: true });
});
});
```
- [ ] **Step 2: Прогнать — FAIL** (`logSafeBaselineAction is not a function`).
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/enforce-hook-helpers.test.mjs`
- [ ] **Step 3: Реализовать** (рядом с judge-логгером из Task 1)
```js
export function logSafeBaselineAction(sessionId, { tool, action }, opts = {}) {
try {
const dir = opts.baseDir || runtimeDir();
const f = join(dir, `safe-baseline-actions-${sessionId || 'unknown'}.jsonl`);
appendFileSync(f, JSON.stringify({
ts: new Date().toISOString(),
tool: tool || 'unknown',
action: String(action || 'allow'),
}) + '\n');
} catch { /* ignore */ }
}
export function readSafeBaselineActions(sessionId, opts = {}) {
try {
const dir = opts.baseDir || runtimeDir();
const f = join(dir, `safe-baseline-actions-${sessionId || 'unknown'}.jsonl`);
if (!existsSync(f)) return [];
return readFileSync(f, 'utf-8').split('\n').filter(Boolean).map((l) => {
try { return JSON.parse(l); } catch { return null; }
}).filter(Boolean);
} catch { return []; }
}
```
- [ ] **Step 4: Прогнать — PASS.**
- [ ] **Step 5: Вызвать из `enforce-safe-baseline-metering.mjs` `runMain`.** Заменить блок `if (res.action === 'soft_flag') logFlag(...)` на запись любого не-allow действия через новый логгер, сохранив старый `logFlag` для совместимости:
```js
if (res.action !== 'allow') {
logSafeBaselineAction(sess, { tool: event.tool_name, action: res.action });
if (res.action === 'soft_flag') logFlag(dir, sess, { tool: event.tool_name, reason: res.reason });
}
```
Добавить `import { logSafeBaselineAction } from './enforce-hook-helpers.mjs';` (или дополнить существующий импорт из helpers). NB: `logSafeBaselineAction` сам резолвит `runtimeDir()` — в проде это совпадает с `dir`; для теста `runMain` принимает `runtimeDir` override, но логгер пишет в реальный runtime — поэтому в тесте инъектировать override не обязательно (проверяем экспортируемый логгер отдельно в Step 1).
- [ ] **Step 6: Регрессия safe-baseline.**
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/enforce-safe-baseline-metering.test.mjs`
Expected: PASS (существующие тесты не трогают `action !== 'allow'` ветку с диском, либо обновить ожидания).
- [ ] **Step 7: Commit**
```bash
git add tools/enforce-hook-helpers.mjs tools/enforce-hook-helpers.test.mjs tools/enforce-safe-baseline-metering.mjs tools/enforce-safe-baseline-metering.test.mjs
git commit -m "feat(brain): log every safe-baseline action incl hard_block"
```
---
## Task 3: Чистый ридер сигналов в окне хода — `observer-v4-signals.mjs`
**Files:**
- Create: `tools/observer-v4-signals.mjs`
- Test: `tools/observer-v4-signals.test.mjs`
- [ ] **Step 1: Тест ридера**
```js
// tools/observer-v4-signals.test.mjs
import { describe, it, expect } from 'vitest';
import { tmpdir } from 'node:os';
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { extractV4Signals } from './observer-v4-signals.mjs';
function write(dir, name, lines) { writeFileSync(join(dir, name), lines.map((l) => JSON.stringify(l)).join('\n') + '\n'); }
describe('extractV4Signals', () => {
it('counts rationalization flags inside the turn window only', () => {
const dir = mkdtempSync(join(tmpdir(), 'v4-'));
write(dir, 'rationalization-flags-s.jsonl', [
{ ts: '2026-05-31T10:00:00.000Z', kind: 'x' }, // before window
{ ts: '2026-05-31T10:05:00.000Z', kind: 'y' }, // in
{ ts: '2026-05-31T10:06:00.000Z', kind: 'z' }, // in
]);
write(dir, 'llm-judge-verdicts-s.jsonl', [
{ ts: '2026-05-31T10:05:30.000Z', tool: 'Edit', verdict: 'YES' },
{ ts: '2026-05-31T10:05:40.000Z', tool: 'Bash', verdict: 'block' },
]);
write(dir, 'safe-baseline-actions-s.jsonl', [
{ ts: '2026-05-31T10:05:10.000Z', tool: 'Edit', action: 'soft_flag' },
{ ts: '2026-05-31T10:05:50.000Z', tool: 'Write', action: 'hard_block' },
]);
writeFileSync(join(dir, 'llm-judge-budget-s.json'), JSON.stringify({ calls: 7 }));
const sig = extractV4Signals('s', {
startMs: Date.parse('2026-05-31T10:04:00.000Z'),
endMs: Date.parse('2026-05-31T10:07:00.000Z'),
baseDir: dir,
});
expect(sig.rationalization_flag_count).toBe(2);
expect(sig.judge_verdict).toBe('block'); // last in-window verdict
expect(sig.safe_baseline_action).toBe('hard_block'); // worst in-window action
expect(sig.judge_calls).toBe(7);
rmSync(dir, { recursive: true, force: true });
});
it('returns zero/null defaults when files absent', () => {
const sig = extractV4Signals('nope', { startMs: 0, endMs: 1, baseDir: tmpdir() });
expect(sig).toEqual({ rationalization_flag_count: 0, judge_verdict: null, safe_baseline_action: null, judge_calls: 0 });
});
});
```
- [ ] **Step 2: Прогнать — FAIL** (модуль не существует).
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/observer-v4-signals.test.mjs`
- [ ] **Step 3: Реализовать `observer-v4-signals.mjs`**
```js
#!/usr/bin/env node
/**
* Pure reader of router-gate v4 runtime signals, scoped to one episode's
* [startMs, endMs] turn window. Feeds observer-transcript-parser (new
* episode.v4_signals block + task_cost.judge_spend_usd). No exec/exit.
*/
import { readFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { homedir } from 'node:os';
function rtDir(override) { return override || join(homedir(), '.claude', 'runtime'); }
function readJsonl(path) {
if (!existsSync(path)) return [];
try {
return readFileSync(path, 'utf-8').split('\n').filter(Boolean).map((l) => {
try { return JSON.parse(l); } catch { return null; }
}).filter(Boolean);
} catch { return []; }
}
function inWindow(rec, startMs, endMs) {
const ms = Date.parse(rec && rec.ts);
return Number.isFinite(ms) && ms >= startMs && ms <= endMs;
}
// soft order: hard_block > soft_flag > allow.
const ACTION_RANK = { allow: 0, soft_flag: 1, hard_block: 2 };
function worstAction(records) {
let best = null;
for (const r of records) {
const a = r && r.action;
if (a == null) continue;
if (best === null || (ACTION_RANK[a] ?? -1) > (ACTION_RANK[best] ?? -1)) best = a;
}
return best;
}
export function extractV4Signals(sessionId, { startMs, endMs, baseDir } = {}) {
const dir = rtDir(baseDir);
const sess = sessionId || 'unknown';
const flags = readJsonl(join(dir, `rationalization-flags-${sess}.jsonl`))
.filter((r) => inWindow(r, startMs, endMs));
const verdicts = readJsonl(join(dir, `llm-judge-verdicts-${sess}.jsonl`))
.filter((r) => inWindow(r, startMs, endMs));
const judge_verdict = verdicts.length ? (verdicts[verdicts.length - 1].verdict ?? null) : null;
const actions = readJsonl(join(dir, `safe-baseline-actions-${sess}.jsonl`))
.filter((r) => inWindow(r, startMs, endMs));
const safe_baseline_action = worstAction(actions);
let judge_calls = 0;
const bp = join(dir, `llm-judge-budget-${sess}.json`);
if (existsSync(bp)) {
try { judge_calls = Number(JSON.parse(readFileSync(bp, 'utf-8')).calls) || 0; } catch { /* keep 0 */ }
}
return {
rationalization_flag_count: flags.length,
judge_verdict,
safe_baseline_action,
judge_calls,
};
}
```
- [ ] **Step 4: Прогнать — PASS.**
- [ ] **Step 5: Commit**
```bash
git add tools/observer-v4-signals.mjs tools/observer-v4-signals.test.mjs
git commit -m "feat(brain): pure reader for v4 runtime signals in turn window"
```
---
## Task 4: Внести сигналы в эпизод (`parseTranscript`)
**Files:**
- Modify: `tools/observer-transcript-parser.mjs` (:888 return object)
- Test: `tools/observer-transcript-parser.test.mjs`
- [ ] **Step 1: Тест — эпизод несёт `v4_signals` и `task_cost.judge_spend_usd`**
```js
// tools/observer-transcript-parser.test.mjs — добавить
it('episode carries v4_signals block (default zero/null when no runtime files)', () => {
const ep = parseTranscript(JSON.stringify({
sessionId: 'no-runtime-sess',
message: { role: 'user', content: 'сделай фичу X' },
timestamp: '2026-05-31T10:00:00.000Z',
}), null, { routerStateBaseDir: tmpdir() });
expect(ep.v4_signals).toEqual({
rationalization_flag_count: 0, judge_verdict: null, safe_baseline_action: null, judge_calls: 0,
});
expect(typeof ep.task_cost.judge_spend_usd).toBe('number');
expect(ep.schema_minor).toBe(4);
});
```
(`tmpdir` импортировать в тест-файле, если ещё не.)
- [ ] **Step 2: Прогнать — FAIL** (`ep.v4_signals` undefined).
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/observer-transcript-parser.test.mjs`
- [ ] **Step 3: Реализовать.** В `observer-transcript-parser.mjs`:
(а) импорт вверху рядом с другими: `import { extractV4Signals } from './observer-v4-signals.mjs';` и `import { JUDGE_PER_CALL_USD } from './cost-pricing.mjs';`
(б) в `parseTranscript`, после вычисления `started_at`/`ended_at` (:847), добавить:
```js
const _v4 = extractV4Signals(sessionId, {
startMs: new Date(started_at).getTime(),
endMs: new Date(ended_at).getTime(),
baseDir: options.runtimeBaseDir || routerStateBaseDir,
});
```
(в) bump `schema_minor: 3``schema_minor: 4` в return.
(г) в return-объект добавить поле `v4_signals: _v4,`.
(д) в `task_cost` влить судейский spend: заменить строку `task_cost: { ...extractTokenUsage(turn), ...((routerState && routerState.task_cost) || {}) },` на:
```js
task_cost: {
...extractTokenUsage(turn),
...((routerState && routerState.task_cost) || {}),
judge_spend_usd: _v4.judge_calls * JUDGE_PER_CALL_USD,
},
```
NB: `routerStateBaseDir` уже извлекается из `options` (:838). `extractV4Signals` принимает тот же baseDir — в проде это `~/.claude/runtime`, в тестах — temp.
- [ ] **Step 4: Прогнать — PASS** (после Task 5, где появится `JUDGE_PER_CALL_USD`). Если выполняется до Task 5 — импорт `JUDGE_PER_CALL_USD` упадёт; **зависимость: Task 5 Step 3 делать до Task 4 Step 3** (или внести константу в этом же шаге). Для линейного исполнения: добавить `JUDGE_PER_CALL_USD` в `cost-pricing.mjs` здесь же (Step 3.е) и продублировать тест в Task 5.
(3.е) В `tools/cost-pricing.mjs` добавить экспорт:
```js
// Оценочная средняя стоимость одного вызова per-tool судьи (Sonnet, ~600 in / ~10 out
// токенов на короткий YES/NO-промпт). Уточнить, когда появится по-вызовный учёт токенов.
export const JUDGE_PER_CALL_USD = 600 * PRICING.sonnet.input + 10 * PRICING.sonnet.output;
```
- [ ] **Step 5: Прогнать целевой тест — PASS.**
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/observer-transcript-parser.test.mjs`
- [ ] **Step 6: Commit**
```bash
git add tools/observer-transcript-parser.mjs tools/observer-transcript-parser.test.mjs tools/cost-pricing.mjs
git commit -m "feat(brain): surface v4_signals + judge_spend_usd into episode (schema_minor 4)"
```
---
## Task 5: Денежный компонент `judge_spend_usd` в агрегаторе и cost-daily
**Files:**
- Modify: `tools/cost-aggregator.mjs` (`episodeUsd` :13, `aggregateDay` :40)
- Test: `tools/cost-aggregator.test.mjs`
- [ ] **Step 1: Тест — `episodeUsd` отдаёт `judge_spend_usd`, `total_usd` его включает**
```js
// tools/cost-aggregator.test.mjs
it('includes judge_spend_usd in episodeUsd + total', () => {
const pricing = { sonnet: { input: 3e-6, output: 15e-6 }, opus: { input: 15e-6, output: 75e-6 } };
const ep = { task_cost: { judge_spend_usd: 0.002 } };
const u = episodeUsd(ep, pricing);
expect(u.judge_spend_usd).toBe(0.002);
expect(u.total_usd).toBeCloseTo(0.002, 9);
});
```
- [ ] **Step 2: Прогнать — FAIL** (`u.judge_spend_usd` undefined).
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/cost-aggregator.test.mjs`
- [ ] **Step 3: Реализовать.** В `episodeUsd` (:13) добавить строку и включить в `total_usd` + объект:
```js
const judge_spend_usd = tc.judge_spend_usd || 0;
const total_usd =
classifier_usd + self_assessment_usd + reviewer_subagent_usd +
reviewer_direct_fallback_usd + self_retrospect_usd + judge_spend_usd;
return {
classifier_usd, self_assessment_usd, reviewer_subagent_usd,
reviewer_direct_fallback_usd, self_retrospect_usd, judge_spend_usd, total_usd,
};
```
В `aggregateDay` (:40) добавить `judge_spend_usd: 0` в `totals`, прибавлять `totals.judge_spend_usd += u.judge_spend_usd;` в цикле и включить в финальный `total_usd`.
- [ ] **Step 4: Прогнать — PASS** + регрессия `cost-stop-hook`:
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/cost-aggregator.test.mjs tools/cost-stop-hook.test.mjs`
Expected: PASS (cost-daily.json теперь получит поле `judge_spend_usd` автоматически — оно идёт из `aggregateDay`; при необходимости обновить ожидания в `cost-stop-hook.test.mjs`).
- [ ] **Step 5: Commit**
```bash
git add tools/cost-aggregator.mjs tools/cost-aggregator.test.mjs tools/cost-stop-hook.test.mjs
git commit -m "feat(brain): add judge_spend_usd cost component to aggregator + cost-daily"
```
---
## Task 6: Четыре новые факторные оси
**Files:**
- Modify: `tools/brain-retro-analyzer.mjs` (`FACTOR_FNS` :326)
- Test: `tools/brain-retro-analyzer.test.mjs`
- [ ] **Step 1: Тест — матрица содержит новые оси**
```js
// tools/brain-retro-analyzer.test.mjs
import { buildFactorMatrix } from './brain-retro-analyzer.mjs';
it('factor matrix exposes v4-signal axes', () => {
const eps = [
{ _inferredOutcome: 'success', v4_signals: { rationalization_flag_count: 0, judge_verdict: 'YES', safe_baseline_action: 'allow', judge_calls: 0 }, task_cost: { judge_spend_usd: 0 } },
{ _inferredOutcome: 'rework', v4_signals: { rationalization_flag_count: 3, judge_verdict: 'block', safe_baseline_action: 'hard_block', judge_calls: 12 }, task_cost: { judge_spend_usd: 0.03 } },
];
const m = buildFactorMatrix(eps);
expect(m.rationalization_flag_count['0']).toEqual({ success: 1 });
expect(m.rationalization_flag_count['2+']).toEqual({ rework: 1 });
expect(m.judge_verdict.YES).toEqual({ success: 1 });
expect(m.judge_verdict.block).toEqual({ rework: 1 });
expect(m.safe_baseline_action.hard_block).toEqual({ rework: 1 });
expect(m.judge_calls_bucket['0']).toEqual({ success: 1 });
expect(m.judge_calls_bucket['10+']).toEqual({ rework: 1 });
});
```
- [ ] **Step 2: Прогнать — FAIL** (осей нет).
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/brain-retro-analyzer.test.mjs`
- [ ] **Step 3: Реализовать.** В `FACTOR_FNS` (:326) добавить (используя существующий `countBucket012` :321 для 0/1/2+ и новый локальный bucket для judge_calls):
```js
// Pass 5 — router-gate v4 signal axes (brain-data-catalog раздел F → факторы).
rationalization_flag_count: (e) => countBucket012((e.v4_signals || {}).rationalization_flag_count),
judge_verdict: (e) => (e.v4_signals || {}).judge_verdict || 'null',
safe_baseline_action: (e) => (e.v4_signals || {}).safe_baseline_action || 'null',
judge_calls_bucket: (e) => judgeCallsBucket((e.v4_signals || {}).judge_calls),
```
И рядом с другими bucket-хелперами (около :321) добавить:
```js
function judgeCallsBucket(n) {
const v = Number(n) || 0;
if (v === 0) return '0';
if (v < 10) return '1-9';
return '10+';
}
```
- [ ] **Step 4: Прогнать — PASS** + полная регрессия анализатора:
Run: `npx vitest run --root app --config vitest.config.tools.mjs tools/brain-retro-analyzer.test.mjs`
Expected: PASS.
- [ ] **Step 5: Commit**
```bash
git add tools/brain-retro-analyzer.mjs tools/brain-retro-analyzer.test.mjs
git commit -m "feat(brain): add 4 v4-signal factor axes (rationalization/judge/safe-baseline)"
```
---
## Task 7: Обновить каталог данных + полная регрессия tools
**Files:**
- Modify: `docs/observer/brain-data-catalog.md`
- [ ] **Step 1: Перенести сигналы из раздела F.** В `brain-data-catalog.md`: в A.7 добавить `judge_spend_usd` в список `task_cost`; добавить новую группу **A.12 `v4_signals`** (`rationalization_flag_count`, `judge_verdict`, `safe_baseline_action`, `judge_calls`); в раздел B дописать «Pass 5 (4 оси): rationalization_flag_count / judge_verdict / safe_baseline_action / judge_calls_bucket»; в разделе F пометить F.1–F.3 как «✅ заведено в эпизод (см. A.12)», оставив F.4/F.5 как есть; bump «схема v4.3» → «v4.4 (schema_minor 4)».
- [ ] **Step 2: Полная регрессия tools-only**
Run: `npx vitest run --root app --config vitest.config.tools.mjs`
Expected: все GREEN (новые тесты + прежний baseline без падений).
- [ ] **Step 3: Commit**
```bash
git add docs/observer/brain-data-catalog.md
git commit -m "docs(observer): catalog — v4 signals promoted from F into episode + factors"
```
---
## Self-Review
**1. Покрытие требований (кандидаты F из каталога):**
- `rationalization_flag_count` → Task 3 (ридер) + Task 4 (поле) + Task 6 (ось). ✅
- `safe_baseline_action` → Task 2 (логирование) + Task 3 + Task 4 + Task 6. ✅
- `judge_verdict` → Task 1 (логирование) + Task 3 + Task 4 + Task 6. ✅
- `judge_spend_usd` → Task 4 (поле, из `judge_calls × JUDGE_PER_CALL_USD`) + Task 5 (агрегатор/cost-daily). ✅
- `judge_calls` → Task 3 (бюджет) + Task 6 (ось `judge_calls_bucket`). ✅
**2. Заглушки:** код в каждом шаге полный; команды и ожидания указаны. `JUDGE_PER_CALL_USD` — явная оценочная константа с комментарием (не TODO-заглушка), уточняемая, когда появится по-вызовный учёт токенов.
**3. Согласованность типов/имён:**
- `extractV4Signals(sessionId, {startMs,endMs,baseDir})` → возвращает `{rationalization_flag_count, judge_verdict, safe_baseline_action, judge_calls}` — те же ключи в Task 3 тесте, Task 4 поле `v4_signals`, Task 6 осях. ✅
- `logJudgeVerdict`/`readJudgeVerdicts`, `logSafeBaselineAction`/`readSafeBaselineActions` — пары имён согласованы между Task 1/2 и Task 3 (файлы `llm-judge-verdicts-<sess>.jsonl`, `safe-baseline-actions-<sess>.jsonl`). ✅
- `judge_spend_usd` — одно имя в Task 4 (запись), Task 5 (агрегатор), каталоге. ✅
- **Зависимость порядка:** Task 5 Step 3 (или Task 4 Step 3.е) должен внести `JUDGE_PER_CALL_USD` в `cost-pricing.mjs` ДО импорта в `observer-transcript-parser.mjs`. Зафиксировано в Task 4 Step 4 примечанием.
**4. Риски/допущения, проверить при исполнении:**
- `observer-transcript-parser` читает runtime по абсолютному `baseDir` (прод `~/.claude/runtime`); тесты инъектируют temp через `options.runtimeBaseDir || routerStateBaseDir`.
- `judge_calls` — кумулятивно per-session (бюджет не сбрасывается per-turn), поэтому `judge_calls_bucket` отражает нагрузку сессии, не хода; это осознанно (для USD это и нужно). Если потребуется per-turn — считать вердикты в окне (`verdicts.length`), отдельная ось.
- `enforce-llm-judge-per-tool` логирует вердикт только при реальном вызове (`result.verdict !== undefined`) — disabled/readonly/test-runner не пишут (verdict отсутствует) → `judge_verdict` останется `null`, что корректно отражает «судья не сработал».
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,374 @@
# Роутер-наставник — мастер-карта стройки (координирующий план)
> **Для исполнителя:** это **карта**, а не пошаговый чертёж. Она задаёт 7 машин, строгий порядок их сборки, зависимости и точки стыковки. Детальные TDD-планы (task-by-task, REQUIRED SUB-SKILL `superpowers:subagent-driven-development`) пишутся **отдельно по каждой машине** на основе этой карты — по одному, в порядке снизу вверх.
**Цель:** заменить нынешний «зоопарк» из ~12 живых блокеров одной цельной конструкцией «план → роутер-наставник → судья → владелец», где **ни одно действие не исполняется без шага замороженного плана, прошедшего судью**, при этом детерминированная защита-пол остаётся независимым слоем.
**Архитектура:** семь машин друг на друге. Низ — неподделываемый фундамент (журнал действий + подписанные расписки + защита секретов/служебки). Над ним — стена (верховный хук default-deny на ВСЁ) и замок плана (хеш-печать). Сбоку у стены — голова (роутер-наставник). На воротах — судья A–I (безопасный отказ, не переопределяет пол). По бокам — укрепление существующей защиты и переделка дисциплины. Сверху — видимость и поэтапное «тихое» включение (D28).
> **СТАТУС РЕВЬЮ 2026-06-04:** Машина 1 (Фундамент) — **✅ СОБРАНА и проверена по коду** (переделка не нужна, см. её раздел). Машина 2 — идёт построчное ревью решений A–G (владелец ведёт). **Закрыто:** A (структура шага), B (строгий матч), **C (C-1…C-14: два плана / две печати / закрытая дверь / контракты+охват / апелляция)** и **L-серия** (меньше итераций: L1/L3/L4/L5/L7; L2/L6 в утиль; принцип «умный не баран»). **Закрыто также:** D (матчер `*` + зелёный проход строго по способности — семена + только-смотрящие, никогда мутирующий; под аудитом дверей), E (указатель шага — линейный сейчас, «волны» → Машина 3), F (авто-аудит дверей + страховка «зелёного прохода» к D), G (сверка план↔журнал постфактум). **ВСЕ A–G + L закрыты — Машина 2 готова к плану сборки.** Детали дельты C/L — в разделе **«НАДСТРОЙКА C/L»** ниже + в чертеже Машины 2. Код Машин 2+ ещё НЕ строим — сначала владелец подтверждает D–G.
**Источники истины (spec):**
- `docs/superpowers/specs/2026-06-01-router-discipline-plan-anchor-design-notes.md` — логика план→роутер→судья→владелец (D1–D36, OQ1–OQ7, судья A–I, снаряжение роутера 6 групп / 24 пункта).
- `docs/superpowers/specs/2026-06-01-router-mentor-hooks-decisions.md` — хуки: 26-пунктная карта покрытия + два прохода усилений + «ПРЕД-СПЕКА #1 — чистовой список 🟥/🟩/🟨/🆕».
**Технологии:** Node.js хуки `tools/enforce-*.mjs` (PreToolUse/PostToolUse/Stop), Sonnet/Haiku/Opus как судьи/роутер через `callAnthropicAPI` (кэш промпта), `audit_chain_hash`-паттерн PostgreSQL для журнала, HMAC-ключ из OS-keychain (`keytar`), Pest 4 mutation для проверки тестов, vitest tools-only для регрессии хуков. **Портативность — жёсткое требование: никакого project-specific хардкода; судья/роутер РАССУЖДАЮТ, не читают зашитые списки.**
---
## СКВОЗНЫЕ НЕСУЩИЕ ПРИНЦИПЫ (держат всю конструкцию — закладываются в фундамент, используются всеми машинами)
1. **Журнал S1 — полный и неподделываемый.** Лог пишется **ДО** действия (нет записи → действие не исполняется, P21-h); append-only хеш-цепочка (как `audit_chain_hash`, стереть/править задним числом нельзя, P21-k); первичен над моими словами (что сделано — из журнала, мой отчёт сверяется, P21-e). Под него легли: честность (п.21), вход судьи (J7), результат субагента (п.13), сверка-аудит по сессии (п.25).
2. **Замороженный план (хеш-печать, P15-f).** После одобрения план «запечатан»; правка = новое одобрение, видимое владельцу. Без этого «в плане» — фикция (я переписал бы план под действие). Закрывает circularity «дисциплину собираю я сам».
3. **Судья НЕ переопределяет детерминированный пол (J9).** «Да» судьи не отпирает тонкий пол/верховный хук — судья только блокирует ИЛИ пропускает к детерм-слоям. Обманутый судья = ограниченный ущерб (не авторизует force-push/секрет/необратимое), не катастрофа.
4. **Машинная правда владельцу при одобрении (E10 / P4-b / J10).** Окно «да» показывает риск/последствие **машинно из фактов действия**, не из моей формулировки. Опасное нельзя подать мягкими словами. Против предела «обманули владельца».
5. **Подпись расписок ключом-вне-меня (P10-c, HMAC из OS-keychain).** Расписки судьи/владельца/GREEN подписаны; взлома служебной папки **мало** — нужен ключ, мне недоступный.
6. **Матчер-«всё» (`*`), не список имён.** Ворота судят по СУТИ действия, не по имени инструмента (урок F1: следили за Bash → PowerShell мимо). Новый канал → закрыт сам, пока осознанно не впишут. + авто-аудит покрытия дверей (P15-b).
7. **Портативность.** Что project-specific — берётся живьём из реального состояния, не вшивается. Зашитой «карты критического» НЕТ — судья рассуждает.
> **О счёте несущих (во избежание путаницы трёх разных счётчиков):** выше — **7 принципов** (высокий уровень). Конкретных несущих **механизмов**, которыми эти принципы держатся, — **10**; их поимённый список с привязкой к машинам — в коверидж-таблице «10 несущих механизмов → машина» внизу. Отдельно «**7 пределов**» (абзац ниже) — это про честный пол ~0.5% (предел защиты), а НЕ про несущие. Три счёта — **7 принципов / 10 механизмов / 7 пределов** — разные, не путать.
**Честный пол ~0.5% (7 пределов, неустраним по конструкции, заложен сознательно):** recursive-trust (я — соавтор плана и частично вход судьи) · user-approval (суверена можно обмануть, E10 сужает) · физ-канал без хуков (#15) · субъективная оценка качества · эфемерная негейтнутая мысль (#22) · cross-session дробление · info-theoretic утечка. Усиления подняли стены, пол не убрали.
**Режим внедрения (D28, общий для всех машин):** чертёж → сборка по TDD **в стороне, не вживую****тихий режим** (новые гейты логируют, что бы заблокировали/эскалировали, реально НЕ блокируют — **только дисциплинарные ворота; защита-пол (секреты/runtime/force-push) включена сразу, не «тихо»**) → подкрутка порогов по реальным данным → **включение по одному** (сперва мутации, потом чтения). Живая система во время стройки не ломается. Две сегодняшние починки кода (`max_tokens` 1500→15000, fix `task_type`) независимы — выкатываемы сразу.
---
## НАДСТРОЙКА C/L (ревью 2026-06-04) — что изменилось в конструкции
> Этот раздел — **актуальный слой поверх** детальных разделов машин ниже (они написаны ДО ревью C/L и остаются верны как фундамент; здесь — дельта). Закрыто на ревью: **A, B, C (C-1…C-14), L-серия, D** (матчер `*` + зелёный проход по способности), **E** (указатель шага линейный; «волны» → Машина 3), **F** (авто-аудит дверей + страховка «зелёного прохода»), **G** (сверка план↔журнал постфактум). **Все A–G + L закрыты 2026-06-04.**
**Главное изменение — план расщепился на ДВА (C-7):**
1. **Разговорный план** (думательный): цель + только семена (думать/спрашивать, реализация запрещена) + обязательный артефакт-результат и условие «готово». Последовательность эмерджентна (следующий вопрос рождается из ответа). Защита в этом окне — НЕ печать (её ещё нет), а запрет инструментов реализации + всегда-включённый пол (секреты/runtime/force-push). Один разговорный план на фазу, внутри — петля до готовности.
2. **Реализационный план** (строящий): фиксирован, опечатан заранее, строгий матч (A/B), каждый шаг ссылается на опечатанное решение артефакта.
**Две печати / два гейта судьи (C-10):**
- **Гейт-1** (после разговора) — судим ПРОДУКТ разговора: артефакт полон/здоров? Вход судьи = артефакт + контракты вызываемых скилов + цель (слеп к живой болтовне, артефакты-документы читать может). GO → печать артефакта → к плану стройки. Ловит «мусор на входе» РАНО.
- **Гейт-2** (после реализационного плана) — судим ПЛАН: верно применяет опечатанный артефакт, строгий, ссылки §X резолвятся? GO → печать плана → стройка.
- Асимметрия честная: план судим ДО (фиксирован), разговор — ПОСЛЕ (эмерджентен → судим продукт-артефакт).
**Закрытая дверь (C-5):** на стройке решать на ходу нельзя. (а) исполнение применяет ТОЛЬКО опечатанные решения; (б) шаг реализации ОБЯЗАН ссылаться на конкретное решение артефакта («крашу в цвет §X»), ссылка не резолвится → стоп; (в) нужно решение, которого нет → единственный легальный ход — возврат в разговор (семя) → пере-печать. Хода «спросил на ходу и продолжил» физически нет → петля обязательна.
**Контракт скила {needs/produces/constraints} (C-13) + L-поля (L-серия):** скил объявляет про себя машинно-читаемо. Базовый контракт `needs / produces / constraints`, расширен L-полями: `preview-form` (L1 — чем дёшево показать образец; `none` для мелочи), `defaults` (L3 — разумный дефолт под нужду, СВЕЖИЙ под задачу, не штамп), `key-decisions` (L4 — что фундаментально), `acceptance-criteria` (L7 — как поймём, что готово). На контракте проверки полноты/охвата = операции над множествами и графом (механика, не «ум ИИ»). Свои скилы — пишем контракт сами (точно); чужие (маркетплейс) — тонкий файл-адаптер рядом, гард-рейлы G1–G6, ключевой **G4: хеш-страж дрейфа** (версия+хеш чужого скила; расхождение → громкий флаг + откат на мягкое рассуждение судьи).
**Машина охвата A/B/C/D поверх контрактов (C-14):** A — граф зависимостей (узлы=скилы, рёбра=needs↔produces; декомпозиция=связные группы, порядок=топосортировка, дыра=нужда без producer, цикл=флаг); B — двусторонний реестр нужды↔решения (дыра / сирота-скоупкрип); C — чек-лист «просьбы цели → план» (мягкий край на извлечении просьб); D — ограничения как полноправные нужды. Хребет — буквальный чек-лист готовности (галочки + указатели §). Прецеденты прошлых планов — advisory, не гейт. + **look-ahead**: при сборке плана роутер читает контракты вызываемых скилов и тащит их вопросы ВПЕРЁД, в разговорную фазу.
**Апелляционный цикл (C-12):** деньги → сразу к владельцу; структурные замечания (нет §, ссылка не резолвится, нужда не покрыта, матч не сошёлся) → судья мотивированно назад ко мне → правлю (сначала сам, потом роутер) → роутер возвращает СУХОЕ (без мотиваций/диалогов) → судья пере-судит; **цикл до 3 раз, не прошло → к владельцу**; суть/дух → сразу к владельцу. Счётчик раундов неподделываемый (журнал М1), канал сухой (анти-инъекция), классификация «деньги» детерминированно-осторожная. Применяется на ОБОИХ гейтах.
**L-серия (меньше итераций, универсально):** L1 дешёвый образец до полной стройки (ОБЯЗАТЕЛЕН при `preview-form ≠ none`) · L3 предложи-и-вето (дефолт на вето вместо открытого вопроса) · L4 порядок решений по влиянию (фундамент раньше мелочи) · L5 ограничения-как-пре-мортем · L7 критерий приёмки заранее (объективное — авто-проверка, вкусовое — глаз владельца). Отклонены L2 (референсы, не универсально) и L6 (скелет-прецедент — «баранит», полноту лучше даёт граф A + look-ahead). **Принцип «умный не баран»:** половина ПЛАНИРОВАНИЯ/РАССУЖДЕНИЯ (роутер/разведка/декомпозиция) думает свежо каждую задачу, никаких шаблонов-решений; половина ЗАЩИТЫ/ПРИНУЖДЕНИЯ (стена/гейт/судья/пол) остаётся тупо-механической (тупой гейт нельзя уболтать).
**Куда садятся изменения (по машинам — детальные разделы ниже надстраиваются этим):**
- **Контракт скила** (новый сквозной артефакт) — общий вход для М3/М4; защищён от само-ослабления (убрал нужду/критерий/расширил дефолт → блок + да владельца + diff; анти-отрава #9/#11). Дом схемы — общий; чтение — М3/М4.
- **Машина 2 (стена):** ДВА режима — разговорный (только семена, артефакт обязателен, матча нет) + реализационный (строгий матч + закрытая дверь); ВТОРАЯ печать — заморозка артефакта (гейт-1), рядом с заморозкой плана; резолвер закрытой двери (шаг ↔ опечатанный §). Детально — в чертеже Машины 2 (Tasks 10–12, дефолты `[РЕШЕНИЕ 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`.
@@ -0,0 +1,103 @@
# Машина 3 — журнал вопросов и решений по ходу автономной сборки (2026-06-04, ночь)
> Владелец дал мандат: «делай полностью Машину 3; где упрёшься в вопрос — не останавливайся, запиши вопрос и делай дальше что можешь». Этот файл — все вопросы/допущения, которые возникли и которые НАДО показать владельцу утром. Каждое допущение помечено `[ДОПУЩЕНИЕ]`, каждый открытый вопрос — `[ВОПРОС]`.
## Контекст
- На чём стоит: Машина 1 (фундамент, собрана) + Машина 2 (стена/замок, собрана).
- Порядок: 3-A контракты скилов → 3-B граф узлов из реестра → 3-C машина охвата A/B/C/D → 3-D движок роутера (+K4 + волны) → 3-E очередь одобрений.
- Принцип: механика — чистый TDD (контроллер строит сам); рассуждение роутера — LLM, вызов мокается, проверки вокруг механические («умный не баран»). Портативность — без project-хардкода.
## Журнал
### 3-A (контракты скилов) — СОБРАНО
- `[ВОПРОС]` Гард-рейлы G1–G3/G5–G6 в спеке поимённо НЕ перечислены (назван только G4 — хеш-страж дрейфа). Реализован G4 + механические валидаторы формы. Остальные G — уточнить при сборке 3-C/3-D или у владельца.
- `[ДОПУЩЕНИЕ]` `PREVIEW_FORMS` = `none/outline/mockup/sample/dry-run/diagram` — выведен из духа L1, дословно в спеке не зафиксирован. Расширяемый.
- `[ДОПУЩЕНИЕ]` Контракты лежат `docs/registry/contracts/*.contract.json` рядом с `nodes.yaml`. Массовое наполнение контрактов для всех ~86 узлов — отдельная задача данных, НЕ код 3-A (собраны 2 образца: own writing-plans + external operations:process-doc).
- `[СТЫК]` Само-ослабление контракта (убрал нужду/критерий → блок + да владельца) — Машина 5, НЕ 3-A (только схема здесь).
### 3-B (граф узлов) — СОБРАНО
- `[ДОПУЩЕНИЕ]` Явных рёбер «близнецы/конфликты» в схеме реестра НЕТ. Близнецы выведены = общий `subcategory`; связи-подсказки = со-членство в `chains`; конфликты = опциональное `attributes.conflicts_with` (пусто, если не задано). Free-text `boundaries` НЕ парсим (ненадёжно). Явные конфликт-рёбра — наполнение позже.
- `[ДОПУЩЕНИЕ]` Члены цепочек вида `superpowers:brainstorming` могут не резолвиться в отдельный узел (суб-скилы superpowers не отдельные узлы) → в связях-подсказках пропускаются. Граф корректен для зарегистрированных узлов.
### 3-C (машина охвата) — СОБРАНО
- `[ДОПУЩЕНИЕ]` Сопоставление need↔produce — по нормализованной строке (lower/trim; равенство или подстрока для просьб). Семантическое сопоставление («spec»≈«требования») — рассуждение роутера (3-D), НЕ механика. Машина охвата честно ловит ТОЧНЫЕ дыры/сироты/циклы.
- `[ДОПУЩЕНИЕ]` Извлечение «просьб цели» из текста (C) — мягкий край, выполняется выше (роутер/владелец); 3-C принимает готовый массив просьб.
### 3-D (движок роутера) — границы автономной сборки (ВАЖНО для владельца)
- `[РЕШЕНИЕ — отложено до Машины 4] K4-поправка к стене (узкое Write-исключение в decideMode)` НЕ активируется в этой сессии. Канон M3 §1 прямо: «ВКЛЮЧАТЬ исключение можно только ПОСЛЕ того, как K1/M4 реальны». Машина 4 (гейт-1/печать) ещё НЕ построена → включишь сейчас = либо инертно, либо дыра (фальшивый артефакт некому отбраковать). Поэтому 3-D строит движок роутера, но `enforce-supreme-gate.decideMode` НЕ трогает. Активация K4 — отдельным шагом после Машины 4.
- `[РЕШЕНИЕ — отложено] Live-wiring роутера` (регистрация в settings.json + реальные вызовы Sonnet вместо моков) — шаг владельца (settings.json Claude'у закрыт) + режим D28 «сперва тихо, не вживую». 3-D строит и тестирует движок с МОКнутым llmCall (как router-classifier); живой вызов через `callAnthropicAPI` готов, но не регистрируется.
- `[РЕШЕНИЕ — отложено] Перенос «волн» (дерево указателей шага)` в живой `enforce-supreme-gate.main()` НЕ делается сейчас (трогает рабочий код Машины 2 + указатель пока линейный достаточен — на прямых планах). Построен СТЕНДОВЫЙ модуль дерева-указателя (мех., оттестирован), готов к замене линейного при живом роутере. Live-замена — отдельный шаг.
- `[ДОПУЩЕНИЕ] Нюх 5.3 / интервьюер 4.4 / самопроверка 5.4` — вшиты в system-инструкции брейнсторма роутера (рассуждение модели). Отдельные МЕХАНИЧЕСКИЕ детекторы «нюха на размытость» не строились жёстко (мягкий край, LLM-суждение). Механическая часть анти-обмана — валидатор трассы (пустой слот=флаг) + заземление (выдумка=отклонение).
### 3-E (очередь обучения) — СОБРАНО
- `[РЕШЕНИЕ — отложено] Доставка в живую инфру` — врезка `statusSignal` в `tools/status-md-generator.mjs` + раздела «На одобрение» (`renderApprovalSection`) в `.claude/skills/brain-retro/SKILL.md` НЕ делается тихо ночью (owner-facing surface, режим D28). Рендер/сигнал — готовые функции, ждут врезки с одобрения.
- `[ИНВАРИАНТ соблюдён] Hard-rule «наполнение только по да владельца, иначе никак»``enqueue` всегда pending; в фонд только `applyApprovalBatch` с явным approve; разведка rate=0 по умолчанию, проба=вопрос владельцу. Подтверждено инвариант-тестами.
---
## СВОДКА СБОРКИ МАШИНЫ 3 (2026-06-04, ночь) — ВСЕ 5 ПОД-ПЛАНОВ СОБРАНЫ
Порядок выдержан: 3-A → 3-B → 3-C → 3-D → 3-E. Всё по TDD (тест→RED→прод→GREEN), контроллер строил сам. Финальная регрессия tools-only **2212 GREEN / 2 skipped**. 14 коммитов на ветке `worktree-brainrepo` (НЕ запушено — ждёт «да» владельца на push).
| Под-план | Модули | Тесты | Коммиты |
|---|---|---|---|
| 3-A контракты | skill-contract.mjs + skill-contract-registry.mjs + 2 образца | 28 | f82cefae, a905abd1, 53db0ee2 |
| 3-B граф узлов | node-graph.mjs (поверх loadRegistry) | 20 | 750f406c, 699da97d |
| 3-C охват A/B/C/D | coverage-machine.mjs | 19 | 8df8d056, 80ebec9e |
| 3-D движок роутера | router-engine.mjs + step-pointer.mjs | 35 | 3a80bdde, 28b129ed, ed89028b |
| 3-E очередь обучения | router-learning-queue.mjs + router-exploration.mjs | 19 | 4cb17fc4, dcf772ba, a27a848d |
**FOLLOW-UP для владельца (НЕ сделано осознанно — нужны Машина 4 / шаг владельца):**
1. **Машина 4** (судья: гейт-1 K1 печать + гейт-2 K5 + апелляция + оценщик). Без неё K4-исключение, live-роутер, наполнение очереди — не активируются.
2. **K4-поправка к стене** (узкое Write-исключение в `decideMode`) — включить ПОСЛЕ Машины 4.
3. **Live-wiring роутера** — регистрация в settings.json (шаг владельца) + замена мокнутого llmCall на `callAnthropicAPI` + режим D28 «сперва тихо».
4. **Перенос волн** — заменить линейный `step_ptr` в `enforce-supreme-gate.main()` на `step-pointer` (дерево).
5. **Доставка 3-E** — врезать `statusSignal`/`renderApprovalSection` в STATUS-генератор + brain-retro SKILL.md.
6. **Наполнение контрактов** — написать контракты для остальных ~84 узлов реестра (данные, не код).
7. **Явные конфликт-рёбра** (`attributes.conflicts_with`) в реестре — наполнить (сейчас пусто → conflictsOf даёт []).
8. **Push** ветки `worktree-brainrepo` в origin — по «да».
---
## АУДИТ МАШИНЫ 3 против канона (2026-06-05, утро) — находки + фикс-сет
Адверсари-сверка по факту (построчно, спека §2/§3 + решение C-13). Механический фундамент 3-A/3-B/3-C и hard-rule 3-E — логика цела, багов в алгоритмах нет; 2212 тестов зелёные. Бреши — на стыке промпта роутера со спекой + один недостроенный гард-рейл контракта.
### Самопоправка
Утренняя пометка «G1–G6 не определены в спеке» — **СНЯТА как ошибочная**. G1–G6 полностью расписаны в решении **C-13** (`2026-06-04-router-mentor-m2-decisions-handoff.md`, стр. 116–121) — это шесть гард-рейлов адаптера ЧУЖИХ скилов. При сборке 3-A не сверился с источником.
### Расклад G1–G6 (адаптер чужих скилов)
- **G1** этикетка про сам скил, без проектных фактов — ⚠️ держится дисциплиной + нейтральной схемой; механической проверки нет → **фикс 5 (опц.)**.
- **G2** лениво (адаптер только для реально используемого) — ✅ соблюдено практикой (2 образца, не 86).
- **G3** детерминированная диспетчеризация (есть+свежий→точно; нет/устарел→мягко, молча не доверять) — ⚠️ **частично**: куски есть (`buildRegistry`+дрейф→`soft-reasoning`), единой функции «по id → точно|мягко» нет → **фикс 4**.
- **G4** хеш-страж дрейфа — ✅ построено (`checkContractDrift`).
- **G5** честная рамка (мягкость→одноразовое авторство+ярлык+страж) — ✅ соблюдено конструкцией.
- **G6** адаптер под журналом М1 + сверка судьёй — ⏸ ждёт Машину 1/4 (не зона 3-A).
### ФИКС-СЕТ (одобрено владельцем: блок А 1–4 + блок В 5)
1. **Заземление семенных навыков** (3-B/3-D, `resolveNode`): резолв по префиксу до `:` ПОСЛЕ суффикса → `superpowers:writing-plans`→узел `superpowers` #19. Чинит L1-цепочку (0 рёбер) + роутер (отклонял свои навыки). Суффикс-резолв сохраняется (`superpowers:architecture-patterns`#38).
2. **look-ahead + контракты в промпт роутера** (3-D, `buildRouterPrompt`+`runRouter`): принимать контракты, тащить needs/key-decisions вызываемых навыков вперёд. Канон §3.
3. **Нюх 5.3 + интервьюер 4.4 в текст промпта** (3-D): анти-обманная тройка (механически был только 5.2).
4. **G3-диспетчер** (3-A, `skill-contract-registry`): `dispatchContract(registry, skill)``{mode:'exact'|'soft-reasoning'}`, отсутствует/дрейф → soft.
5. **G1-страж** (3-A, `skill-contract`): `checkContractNeutrality(contract, {projectTerms})` — мягкая проверка против ИНЪЕЦИРУЕМОГО списка проектных терминов (портативно, без хардкода).
### Корректно отложено (не бреши)
K4-поправка / live-wiring / перенос волн / доставка 3-E / G6 / наполнение контрактов ~84 узлов / конфликт-рёбра — ждут Машину 4 или шаг владельца.
---
## ОТЛОЖЕНО ПО МАШИНЕ 3 — ИТОГОВАЯ СВОДКА (зафиксировано 2026-06-05, после фикс-сета 1–5)
Машина 3 собрана и проаудирована; фикс-сет 1–5 влит (коммиты `14230814``b94f7d24`, регрессия 2222 GREEN). **Сознательно отложено** (каждое — ждёт Машину 4 или явный шаг владельца, НЕ брешь):
1. **Машина 4 (судья)** — гейт-1 (печать артефакта K1) + гейт-2 (план K5) + апелляция + оценщик. Корень: без неё не активируются K4 / live-роутер / наполнение очереди.
2. **K4-поправка к стене** — узкое Write-исключение в `enforce-supreme-gate.decideMode`. Включать ТОЛЬКО после Машины 4 (канон M3 §1: фальшивый артефакт некому отбраковать до судьи).
3. **Live-wiring роутера** — регистрация в `.claude/settings.json` (шаг владельца) + замена мокнутого `llmCall` на `callAnthropicAPI` + режим D28 «сперва тихо».
4. **Перенос «волн»** — линейный `step_ptr` в `enforce-supreme-gate.main()` → дерево `step-pointer` (стендовый модуль готов, оттестирован).
5. **Доставка 3-E**`statusSignal` в `tools/status-md-generator.mjs` + `renderApprovalSection` в `.claude/skills/brain-retro/SKILL.md` (функции готовы, ждут врезки с одобрения).
6. **G6** (адаптер чужого скила под журналом М1 + сверка судьёй) — зона Машин 1/4.
7. **Наполнение контрактов** ~84 узлов реестра — данные, не код (собраны 2 образца).
8. **Явные конфликт-рёбра** `attributes.conflicts_with` — данные (сейчас пусто → `conflictsOf` даёт []).
9. **Push** ветки `worktree-brainrepo` в origin — по «да» владельца.
**Готово и НЕ требует возврата:** 3-A (схема+G1/G4-страж), 3-B (граф+заземление), 3-C (охват A/B/C/D), 3-D (движок+трасса+заземление+нюх/интервьюер/look-ahead), 3-E (очередь hard-rule+разведка rate=0).
@@ -0,0 +1,485 @@
# Машина 3 / под-план 3-A — Контракты скилов — план реализации
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax.
> **NB сборки (урок M2):** субагент-исполнитель НЕ проходит TDD-гейт (его правка теста невидима хуку) → код строит САМ контроллер по TDD (тест→RED→прод→GREEN в одном ходе).
**Goal:** дать машинно-читаемую схему контракта скила `{needs/produces/constraints + preview-form/defaults/key-decisions/acceptance-criteria}`, механический валидатор формы и G4 хеш-страж дрейфа чужих скилов — фундамент для машины охвата 3-C (рёбра графа needs↔produces) и входа роутера 3-D.
**Architecture:** чистое ядро `tools/skill-contract.mjs` (схема + валидатор + нормализация + accessors needs/produces + G4 хеш-страж) + загрузчик `tools/skill-contract-registry.mjs` (чистый `buildRegistry` над записями + `loadRegistry` с диска, fs инъектируется — паттерн проекта). Без LLM: полнота/тип слотов — операции над структурой, не «ум ИИ» («умный не баран»). Портативность: никакого project-хардкода имён скилов в коде — контракты лежат данными на диске.
**Tech Stack:** Node.js ESM, `node:crypto` (sha256 для G4), vitest tools-only. Образец дисциплины — `tools/plan-lock.mjs` (Машина 2).
---
## ⚠️ Контекст исполнения (как M1/M2)
- Worktree `brainrepo`, ветка `worktree-brainrepo`. Git — только `git -C "<worktree>"`, по одной команде.
- Тесты: `npx vitest run --root ".claude/worktrees/brainrepo/app" --config vitest.config.tools.mjs <фильтр>` с `dangerouslyDisableSandbox=true`, через Bash (гейт ищет Bash-vitest для RED).
- Тест-файлы — в `<worktree>\tools\` (конфиг резолвит `../tools/*.test.mjs`).
- TDD-гейт прод-файла требует в одном ходе: Read этого плана прямыми слэшами + правка теста + Bash-vitest RED, потом прод.
- Тест-файлы писать ЦЕЛИКОМ через Write (real-test-verifier блокирует голую правку импорта без `expect`).
## Границы 3-A (что НЕ здесь)
- Чтение контрактов в машину охвата A/B/C/D, look-ahead, L-ядро роутера → **3-D/3-C**.
- Защита контракта от само-ослабления (убрал нужду/критерий → блок + да владельца + diff, анти-отрава) → **Машина 5** (здесь только схема, не гейт).
- Массовое НАПИСАНИЕ контрактов для всех ~86 узлов → задача наполнения (данные), не код 3-A. Здесь — машинерия + 2 образца (own + external).
---
## Структура файлов
**Создаём:**
- `tools/skill-contract.mjs` — схема (`CONTRACT_FIELDS`, `PREVIEW_FORMS`), `validateContract`, `normalizeContract`, `needsOf`/`producesOf`, `contractHash`, `checkContractDrift` (G4).
- `tools/skill-contract-registry.mjs``buildRegistry` (чистый, над записями) + `loadRegistry` (диск, fs инъектируется).
- Тесты: `tools/skill-contract.test.mjs`, `tools/skill-contract-registry.test.mjs`.
- Образцы данных: `docs/registry/contracts/writing-plans.contract.json` (own), `docs/registry/contracts/operations-process-doc.contract.json` (external, с `source{version,hash,path}`).
**Порядок:** схема+валидатор (Task 1) → нормализация+accessors (Task 2) → G4 хеш-страж (Task 3) → buildRegistry (Task 4) → loadRegistry+диск (Task 5) → образцы+инварианты (Task 6).
---
## Task 1: Схема контракта + механический валидатор формы
**Files:** Create `tools/skill-contract.mjs`, `tools/skill-contract.test.mjs`
- [ ] **Step 1: Падающий тест**`tools/skill-contract.test.mjs`
```js
import { describe, it, expect } from 'vitest';
import { CONTRACT_FIELDS, PREVIEW_FORMS, validateContract } from './skill-contract.mjs';
const OWN = {
skill: 'writing-plans', kind: 'own',
needs: ['spec'], produces: ['implementation-plan'], constraints: ['no code'],
'preview-form': 'outline', defaults: ['bite-sized tasks'],
'key-decisions': ['file structure'], 'acceptance-criteria': ['each step 2-5 min'],
};
const EXT = {
skill: 'operations:process-doc', kind: 'external',
needs: ['as-is process'], produces: ['process doc'], constraints: [],
'preview-form': 'none', defaults: [], 'key-decisions': [], 'acceptance-criteria': ['doc covers all steps'],
source: { version: '1.2.0', hash: 'a'.repeat(64), path: 'x/SKILL.md' },
};
describe('CONTRACT_FIELDS / PREVIEW_FORMS', () => {
it('canonical fields present (C-13 + L-поля)', () => {
for (const f of ['skill','kind','needs','produces','constraints','preview-form','defaults','key-decisions','acceptance-criteria'])
expect(CONTRACT_FIELDS).toContain(f);
});
it('preview-form includes none', () => { expect(PREVIEW_FORMS).toContain('none'); });
});
describe('validateContract (форма, без LLM)', () => {
it('valid own contract → ok', () => { expect(validateContract(OWN)).toEqual({ ok: true, errors: [] }); });
it('valid external contract → ok', () => { expect(validateContract(EXT).ok).toBe(true); });
it('non-object → error', () => { expect(validateContract(null).ok).toBe(false); });
it('empty skill → error', () => { expect(validateContract({ ...OWN, skill: '' }).ok).toBe(false); });
it('bad kind → error', () => { expect(validateContract({ ...OWN, kind: 'maybe' }).ok).toBe(false); });
it('needs not string-array → error', () => { expect(validateContract({ ...OWN, needs: 'spec' }).ok).toBe(false); });
it('bad preview-form → error', () => { expect(validateContract({ ...OWN, 'preview-form': 'fancy' }).ok).toBe(false); });
it('external without source → error', () => {
const { source, ...noSrc } = EXT; expect(validateContract(noSrc).ok).toBe(false);
});
it('external with bad hash → error', () => {
expect(validateContract({ ...EXT, source: { version: '1', hash: 'short' } }).ok).toBe(false);
});
});
```
- [ ] **Step 2: RED**`npx vitest run --root ".claude/worktrees/brainrepo/app" --config vitest.config.tools.mjs skill-contract` → FAIL (import).
- [ ] **Step 3: Реализация**`tools/skill-contract.mjs`
```js
#!/usr/bin/env node
/**
* skill-contract — схема контракта скила (C-13 + L-серия) + механический
* валидатор формы + G4 хеш-страж дрейфа чужих скилов. Чистый модуль (без LLM):
* полнота/тип слотов — операции над структурой («умный не баран»). Фундамент
* для машины охвата 3-C (needs/produces = рёбра графа) и входа роутера 3-D.
*/
import { createHash } from 'node:crypto';
// Канонические поля (C-13 базовые needs/produces/constraints + L-поля).
export const CONTRACT_FIELDS = Object.freeze([
'skill', 'kind', 'needs', 'produces', 'constraints',
'preview-form', 'defaults', 'key-decisions', 'acceptance-criteria',
]);
// L1 preview-form — закрытый список форм дешёвого образца ('none' для мелочи).
export const PREVIEW_FORMS = Object.freeze(['none', 'outline', 'mockup', 'sample', 'dry-run', 'diagram']);
const ARRAY_FIELDS = Object.freeze(['needs', 'produces', 'constraints', 'defaults', 'key-decisions', 'acceptance-criteria']);
function isStringArray(v) { return Array.isArray(v) && v.every((x) => typeof x === 'string'); }
/**
* Механический валидатор контракта → {ok, errors[]}. Проверяет ФОРМУ (наличие/тип
* полей, enum preview-form, source{version,hash} у external), НЕ «полноту под
* задачу» (это машина охвата 3-C).
*/
export function validateContract(c) {
if (!c || typeof c !== 'object') return { ok: false, errors: ['contract is not an object'] };
const errors = [];
if (typeof c.skill !== 'string' || !c.skill.trim()) errors.push('skill: non-empty string required');
if (c.kind !== 'own' && c.kind !== 'external') errors.push("kind: must be 'own' or 'external'");
for (const f of ARRAY_FIELDS) if (!isStringArray(c[f])) errors.push(`${f}: array of strings required`);
if (!PREVIEW_FORMS.includes(c['preview-form'])) errors.push(`preview-form: one of ${PREVIEW_FORMS.join('|')}`);
if (c.kind === 'external') {
const s = c.source;
if (!s || typeof s !== 'object') errors.push('external: source{version,hash} required');
else {
if (typeof s.version !== 'string' || !s.version.trim()) errors.push('source.version: non-empty string required');
if (typeof s.hash !== 'string' || !/^[0-9a-f]{64}$/.test(s.hash)) errors.push('source.hash: sha256 hex required');
}
}
return { ok: errors.length === 0, errors };
}
```
- [ ] **Step 4: GREEN** — прогон → PASS.
- [ ] **Step 5: Commit**`git -C "<worktree>" add tools/skill-contract.mjs tools/skill-contract.test.mjs` затем `git -C "<worktree>" commit -m "feat(m3-a): skill-contract schema + mechanical form validator (C-13/L)"`
---
## Task 2: Нормализация + accessors needs/produces
**Files:** Modify `tools/skill-contract.mjs`, `tools/skill-contract.test.mjs`
- [ ] **Step 1: Падающие тесты (добавить)**
```js
import { normalizeContract, needsOf, producesOf } from './skill-contract.mjs';
describe('normalizeContract', () => {
it('missing arrays → [] and preview-form → none', () => {
const n = normalizeContract({ skill: ' x ', kind: 'own' });
expect(n.skill).toBe('x');
for (const f of ['needs','produces','constraints','defaults','key-decisions','acceptance-criteria']) expect(n[f]).toEqual([]);
expect(n['preview-form']).toBe('none');
});
it('does not mutate input', () => {
const raw = { skill: 'x', kind: 'own' };
normalizeContract(raw); expect(raw.needs).toBeUndefined();
});
});
describe('needsOf / producesOf', () => {
it('return copies of arrays', () => {
const c = { needs: ['a'], produces: ['b'] };
expect(needsOf(c)).toEqual(['a']); expect(producesOf(c)).toEqual(['b']);
needsOf(c).push('z'); expect(c.needs).toEqual(['a']);
});
it('missing → []', () => { expect(needsOf({})).toEqual([]); expect(producesOf({})).toEqual([]); });
});
```
- [ ] **Step 2: RED.**
- [ ] **Step 3: Реализация (дописать в `tools/skill-contract.mjs`)**
```js
/** Нормализация формы перед чтением/валидацией: отсутствующие массивы → [],
* preview-form → 'none', trim skill. Не «исправляет» невалидное — только форма. */
export function normalizeContract(raw) {
const c = { ...(raw || {}) };
if (typeof c.skill === 'string') c.skill = c.skill.trim();
for (const f of ARRAY_FIELDS) if (c[f] == null) c[f] = [];
if (c['preview-form'] == null) c['preview-form'] = 'none';
return c;
}
/** Нужды контракта (копия) — рёбра графа 3-C. */
export function needsOf(c) { return Array.isArray(c?.needs) ? [...c.needs] : []; }
/** Что производит (копия) — рёбра графа 3-C. */
export function producesOf(c) { return Array.isArray(c?.produces) ? [...c.produces] : []; }
```
- [ ] **Step 4: GREEN.**
- [ ] **Step 5: Commit**`git -C "<worktree>" commit -am "feat(m3-a): normalizeContract + needs/produces accessors"`
---
## Task 3: G4 — хеш-страж дрейфа чужого скила
**Files:** Modify `tools/skill-contract.mjs`, `tools/skill-contract.test.mjs`
- [ ] **Step 1: Падающие тесты (добавить)**
```js
import { contractHash, checkContractDrift } from './skill-contract.mjs';
describe('contractHash', () => {
it('sha256 hex, deterministic', () => {
expect(contractHash('abc')).toMatch(/^[0-9a-f]{64}$/);
expect(contractHash('abc')).toBe(contractHash('abc'));
expect(contractHash('abc')).not.toBe(contractHash('abd'));
});
});
describe('checkContractDrift (G4)', () => {
const ext = (hash) => ({ skill: 's', kind: 'external', source: { version: '1', hash } });
it('own contract → дрейф не сторожится (ok)', () => {
expect(checkContractDrift({ contract: { skill: 's', kind: 'own' }, currentContent: 'anything' }).ok).toBe(true);
});
it('external, hash совпал → ok, not drifted', () => {
const h = contractHash('SKILL body');
expect(checkContractDrift({ contract: ext(h), currentContent: 'SKILL body' })).toMatchObject({ ok: true, drifted: false });
});
it('external, содержание изменилось → drifted + fallback soft-reasoning', () => {
const h = contractHash('old body');
const r = checkContractDrift({ contract: ext(h), currentContent: 'NEW body' });
expect(r.drifted).toBe(true); expect(r.ok).toBe(false); expect(r.fallback).toBe('soft-reasoning');
});
it('external без сохранённого отпечатка → drifted (нельзя доверять)', () => {
const r = checkContractDrift({ contract: { skill: 's', kind: 'external', source: { version: '1' } }, currentContent: 'x' });
expect(r.drifted).toBe(true);
});
});
```
- [ ] **Step 2: RED.**
- [ ] **Step 3: Реализация (дописать)**
```js
/** sha256 содержания — отпечаток чужого скила для G4. */
export function contractHash(content) {
return createHash('sha256').update(String(content ?? '')).digest('hex');
}
/**
* G4 — хеш-страж дрейфа (C-13 ключевой гард-рейл): сверяет сохранённый отпечаток
* (contract.source.hash) с актуальным содержанием чужого SKILL.md. Расхождение →
* drifted=true + fallback 'soft-reasoning' (откат на мягкое рассуждение судьи).
* Своим (kind='own') не сторожим — контракт пишем сами.
*/
export function checkContractDrift({ contract, currentContent }) {
if (!contract || contract.kind !== 'external')
return { ok: true, drifted: false, reason: 'own/нет внешнего источника — дрейф не сторожится' };
const stored = contract.source?.hash;
const actual = contractHash(currentContent);
if (!stored) return { ok: false, drifted: true, reason: 'нет сохранённого отпечатка внешнего скила', fallback: 'soft-reasoning' };
if (stored !== actual)
return { ok: false, drifted: true, reason: `дрейф чужого скила: ${stored.slice(0, 12)}… ≠ ${actual.slice(0, 12)}…`, fallback: 'soft-reasoning' };
return { ok: true, drifted: false, reason: 'отпечаток совпал' };
}
```
- [ ] **Step 4: GREEN.**
- [ ] **Step 5: Commit**`git -C "<worktree>" commit -am "feat(m3-a): G4 drift-guard (contractHash + checkContractDrift)"`
---
## Task 4: buildRegistry — чистая сборка набора контрактов
**Files:** Create `tools/skill-contract-registry.mjs`, `tools/skill-contract-registry.test.mjs`
- [ ] **Step 1: Падающий тест**`tools/skill-contract-registry.test.mjs`
```js
import { describe, it, expect } from 'vitest';
import { buildRegistry } from './skill-contract-registry.mjs';
const own = (skill) => ({ skill, kind: 'own', needs: [], produces: [], constraints: [], 'preview-form': 'none', defaults: [], 'key-decisions': [], 'acceptance-criteria': [] });
describe('buildRegistry (чистый)', () => {
it('валидные контракты собираются', () => {
const r = buildRegistry([{ contract: own('a') }, { contract: own('b') }]);
expect(r.contracts).toHaveLength(2); expect(r.errors).toEqual([]); expect(r.driftFlags).toEqual([]);
});
it('невалидный контракт → в errors, не в contracts', () => {
const r = buildRegistry([{ contract: { skill: '', kind: 'own' } }]);
expect(r.contracts).toHaveLength(0); expect(r.errors).toHaveLength(1);
});
it('дубль skill → ошибка, первый остаётся', () => {
const r = buildRegistry([{ contract: own('a') }, { contract: own('a') }]);
expect(r.contracts).toHaveLength(1); expect(r.errors[0].errors[0]).toMatch(/duplicate/);
});
it('external дрейф → в driftFlags, контракт всё равно загружен (с пометкой)', () => {
const ext = { skill: 'e', kind: 'external', needs: [], produces: [], constraints: [], 'preview-form': 'none', defaults: [], 'key-decisions': [], 'acceptance-criteria': [], source: { version: '1', hash: 'f'.repeat(64) } };
const r = buildRegistry([{ contract: ext, currentContent: 'changed body' }]);
expect(r.contracts).toHaveLength(1); expect(r.driftFlags).toHaveLength(1); expect(r.driftFlags[0].skill).toBe('e');
});
});
```
- [ ] **Step 2: RED.**
- [ ] **Step 3: Реализация**`tools/skill-contract-registry.mjs`
```js
#!/usr/bin/env node
/**
* skill-contract-registry — сборка набора контрактов скилов. Чистый buildRegistry
* (над записями {contract, currentContent?}) + loadRegistry (диск, fs инъектируется).
* Валидирует каждый, для external сверяет дрейф G4. Вход для машины охвата 3-C.
*/
import fsDefault from 'node:fs';
import { validateContract, normalizeContract, checkContractDrift } from './skill-contract.mjs';
/** Чистая сборка: валидирует, ловит дубли, помечает дрейф external. */
export function buildRegistry(entries) {
const contracts = [], errors = [], driftFlags = [], seen = new Set();
for (const e of entries || []) {
const c = normalizeContract(e.contract);
const v = validateContract(c);
if (!v.ok) { errors.push({ skill: c.skill || '(?)', errors: v.errors }); continue; }
if (seen.has(c.skill)) { errors.push({ skill: c.skill, errors: ['duplicate skill contract'] }); continue; }
seen.add(c.skill);
if (c.kind === 'external') {
const d = checkContractDrift({ contract: c, currentContent: e.currentContent });
if (d.drifted) driftFlags.push({ skill: c.skill, reason: d.reason, fallback: d.fallback });
}
contracts.push(c);
}
return { contracts, errors, driftFlags };
}
```
- [ ] **Step 4: GREEN.**
- [ ] **Step 5: Commit**`git -C "<worktree>" add tools/skill-contract-registry.mjs tools/skill-contract-registry.test.mjs` затем commit `"feat(m3-a): buildRegistry — validate + dedupe + drift-flag"`
---
## Task 5: loadRegistry — загрузка с диска (fs инъектируется)
**Files:** Modify `tools/skill-contract-registry.mjs`, `tools/skill-contract-registry.test.mjs`
- [ ] **Step 1: Падающие тесты (добавить)**
```js
import { loadRegistry } from './skill-contract-registry.mjs';
function memFs(map) {
return {
readdirSync: () => Object.keys(map).map((p) => p.split('/').pop()),
readFileSync: (p) => { const k = String(p); if (!(k in map)) { const e = new Error('ENOENT'); e.code='ENOENT'; throw e; } return map[k]; },
};
}
describe('loadRegistry (диск, fs инъектируется)', () => {
it('читает *.contract.json, валидирует, external тянет source.path для дрейфа', () => {
const ownC = JSON.stringify({ skill: 'wp', kind: 'own', needs: [], produces: [], constraints: [], 'preview-form': 'none', defaults: [], 'key-decisions': [], 'acceptance-criteria': [] });
const extC = JSON.stringify({ skill: 'pd', kind: 'external', needs: [], produces: [], constraints: [], 'preview-form': 'none', defaults: [], 'key-decisions': [], 'acceptance-criteria': [], source: { version: '1', hash: '0'.repeat(64), path: '/skills/pd/SKILL.md' } });
const map = { '/c/wp.contract.json': ownC, '/c/pd.contract.json': extC, '/skills/pd/SKILL.md': 'current body' };
const r = loadRegistry({ dir: '/c', fsImpl: memFs(map) });
expect(r.contracts).toHaveLength(2);
expect(r.driftFlags).toHaveLength(1); // hash 0..0 ≠ хеш 'current body'
});
it('игнорирует не-.contract.json', () => {
const map = { '/c/readme.md': 'x' };
const r = loadRegistry({ dir: '/c', fsImpl: memFs(map) });
expect(r.contracts).toEqual([]); expect(r.errors).toEqual([]);
});
});
```
> **NB по memFs:** `readdirSync` отдаёт basename'ы, `readFileSync` ждёт полный путь `${dir}/${file}`. В `loadRegistry` собираем путь как `` `${dir}/${f}` ``.
- [ ] **Step 2: RED.**
- [ ] **Step 3: Реализация (дописать в `tools/skill-contract-registry.mjs`)**
```js
/** Загрузка с диска: dir/*.contract.json. Для external читает source.path
* (актуальный SKILL.md) для дрейф-сверки G4, если путь задан и доступен. */
export function loadRegistry({ dir, fsImpl = fsDefault }) {
const files = fsImpl.readdirSync(dir).filter((f) => f.endsWith('.contract.json'));
const entries = files.map((f) => {
const raw = JSON.parse(fsImpl.readFileSync(`${dir}/${f}`, 'utf8'));
let currentContent;
if (raw && raw.kind === 'external' && raw.source && raw.source.path) {
try { currentContent = fsImpl.readFileSync(raw.source.path, 'utf8'); } catch { currentContent = undefined; }
}
return { contract: raw, currentContent };
});
return buildRegistry(entries);
}
```
- [ ] **Step 4: GREEN.**
- [ ] **Step 5: Commit**`git -C "<worktree>" commit -am "feat(m3-a): loadRegistry — disk loader, external pulls source.path for G4"`
---
## Task 6: Образцы контрактов + инварианты 3-A + регрессия
**Files:** Create `docs/registry/contracts/writing-plans.contract.json`, `docs/registry/contracts/operations-process-doc.contract.json`, `tools/m3a-contract-invariants.test.mjs`
- [ ] **Step 1: Образец own**`docs/registry/contracts/writing-plans.contract.json`
```json
{
"skill": "writing-plans",
"kind": "own",
"needs": ["spec or requirements", "file structure decisions"],
"produces": ["implementation-plan with bite-sized TDD tasks"],
"constraints": ["only writes the plan file", "no code, no reads beyond plan authoring"],
"preview-form": "outline",
"defaults": ["one action per step (2-5 min)", "test->RED->code->GREEN->commit"],
"key-decisions": ["file structure / decomposition", "task granularity"],
"acceptance-criteria": ["every step has concrete content (no placeholders)", "types consistent across tasks"]
}
```
- [ ] **Step 2: Образец external**`docs/registry/contracts/operations-process-doc.contract.json`
```json
{
"skill": "operations:process-doc",
"kind": "external",
"needs": ["as-is process description"],
"produces": ["structured process documentation"],
"constraints": ["marketplace skill — outputs doc only"],
"preview-form": "none",
"defaults": ["follow operations plugin process-doc template"],
"key-decisions": ["scope of the process being documented"],
"acceptance-criteria": ["doc covers all process steps and owners"],
"source": { "version": "1.2.0", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
```
> **NB:** `hash` образца — плейсхолдер нулями + `path: ""` (пустой → дрейф-сверка пропускается, `currentContent` undefined → external без отпечатка-сверки даст «нет содержания»: при пустом path loadRegistry не читает файл, buildRegistry получает `currentContent: undefined``contractHash(undefined)` ≠ нули → дрейф-флаг). Чтобы образец грузился без шумного дрейф-флага в инварианте, в тесте подаём его через `buildRegistry` с `currentContent`, чей хеш = нули НЕ совпадёт — поэтому инвариант проверяет, что образец ВАЛИДЕН по форме (а не отсутствие дрейфа). Реальный отпечаток проставляется при наполнении (вне 3-A).
- [ ] **Step 3: Инвариант-тест**`tools/m3a-contract-invariants.test.mjs`
```js
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { validateContract } from './skill-contract.mjs';
const here = dirname(fileURLToPath(import.meta.url));
const contractsDir = join(here, '..', 'docs', 'registry', 'contracts');
describe('Машина 3-A — инварианты контрактов', () => {
it('образец own (writing-plans) валиден по форме', () => {
const c = JSON.parse(readFileSync(join(contractsDir, 'writing-plans.contract.json'), 'utf8'));
expect(validateContract(c)).toEqual({ ok: true, errors: [] });
});
it('образец external (operations:process-doc) валиден по форме', () => {
const c = JSON.parse(readFileSync(join(contractsDir, 'operations-process-doc.contract.json'), 'utf8'));
expect(validateContract(c).ok).toBe(true);
});
});
```
> **NB путь:** тест-файл лежит в `<worktree>/tools/`, контракты — в `<worktree>/docs/registry/contracts/`. `join(here, '..', 'docs', ...)` поднимается из `tools/` в корень worktree. Проверить фактический `here` при RED; если конфиг резолвит из другого корня — поправить относительный путь (записать в журнал, если разойдётся).
- [ ] **Step 4: GREEN**`npx vitest run --root ".claude/worktrees/brainrepo/app" --config vitest.config.tools.mjs m3a-contract-invariants`
- [ ] **Step 5: Полная регрессия 3-A + всё**`npx vitest run --root ".claude/worktrees/brainrepo/app" --config vitest.config.tools.mjs` → все прежние + M1 + M2 + 3-A зелёные.
- [ ] **Step 6: Commit**`git -C "<worktree>" add docs/registry/contracts/ tools/m3a-contract-invariants.test.mjs` затем commit `"test(m3-a): contract sample fixtures (own+external) + 3-A invariants"`
---
## Self-Review (против канона §2 3-A + roadmap C-13)
- **Схема `{needs/produces/constraints + preview-form/defaults/key-decisions/acceptance-criteria}`** → Task 1 `CONTRACT_FIELDS` + `validateContract`. ✅
- **Свои контракты пишем сами** → формат own + образец writing-plans (Task 6). ✅
- **Чужие — тонкий адаптер + G4 хеш-страж дрейфа (версия+хеш, расхождение → флаг + откат на мягкое рассуждение)** → Task 3 `checkContractDrift` (`fallback: 'soft-reasoning'`) + external `source{version,hash,path}` + образец external (Task 6). ✅
- **Фундамент для машины охвата 3-C**`needsOf`/`producesOf` (Task 2) + `buildRegistry`/`loadRegistry` (Task 4/5) дают набор контрактов с рёбрами. ✅
- **Без LLM, «умный не баран»** → весь модуль чистый, операции над структурой. ✅
- **Портативность** → имена скилов не зашиты в код, лежат данными в `docs/registry/contracts/`. ✅
- **[ВОПРОС записан в журнал]** G1–G3/G5–G6 в спеке поимённо НЕ перечислены (назван только G4) → реализован G4 + механические валидаторы формы; остальные гард-рейлы уточнить у владельца/при сборке 3-C.
- **[ДОПУЩЕНИЕ]** PREVIEW_FORMS список (`none/outline/mockup/sample/dry-run/diagram`) — выведен из духа L1, не зафиксирован дословно в спеке → расширяемый.
- **[ДОПУЩЕНИЕ]** контракты хранятся как `docs/registry/contracts/*.contract.json` рядом с `docs/registry/nodes.yaml` → согласовать с 3-B (граф узлов из реестра).
**Заглушек нет. Само-ослабление контракта (M5) и массовое наполнение — вне 3-A (отмечено).**
@@ -0,0 +1,339 @@
# Машина 3 / под-план 3-B — Граф узлов из реестра — план реализации
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development / executing-plans. Steps — checkbox.
> **NB сборки:** код строит контроллер по TDD (субагент не проходит TDD-гейт).
**Goal:** дать роутеру (3-D) и судье (М4) СТАБИЛЬНУЮ карту инструментов из РЕЕСТРА (`docs/registry/nodes.yaml`, НЕ graphify): узлы = скилы/MCP/агенты, рёбра-подсказки = близнецы (общий subcategory) / связи (со-членство в цепочке) / конфликты (явные) + механическое заземление скила в реальный узел (ОВ-Д2) + чувство свежести (3.6).
**Architecture:** чистый `tools/node-graph.mjs` поверх существующего `tools/registry-load.mjs` (`loadRegistry``{nodes, chains, indexById}`; js-yaml+ajv уже в проекте). Граф НЕ парсит YAML сам — потребляет `registry`-объект. Рёбра — ПОДСКАЗКИ (не рецепты, ОВ-Д1): близнецы/связи/конфликты выводятся механически из существующих полей (subcategory / chains / attributes.conflicts_with). Резолв скила в узел — детерминированный (id/slug/name/suffix; выдумка → null). Без LLM («умный не баран»).
**Tech Stack:** Node.js ESM, vitest tools-only. Реюз `loadRegistry` (registry-load.mjs).
---
## ⚠️ Контекст исполнения (как 3-A)
- Git только `git -C "<worktree>"`. Тесты: `npx vitest run --root ".claude/worktrees/brainrepo/app" --config vitest.config.tools.mjs <фильтр>` через Bash, `dangerouslyDisableSandbox=true`, без `cd` (router-gate режет цепочки).
- Тест-файлы — в `<worktree>\tools\`, писать ЦЕЛИКОМ через Write.
- TDD-гейт: Read этого плана прямыми слэшами + правка теста + Bash-RED, потом прод.
## Границы 3-B (что НЕ здесь)
- Машина охвата A/B/C/D (граф нужд needs↔produces из КОНТРАКТОВ 3-A) → **3-C**. NB: 3-B — граф УЗЛОВ (инструменты+связи-подсказки), 3-C — граф НУЖД (декомпозиция по контрактам). Разные графы.
- Выбор скила рассуждением, look-ahead, L-ядро → **3-D**.
- **[ДОПУЩЕНИЕ]** Явных рёбер «близнецы/альтернативы/конфликты» в схеме реестра НЕТ. Выводим: близнецы = общий `subcategory`; связи-подсказки = со-членство в `chains`; конфликты = опциональное `attributes.conflicts_with` (массив ref'ов; пусто, если не задано). Фаззи-парсинг free-text `boundaries` НЕ делаем (ненадёжно). Явные конфликт-рёбра — наполнение позже (вне 3-B).
- **[ДОПУЩЕНИЕ]** Члены цепочек вида `superpowers:brainstorming` могут не резолвиться в отдельный узел (если суб-скилы superpowers не отдельные узлы реестра) → в связях-подсказках такие члены пропускаются. Граф корректен для зарегистрированных узлов.
---
## Структура файлов
**Создаём:**
- `tools/node-graph.mjs``buildNodeGraph(registry)`, `resolveNode(graph, ref)` (ОВ-Д2), `twinsOf`, `hintLinksOf`, `conflictsOf`, `checkGraphFreshness` (3.6).
- Тесты: `tools/node-graph.test.mjs` (чистый, на fake-registry), `tools/m3b-node-graph-invariants.test.mjs` (на РЕАЛЬНОМ реестре через loadRegistry).
**Порядок:** buildNodeGraph+resolveNode (Task 1) → twins/hints/conflicts (Task 2) → freshness 3.6 (Task 3) → инварианты на реальном реестре + регрессия (Task 4).
---
## Task 1: buildNodeGraph + resolveNode (ОВ-Д2 заземление)
**Files:** Create `tools/node-graph.mjs`, `tools/node-graph.test.mjs`
- [ ] **Step 1: Падающий тест**`tools/node-graph.test.mjs`
```js
import { describe, it, expect } from 'vitest';
import { buildNodeGraph, resolveNode } from './node-graph.mjs';
// fake-registry в форме loadRegistry (nodes + chains)
const REG = {
nodes: [
{ id: '#19', name: 'Superpowers', slug: 'superpowers', subcategory: null, status: 'active' },
{ id: '#36', name: 'adr-kit', slug: 'adr-kit', subcategory: 'architecture-tooling', status: 'active' },
{ id: '#37', name: 'mermaid-skill', slug: 'mermaid', subcategory: 'architecture-tooling', status: 'active' },
{ id: '#38', name: 'architecture-patterns', slug: 'architecture-patterns', subcategory: 'architecture-tooling', status: 'active' },
{ id: '#17', name: 'pg_partman', slug: 'pg-partman', subcategory: null, status: 'dormant' },
],
chains: {
L4: { name: 'diagram', sequence: ['#36', '#37'] },
},
};
describe('buildNodeGraph', () => {
it('builds id/slug/name indexes + node count', () => {
const g = buildNodeGraph(REG);
expect(g.nodes).toHaveLength(5);
expect(g.byId.get('#36').name).toBe('adr-kit');
expect(g.bySlug.get('mermaid').id).toBe('#37');
});
});
describe('resolveNode (ОВ-Д2 — механическое заземление)', () => {
const g = buildNodeGraph(REG);
it('resolves by exact id', () => { expect(resolveNode(g, '#36').slug).toBe('adr-kit'); });
it('resolves by exact slug', () => { expect(resolveNode(g, 'mermaid').id).toBe('#37'); });
it('resolves by exact name', () => { expect(resolveNode(g, 'adr-kit').id).toBe('#36'); });
it('resolves by suffix after colon (skill-ref)', () => {
expect(resolveNode(g, 'superpowers:architecture-patterns').id).toBe('#38');
});
it('invented skill → null (выдумка отклоняется)', () => {
expect(resolveNode(g, 'elasticsearch-mcp')).toBe(null);
expect(resolveNode(g, '#999')).toBe(null);
});
it('empty/garbage → null', () => {
expect(resolveNode(g, '')).toBe(null);
expect(resolveNode(g, null)).toBe(null);
});
});
```
- [ ] **Step 2: RED**`... skill ... node-graph` → FAIL (import).
- [ ] **Step 3: Реализация**`tools/node-graph.mjs`
```js
#!/usr/bin/env node
/**
* node-graph — стабильный граф УЗЛОВ из реестра (3.1/3.2/3.3 + 3.6). Узлы =
* скилы/MCP/агенты из docs/registry/nodes.yaml; рёбра-ПОДСКАЗКИ (не рецепты):
* близнецы (общий subcategory) / связи (со-членство в chains) / конфликты
* (явные attributes.conflicts_with). Резолв скила в узел детерминированный
* (ОВ-Д2: выдумка → null). Без LLM. Потребляет registry от loadRegistry.
*/
/** Построить граф из registry ({nodes, chains}). Индексы id/slug/name + субкатегории + цепочки. */
export function buildNodeGraph(registry) {
const nodes = (registry && registry.nodes) || [];
const chains = (registry && registry.chains) || {};
const byId = new Map(), bySlug = new Map(), byName = new Map();
const subcategoryIndex = new Map();
for (const n of nodes) {
if (n.id) byId.set(String(n.id), n);
if (n.slug) bySlug.set(String(n.slug).toLowerCase(), n);
if (n.name) byName.set(String(n.name).toLowerCase(), n);
if (n.subcategory) {
if (!subcategoryIndex.has(n.subcategory)) subcategoryIndex.set(n.subcategory, []);
subcategoryIndex.get(n.subcategory).push(n);
}
}
return { nodes, chains, byId, bySlug, byName, subcategoryIndex };
}
/**
* ОВ-Д2 — механический резолв ссылки в реальный узел: id (#NN) → slug → name →
* суффикс после ':' (skill-ref как superpowers:brainstorming) как slug/name.
* Не нашли → null (выдумка отклоняется, не выдаём догадку за факт).
*/
export function resolveNode(graph, ref) {
if (!graph || typeof ref !== 'string') return null;
const r = ref.trim();
if (!r) return null;
if (graph.byId.has(r)) return graph.byId.get(r);
const low = r.toLowerCase();
if (graph.bySlug.has(low)) return graph.bySlug.get(low);
if (graph.byName.has(low)) return graph.byName.get(low);
if (r.includes(':')) {
const suf = low.split(':').pop();
if (graph.bySlug.has(suf)) return graph.bySlug.get(suf);
if (graph.byName.has(suf)) return graph.byName.get(suf);
}
return null;
}
```
- [ ] **Step 4: GREEN.**
- [ ] **Step 5: Commit**`git -C "<worktree>" add tools/node-graph.mjs tools/node-graph.test.mjs` + commit `"feat(m3-b): node-graph buildNodeGraph + resolveNode (ОВ-Д2 grounding)"`
---
## Task 2: Рёбра-подсказки — twinsOf / hintLinksOf / conflictsOf
**Files:** Modify `tools/node-graph.mjs`, `tools/node-graph.test.mjs`
- [ ] **Step 1: Падающие тесты (добавить)**
```js
import { twinsOf, hintLinksOf, conflictsOf } from './node-graph.mjs';
describe('twinsOf (близнецы = общий subcategory, активные)', () => {
const g = buildNodeGraph(REG);
it('возвращает соседей по subcategory без себя', () => {
const t = twinsOf(g, '#36').map((n) => n.id).sort();
expect(t).toEqual(['#37', '#38']);
});
it('узел без subcategory → нет близнецов', () => {
expect(twinsOf(g, '#19')).toEqual([]);
});
it('несуществующий ref → []', () => { expect(twinsOf(g, 'nope')).toEqual([]); });
});
describe('hintLinksOf (связи = со-членство в цепочке)', () => {
const g = buildNodeGraph(REG);
it('соседи по chains, без себя', () => {
expect(hintLinksOf(g, '#36').map((n) => n.id)).toEqual(['#37']);
expect(hintLinksOf(g, '#37').map((n) => n.id)).toEqual(['#36']);
});
it('узел вне цепочек → []', () => { expect(hintLinksOf(g, '#19')).toEqual([]); });
});
describe('conflictsOf (явные attributes.conflicts_with)', () => {
it('резолвит явные конфликт-рёбра', () => {
const reg = { nodes: [
{ id: '#a', slug: 'a', status: 'active', attributes: { conflicts_with: ['#b'] } },
{ id: '#b', slug: 'b', status: 'active' },
], chains: {} };
const g = buildNodeGraph(reg);
expect(conflictsOf(g, '#a').map((n) => n.id)).toEqual(['#b']);
});
it('нет поля → []', () => {
const g = buildNodeGraph(REG);
expect(conflictsOf(g, '#36')).toEqual([]);
});
});
```
- [ ] **Step 2: RED.**
- [ ] **Step 3: Реализация (дописать)**
```js
/** Близнецы — активные узлы той же subcategory (без себя). Роутер ОБЯЗАН сравнить (1.1). */
export function twinsOf(graph, ref) {
const self = resolveNode(graph, ref);
if (!self || !self.subcategory) return [];
return (graph.subcategoryIndex.get(self.subcategory) || [])
.filter((n) => n !== self && n.status === 'active');
}
/** Связи-подсказки — узлы, со-встречающиеся с этим в любой цепочке (без себя), дедуп. */
export function hintLinksOf(graph, ref) {
const self = resolveNode(graph, ref);
if (!self) return [];
const seen = new Set(), out = [];
for (const chain of Object.values(graph.chains || {})) {
const members = (chain.sequence || []).map((s) => resolveNode(graph, s)).filter(Boolean);
if (!members.includes(self)) continue;
for (const m of members) {
if (m === self || seen.has(m.id)) continue;
seen.add(m.id); out.push(m);
}
}
return out;
}
/** Конфликты — явные attributes.conflicts_with (массив ref'ов), резолвятся в узлы. Free-text не парсим. */
export function conflictsOf(graph, ref) {
const self = resolveNode(graph, ref);
if (!self) return [];
const refs = (self.attributes && self.attributes.conflicts_with) || [];
return refs.map((r) => resolveNode(graph, r)).filter(Boolean);
}
```
- [ ] **Step 4: GREEN.**
- [ ] **Step 5: Commit**`git -C "<worktree>" commit -am "feat(m3-b): hint edges — twinsOf/hintLinksOf/conflictsOf"`
---
## Task 3: Чувство свежести (3.6)
**Files:** Modify `tools/node-graph.mjs`, `tools/node-graph.test.mjs`
- [ ] **Step 1: Падающие тесты (добавить)**
```js
import { checkGraphFreshness } from './node-graph.mjs';
describe('checkGraphFreshness (3.6)', () => {
it('реестр не новее сборки → fresh', () => {
expect(checkGraphFreshness({ registryMtimeMs: 100, builtAtMs: 200 })).toMatchObject({ fresh: true, stale: false });
});
it('реестр новее сборки → stale + причина', () => {
const r = checkGraphFreshness({ registryMtimeMs: 300, builtAtMs: 200 });
expect(r.stale).toBe(true); expect(r.fresh).toBe(false); expect(r.reason).toMatch(/устар|реестр новее|stale/i);
});
it('нет данных о времени сборки → stale (не доверяем уверенно)', () => {
expect(checkGraphFreshness({ registryMtimeMs: 100, builtAtMs: null }).stale).toBe(true);
});
});
```
- [ ] **Step 2: RED.**
- [ ] **Step 3: Реализация (дописать)**
```js
/**
* 3.6 — чувство свежести: реестр новее, чем дата сборки графа/каталога → данные
* могли устареть (роутер предупреждает + пересобирает, не работает уверенно по старью).
* mtimes инъектируются (чистая функция). Нет builtAt → считаем устаревшим.
*/
export function checkGraphFreshness({ registryMtimeMs, builtAtMs }) {
if (typeof builtAtMs !== 'number') return { fresh: false, stale: true, reason: 'нет даты сборки графа — пересобрать' };
if (typeof registryMtimeMs === 'number' && registryMtimeMs > builtAtMs)
return { fresh: false, stale: true, reason: 'реестр новее графа — данные могли устареть, пересобрать' };
return { fresh: true, stale: false, reason: 'граф актуален' };
}
```
- [ ] **Step 4: GREEN.**
- [ ] **Step 5: Commit**`git -C "<worktree>" commit -am "feat(m3-b): checkGraphFreshness (3.6 freshness sense)"`
---
## Task 4: Инварианты на РЕАЛЬНОМ реестре + регрессия
**Files:** Create `tools/m3b-node-graph-invariants.test.mjs`
- [ ] **Step 1: Инвариант-тест**`tools/m3b-node-graph-invariants.test.mjs`
```js
import { describe, it, expect } from 'vitest';
import { statSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { loadRegistry, clearCache } from './registry-load.mjs';
import { buildNodeGraph, resolveNode, twinsOf, checkGraphFreshness } from './node-graph.mjs';
const here = dirname(fileURLToPath(import.meta.url));
const registryPath = join(here, '..', 'docs', 'registry', 'nodes.yaml');
describe('Машина 3-B — граф на реальном реестре', () => {
it('граф строится из nodes.yaml и резолвит известные узлы', () => {
clearCache();
const reg = loadRegistry({ registryPath, useCache: false });
const g = buildNodeGraph(reg);
expect(g.nodes.length).toBeGreaterThan(50);
expect(resolveNode(g, '#36')).not.toBe(null); // adr-kit by id
expect(resolveNode(g, 'mermaid')).not.toBe(null); // by slug
expect(resolveNode(g, 'totally-made-up-skill')).toBe(null); // выдумка
});
it('близнецы architecture-tooling включают друг друга', () => {
clearCache();
const reg = loadRegistry({ registryPath, useCache: false });
const g = buildNodeGraph(reg);
const adr = resolveNode(g, 'adr-kit');
if (adr && adr.subcategory) {
const twinSlugs = twinsOf(g, adr.id).map((n) => n.slug);
expect(twinSlugs.length).toBeGreaterThanOrEqual(1);
}
});
it('freshness против реальной mtime реестра работает', () => {
const mtime = statSync(registryPath).mtimeMs;
expect(checkGraphFreshness({ registryMtimeMs: mtime, builtAtMs: mtime + 1000 }).fresh).toBe(true);
expect(checkGraphFreshness({ registryMtimeMs: mtime, builtAtMs: mtime - 1000 }).stale).toBe(true);
});
});
```
> **NB:** `loadRegistry` кэширует — используем `useCache: false` + `clearCache()` (как в registry-load.test.mjs), чтобы не словить чужой кэш. Если `resolveNode(g, 'mermaid')` вернёт null — slug в реестре другой; поправить ref по факту (записать в журнал).
- [ ] **Step 2: GREEN**`... m3b-node-graph-invariants`.
- [ ] **Step 3: Полная регрессия**`npx vitest run --root ... --config ...` (без фильтра) → всё зелёное.
- [ ] **Step 4: Commit**`git -C "<worktree>" add tools/m3b-node-graph-invariants.test.mjs docs/superpowers/plans/2026-06-04-router-mentor-3b-node-graph.md` + commit `"test(m3-b): node-graph invariants on real registry + plan"`
---
## Self-Review (против канона §2 3-B)
- **Стабильная карта из РЕЕСТРА, не graphify**`buildNodeGraph(registry)` поверх `loadRegistry`. ✅
- **Узлы = скилы/MCP/агенты, рёбра = близнецы/связи/конфликты (подсказки)** → twinsOf (subcategory) / hintLinksOf (chains) / conflictsOf (explicit). ✅ (близнецы/связи выведены механически; конфликты — явные, free-text не парсим — [ДОПУЩЕНИЕ] в журнал).
- **Заземление ОВ-Д2: скил механически резолвится в реальный узел, выдумка → отклонение**`resolveNode` (id/slug/name/suffix; null на выдумку). ✅
- **3.6 свежесть: дата каталога против даты правки реестра**`checkGraphFreshness` (mtimes инъектируются; реальная mtime в инвариантах). ✅
- **Без LLM, портативно** → чистый модуль, имена не зашиты (всё из реестра). ✅
- **Граница с 3-C** (граф НУЖД из контрактов) и 3-D (выбор рассуждением) — отмечена. ✅
@@ -0,0 +1,379 @@
# Машина 3 / под-план 3-C — Машина охвата A/B/C/D — план реализации
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development / executing-plans.
> **NB сборки:** код строит контроллер по TDD (субагент не проходит TDD-гейт).
**Goal:** на контрактах 3-A построить НЕЗАВИСИМЫЙ механический верификатор полноты плана (C-14): A граф зависимостей (needs↔produces: топосорт-порядок, дыра=нужда-без-producer, цикл=флаг, декомпозиция=связные группы); B реестр нужды↔решения (дыры / сироты-скоупкрип); C чек-лист «просьбы цели → план»; D ограничения = полноправные нужды; хребет — буквальный чек-лист готовности (галочки + указатели §).
**Architecture:** чистый `tools/coverage-machine.mjs` поверх контрактов 3-A (`skill-contract` needs/produces/constraints). Только set/graph-операции, без LLM («умный не баран» — машина считает, роутер рассуждает). Сопоставление need↔produce — по нормализованной строке (lower/trim); семантическое сопоставление — НЕ здесь (это рассуждение роутера 3-D). Это рычаг E дисциплины роутера (§6.3): независимый верификатор охвата.
**Tech Stack:** Node.js ESM, vitest tools-only.
---
## ⚠️ Контекст исполнения (как 3-A/3-B)
- Git только `git -C "<worktree>"`. Тесты: `npx vitest run --root ".claude/worktrees/brainrepo/app" --config vitest.config.tools.mjs <фильтр>` через Bash, `dangerouslyDisableSandbox=true`, без `cd`.
- Тест-файлы — в `<worktree>\tools\`, ЦЕЛИКОМ через Write. TDD-гейт: Read плана прямыми слэшами + тест + Bash-RED, потом прод.
## Границы 3-C (что НЕ здесь)
- Выбор скилов рассуждением, сборка цепочки, look-ahead, L-ядро → **3-D** (машина охвата — ВЕРИФИКАТОР готового набора, не селектор).
- **[ДОПУЩЕНИЕ]** Сопоставление need↔produce — по нормализованной строке (lower/trim; равенство или подстрока для просьб). Семантическое сопоставление («spec» ≈ «требования») — рассуждение роутера (3-D), не механика. Машина охвата честно ловит точные дыры/сироты/циклы; нечёткие — задача роутера.
- **[ДОПУЩЕНИЕ]** Извлечение «просьб цели» из текста (C) — мягкий край, выполняется выше (роутер/владелец); 3-C принимает готовый массив просьб и механически сверяет покрытие.
---
## Структура файлов
**Создаём:**
- `tools/coverage-machine.mjs``normToken`, `effectiveNeeds` (D), `buildDependencyGraph`/`topoOrder`/`findHoles`/`decompositionGroups` (A), `coverageRegistry` (B), `requestsChecklist` (C), `readinessChecklist` (хребет).
- Тесты: `tools/coverage-machine.test.mjs` (чистый) + `tools/m3c-coverage-invariants.test.mjs` (на контрактах 3-A через skill-contract-registry).
**Порядок:** A граф+топосорт+дыры+группы (Task 1) → D effectiveNeeds + B реестр + C чек-лист просьб (Task 2) → хребет readinessChecklist + инварианты + регрессия (Task 3).
---
## Task 1: A — граф зависимостей (needs↔produces): topoOrder / findHoles / decompositionGroups
**Files:** Create `tools/coverage-machine.mjs`, `tools/coverage-machine.test.mjs`
- [ ] **Step 1: Падающий тест**`tools/coverage-machine.test.mjs`
```js
import { describe, it, expect } from 'vitest';
import { normToken, buildDependencyGraph, topoOrder, findHoles, decompositionGroups } from './coverage-machine.mjs';
const c = (skill, needs, produces, constraints = []) => ({ skill, needs, produces, constraints });
// Y produces 'spec'; X needs 'spec' → Y перед X
const CHAIN = [c('X', ['spec'], ['code']), c('Y', [], ['spec'])];
describe('normToken', () => {
it('lower + trim', () => { expect(normToken(' Spec ')).toBe('spec'); });
});
describe('buildDependencyGraph (A: needs↔produces)', () => {
it('ребро producer→consumer via need', () => {
const g = buildDependencyGraph(CHAIN);
expect(g.edges).toContainEqual({ from: 'Y', to: 'X', via: 'spec' });
});
});
describe('topoOrder (A: порядок = топосортировка, цикл = флаг)', () => {
it('порядок: Y перед X', () => {
const r = topoOrder(CHAIN);
expect(r.cycle).toBe(null);
expect(r.order.indexOf('Y')).toBeLessThan(r.order.indexOf('X'));
});
it('цикл помечается', () => {
const cyc = [c('A', ['b'], ['a']), c('B', ['a'], ['b'])];
const r = topoOrder(cyc);
expect(r.order).toBe(null);
expect(r.cycle.sort()).toEqual(['A', 'B']);
});
});
describe('findHoles (A: нужда без producer; D: ограничения тоже)', () => {
it('нужда, которую никто не производит → дыра', () => {
const h = findHoles([c('X', ['spec'], ['code'])]);
expect(h).toContainEqual({ need: 'spec', neededBy: 'X', kind: 'need' });
});
it('initialInputs закрывают нужду (не дыра)', () => {
expect(findHoles([c('X', ['spec'], ['code'])], { initialInputs: ['spec'] })).toEqual([]);
});
it('ограничение без покрытия → дыра kind=constraint (D)', () => {
const h = findHoles([c('X', [], ['code'], ['must be RLS-safe'])]);
expect(h).toContainEqual({ need: 'must be RLS-safe', neededBy: 'X', kind: 'constraint' });
});
it('produced нужда не дыра', () => {
expect(findHoles(CHAIN)).toEqual([]);
});
});
describe('decompositionGroups (A: связные группы)', () => {
it('связанные скилы — одна группа, несвязанный — отдельная', () => {
const groups = decompositionGroups([...CHAIN, c('Z', [], ['unrelated'])]);
const sizes = groups.map((g) => g.length).sort();
expect(sizes).toEqual([1, 2]);
});
});
```
- [ ] **Step 2: RED.**
- [ ] **Step 3: Реализация**`tools/coverage-machine.mjs`
```js
#!/usr/bin/env node
/**
* coverage-machine — машина охвата A/B/C/D (C-14) поверх контрактов 3-A.
* НЕЗАВИСИМЫЙ механический верификатор полноты плана: set/graph-операции,
* без LLM («умный не баран»). Рычаг E дисциплины роутера (§6.3).
*/
export function normToken(s) { return String(s ?? '').trim().toLowerCase(); }
/** D — эффективные нужды контракта = needs + constraints (ограничения как полноправные нужды). */
export function effectiveNeeds(contract) {
const needs = (contract.needs || []).map((n) => ({ token: n, kind: 'need' }));
const cons = (contract.constraints || []).map((cN) => ({ token: cN, kind: 'constraint' }));
return [...needs, ...cons];
}
/** Множество всего, что производят контракты (нормализованные produces). */
function producedSet(contracts) {
const s = new Set();
for (const c of contracts) for (const p of c.produces || []) s.add(normToken(p));
return s;
}
/** A — граф зависимостей: ребро producer→consumer via need (по needs↔produces). */
export function buildDependencyGraph(contracts) {
const byProduce = new Map(); // normProduce → [skill]
for (const c of contracts) for (const p of c.produces || []) {
const k = normToken(p);
if (!byProduce.has(k)) byProduce.set(k, []);
byProduce.get(k).push(c.skill);
}
const edges = [];
for (const c of contracts) for (const n of c.needs || []) {
const producers = byProduce.get(normToken(n)) || [];
for (const p of producers) if (p !== c.skill) edges.push({ from: p, to: c.skill, via: normToken(n) });
}
return { nodes: contracts.map((c) => c.skill), edges };
}
/** A — топосортировка (Kahn). Цикл → {order:null, cycle:[оставшиеся скилы]}. */
export function topoOrder(contracts) {
const { nodes, edges } = buildDependencyGraph(contracts);
const indeg = new Map(nodes.map((n) => [n, 0]));
const adj = new Map(nodes.map((n) => [n, []]));
for (const e of edges) { indeg.set(e.to, (indeg.get(e.to) || 0) + 1); adj.get(e.from).push(e.to); }
const queue = nodes.filter((n) => (indeg.get(n) || 0) === 0);
const order = [];
while (queue.length) {
const n = queue.shift(); order.push(n);
for (const m of adj.get(n) || []) { indeg.set(m, indeg.get(m) - 1); if (indeg.get(m) === 0) queue.push(m); }
}
if (order.length !== nodes.length) {
const cycle = nodes.filter((n) => !order.includes(n));
return { order: null, cycle };
}
return { order, cycle: null };
}
/** A+D — дыры: нужда/ограничение, которую никто не производит и нет в initialInputs. */
export function findHoles(contracts, { initialInputs = [], includeConstraints = true } = {}) {
const produced = producedSet(contracts);
const inputs = new Set(initialInputs.map(normToken));
const holes = [];
for (const c of contracts) {
const items = includeConstraints ? effectiveNeeds(c) : (c.needs || []).map((n) => ({ token: n, kind: 'need' }));
for (const { token, kind } of items) {
const k = normToken(token);
if (!produced.has(k) && !inputs.has(k)) holes.push({ need: token, neededBy: c.skill, kind });
}
}
return holes;
}
/** A — связные группы (неориентированные компоненты по рёбрам needs↔produces). */
export function decompositionGroups(contracts) {
const { nodes, edges } = buildDependencyGraph(contracts);
const parent = new Map(nodes.map((n) => [n, n]));
const find = (x) => { while (parent.get(x) !== x) { parent.set(x, parent.get(parent.get(x))); x = parent.get(x); } return x; };
const union = (a, b) => { parent.set(find(a), find(b)); };
for (const e of edges) union(e.from, e.to);
const groups = new Map();
for (const n of nodes) { const r = find(n); if (!groups.has(r)) groups.set(r, []); groups.get(r).push(n); }
return [...groups.values()];
}
```
- [ ] **Step 4: GREEN.**
- [ ] **Step 5: Commit**`git -C "<worktree>" add tools/coverage-machine.mjs tools/coverage-machine.test.mjs` + commit `"feat(m3-c): coverage A — dep graph / topoOrder / holes(+D) / groups (C-14)"`
---
## Task 2: B реестр нужды↔решения (сироты) + C чек-лист просьбы→план
**Files:** Modify `tools/coverage-machine.mjs`, `tools/coverage-machine.test.mjs`
- [ ] **Step 1: Падающие тесты (добавить)**
```js
import { coverageRegistry, requestsChecklist } from './coverage-machine.mjs';
describe('coverageRegistry (B: нужды↔решения, дыры + сироты)', () => {
it('дыра попадает в holes', () => {
const r = coverageRegistry([c('X', ['spec'], ['code'])]);
expect(r.holes.map((h) => h.need)).toContain('spec');
});
it('сирота-скоупкрип: produces никому не нужен и не покрывает просьбу', () => {
// Y produces 'spec' (нужен X). Z produces 'extra' — никому не нужен, просьбы нет → сирота
const r = coverageRegistry([c('X', ['spec'], ['code']), c('Y', [], ['spec']), c('Z', [], ['extra'])], { requests: ['code'] });
expect(r.orphans.map((o) => o.skill)).toContain('Z');
expect(r.orphans.map((o) => o.skill)).not.toContain('Y'); // Y нужен X
});
});
describe('requestsChecklist (C: просьбы цели → план)', () => {
it('просьба, которую кто-то производит → ok', () => {
const list = requestsChecklist(['code'], [c('X', [], ['code'])]);
expect(list).toEqual([{ request: 'code', coveredBy: 'X', ok: true }]);
});
it('непокрытая просьба → ok=false, coveredBy=null', () => {
const list = requestsChecklist(['report'], [c('X', [], ['code'])]);
expect(list[0]).toMatchObject({ request: 'report', coveredBy: null, ok: false });
});
it('покрытие по подстроке (мягкий край)', () => {
const list = requestsChecklist(['csv'], [c('X', [], ['export to csv file'])]);
expect(list[0].ok).toBe(true);
});
});
```
- [ ] **Step 2: RED.**
- [ ] **Step 3: Реализация (дописать)**
```js
/** Найти контракт, чей produces покрывает запрос (равенство нормализованных ИЛИ подстрока — мягкий край C). */
function coveringSkill(contracts, request) {
const r = normToken(request);
for (const c of contracts) for (const p of c.produces || []) {
const pp = normToken(p);
if (pp === r || pp.includes(r) || r.includes(pp)) return c.skill;
}
return null;
}
/** C — чек-лист «просьбы цели → план»: каждая просьба сверяется с produces плана. */
export function requestsChecklist(requests, contracts) {
return (requests || []).map((req) => {
const coveredBy = coveringSkill(contracts, req);
return { request: req, coveredBy, ok: coveredBy !== null };
});
}
/** B — двусторонний реестр: дыры (findHoles) + сироты (produces никому не нужен и не покрывает просьбу). */
export function coverageRegistry(contracts, { requests = [], initialInputs = [] } = {}) {
const holes = findHoles(contracts, { initialInputs });
// нужды всех контрактов (для проверки «кому-то нужно»)
const allNeeds = new Set();
for (const c of contracts) for (const n of c.needs || []) allNeeds.add(normToken(n));
const reqTokens = (requests || []).map(normToken);
const orphans = [];
for (const c of contracts) {
const produces = (c.produces || []).map(normToken);
const neededBySomeone = produces.some((p) => [...allNeeds].some((n) => n === p));
const coversRequest = produces.some((p) => reqTokens.some((r) => r === p || p.includes(r) || r.includes(p)));
if (produces.length > 0 && !neededBySomeone && !coversRequest)
orphans.push({ skill: c.skill, reason: 'produces никому не нужен и не покрывает просьбу цели (scope creep?)' });
}
return { holes, orphans };
}
```
- [ ] **Step 4: GREEN.**
- [ ] **Step 5: Commit**`git -C "<worktree>" commit -am "feat(m3-c): coverage B (registry/orphans) + C (requests checklist)"`
---
## Task 3: Хребет — readinessChecklist + инварианты на контрактах 3-A + регрессия
**Files:** Modify `tools/coverage-machine.mjs`, `tools/coverage-machine.test.mjs`; Create `tools/m3c-coverage-invariants.test.mjs`
- [ ] **Step 1: Падающие тесты (добавить в coverage-machine.test.mjs)**
```js
import { readinessChecklist } from './coverage-machine.mjs';
describe('readinessChecklist (хребет — галочки + указатели §)', () => {
it('полный план → ready=true, все галочки', () => {
const r = readinessChecklist({ contracts: [c('Y', [], ['spec']), c('X', ['spec'], ['code'])], requests: ['code'] });
expect(r.ready).toBe(true);
expect(r.items.every((i) => i.ok)).toBe(true);
expect(r.items.every((i) => typeof i.pointer === 'string')).toBe(true);
});
it('дыра → ready=false + пункт про дыры провален', () => {
const r = readinessChecklist({ contracts: [c('X', ['spec'], ['code'])], requests: ['code'] });
expect(r.ready).toBe(false);
expect(r.items.find((i) => /дыр|hole/i.test(i.label)).ok).toBe(false);
});
it('цикл → пункт про циклы провален', () => {
const r = readinessChecklist({ contracts: [c('A', ['b'], ['a']), c('B', ['a'], ['b'])], requests: [] });
expect(r.items.find((i) => /цикл|cycle/i.test(i.label)).ok).toBe(false);
});
});
```
- [ ] **Step 2: RED.**
- [ ] **Step 3: Реализация (дописать)**
```js
/**
* Хребет машины охвата — буквальный чек-лист готовности плана (галочки + указатели §).
* Объединяет A (дыры/циклы), B (сироты), C (просьбы). ready = все пункты ok.
*/
export function readinessChecklist({ contracts = [], requests = [], initialInputs = [] }) {
const { holes, orphans } = coverageRegistry(contracts, { requests, initialInputs });
const topo = topoOrder(contracts);
const reqList = requestsChecklist(requests, contracts);
const items = [
{ label: 'Все нужды/ограничения покрыты (нет дыр)', ok: holes.length === 0, pointer: '§A findHoles', detail: holes },
{ label: 'Нет циклов зависимостей', ok: topo.cycle === null, pointer: '§A topoOrder', detail: topo.cycle },
{ label: 'Нет сирот-скоупкрипа', ok: orphans.length === 0, pointer: '§B coverageRegistry', detail: orphans },
{ label: 'Все просьбы цели покрыты', ok: reqList.every((r) => r.ok), pointer: '§C requestsChecklist', detail: reqList.filter((r) => !r.ok) },
];
return { ready: items.every((i) => i.ok), items };
}
```
- [ ] **Step 4: GREEN** (coverage-machine).
- [ ] **Step 5: Инвариант на контрактах 3-A**`tools/m3c-coverage-invariants.test.mjs`
```js
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { buildRegistry } from './skill-contract-registry.mjs';
import { readinessChecklist, findHoles } from './coverage-machine.mjs';
const here = dirname(fileURLToPath(import.meta.url));
const cdir = join(here, '..', 'docs', 'registry', 'contracts');
describe('Машина 3-C — охват на реальных контрактах 3-A', () => {
it('контракты 3-A загружаются и прогоняются через машину охвата', () => {
const wp = JSON.parse(readFileSync(join(cdir, 'writing-plans.contract.json'), 'utf8'));
const pd = JSON.parse(readFileSync(join(cdir, 'operations-process-doc.contract.json'), 'utf8'));
const { contracts } = buildRegistry([{ contract: wp }, { contract: pd, currentContent: '' }]);
// машина охвата принимает контракты 3-A без ошибок и даёт детерминированный чек-лист
const r = readinessChecklist({ contracts, requests: ['implementation-plan'] });
expect(Array.isArray(r.items)).toBe(true);
expect(r.items).toHaveLength(4);
// writing-plans produces 'implementation-plan...' → просьба покрыта по подстроке
expect(r.items.find((i) => /просьб/i.test(i.label)).ok).toBe(true);
});
it('findHoles на контракте с непокрытой нуждой ловит дыру', () => {
const wp = JSON.parse(readFileSync(join(cdir, 'writing-plans.contract.json'), 'utf8'));
const holes = findHoles([wp]);
// writing-plans needs 'spec or requirements' — никто в одиночном наборе не производит → дыра
expect(holes.length).toBeGreaterThan(0);
});
});
```
- [ ] **Step 6: GREEN** (m3c invariants).
- [ ] **Step 7: Полная регрессия**`npx vitest run --root ... --config ...` без фильтра → всё зелёное.
- [ ] **Step 8: Commit**`git -C "<worktree>" add tools/coverage-machine.mjs tools/coverage-machine.test.mjs tools/m3c-coverage-invariants.test.mjs docs/superpowers/plans/2026-06-04-router-mentor-3c-coverage-machine.md` + commit `"feat(m3-c): readinessChecklist backbone + invariants on 3-A contracts + plan"`
---
## Self-Review (против канона §2 3-C + C-14)
- **A граф зависимостей (needs↔produces; декомпозиция=группы, порядок=топосорт, дыра=нужда-без-producer, цикл=флаг)** → buildDependencyGraph/topoOrder/findHoles/decompositionGroups. ✅
- **B двусторонний реестр нужды↔решения (дыра / сирота-скоупкрип)** → coverageRegistry (holes + orphans). ✅
- **C чек-лист «просьбы цели → план» (мягкий край на извлечении просьб)** → requestsChecklist (просьбы принимаются готовым массивом — извлечение выше; сопоставление мех. по подстроке). ✅
- **D ограничения как полноправные нужды** → effectiveNeeds + findHoles includeConstraints (дыра kind='constraint'). ✅
- **Хребет — буквальный чек-лист готовности (галочки + указатели §)** → readinessChecklist (4 пункта, pointer на §). ✅
- **На контрактах 3-A, без LLM, рычаг E верификатора (§6.3)** → инварианты на реальных контрактах; чистые set/graph-операции. ✅
- **Граница с 3-D** (селектор/рассуждение) — машина охвата только ВЕРИФИЦИРУЕТ. ✅

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