Files
portal/docs/automation-graph.html
T

2809 lines
263 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Система автоматизации Лидерры</title>
<script src="https://unpkg.com/vis-network@9.1.9/standalone/umd/vis-network.min.js"></script>
<script src="automation-graph-data.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: #0d0d1a; color: #fdf6e3; font-family: 'Segoe UI', system-ui, sans-serif; height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
/* ── Toolbar ── */
#toolbar { background: #073642; border-bottom: 1px solid #586e75; padding: 8px 12px; display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
#toolbar h1 { font-size: 14px; color: #93a1a1; white-space: nowrap; margin-right: 6px; }
#search { background: #002b36; border: 1px solid #586e75; color: #fdf6e3; border-radius: 5px; padding: 5px 10px; font-size: 13px; width: 220px; outline: none; }
#search:focus { border-color: #268bd2; }
#search::placeholder { color: #586e75; }
.btn { background: #073642; border: 1px solid #586e75; color: #93a1a1; border-radius: 5px; padding: 5px 12px; font-size: 12px; cursor: pointer; transition: all 0.15s; }
.btn:hover { background: #0d4a5a; color: #fdf6e3; border-color: #839496; }
#btn-clear { margin-left: auto; }
/* ── Main area ── */
#main { flex: 1; display: flex; overflow: hidden; }
/* ── Graph canvas ── */
#network { flex: 1; background: #1e1e2e; }
#network canvas { background: #1e1e2e; }
/* ── Legend panel ── */
#legend-panel { width: 300px; min-width: 300px; background: #002b36; border-left: 1px solid #586e75; overflow-y: auto; padding: 16px; display: none; flex-direction: column; gap: 14px; position: relative; }
#legend-handle {
position: absolute; left: 0; top: 0;
width: 6px; height: 100%;
cursor: col-resize;
background: transparent;
transition: background 0.15s;
z-index: 10;
}
#legend-handle:hover, #legend-handle.dragging { background: #0d4a5a; }
#legend-panel.visible { display: flex; }
#legend-close { align-self: flex-end; background: none; border: none; color: #586e75; font-size: 18px; cursor: pointer; line-height: 1; padding: 0; }
#legend-close:hover { color: #fdf6e3; }
#legend-title { font-size: 15px; font-weight: 600; color: #fdf6e3; overflow-wrap: break-word; }
#legend-edge-title { font-size: 15px; font-weight: 600; color: #fdf6e3; overflow-wrap: break-word; line-height: 1.4; }
#legend-category { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; }
.legend-section { background: #073642; border-radius: 6px; padding: 10px 12px; }
.legend-section h4 { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: #839496; margin-bottom: 6px; }
.legend-section p { font-size: 13px; color: #eee8d5; line-height: 1.5; }
.legend-section ul { list-style: none; display: flex; flex-direction: column; gap: 4px; }
.legend-section li { font-size: 12px; color: #eee8d5; line-height: 1.4; }
.legend-section li span.cond { color: #839496; font-style: italic; }
.conflict-item { border-radius: 4px; padding: 6px 8px; margin-top: 4px; }
.conflict-item .cname { font-weight: 600; font-size: 12px; }
.conflict-item .cdesc { color: #eee8d5; font-size: 11px; margin-top: 2px; line-height: 1.4; }
#legend-no-conflicts { font-size: 12px; color: #586e75; }
/* ── Category legend footer ── */
#cat-legend { background: #073642; border-top: 1px solid #586e75; padding: 7px 14px; display: flex; flex-wrap: wrap; gap: 12px; flex-shrink: 0; }
.cat-item { display: flex; align-items: center; gap: 5px; font-size: 11px; color: #839496; }
.cat-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
/* .cat-item interactive states (Task 1) — separate from layout rule above to keep concerns split */
.cat-item {
cursor: pointer;
padding: 2px 6px;
border-radius: 4px;
transition: background 0.12s, box-shadow 0.12s;
user-select: none;
}
.cat-item:hover { background: rgba(255,255,255,0.05); }
.cat-item.active {
background: rgba(253,246,227,0.12);
box-shadow: inset 0 0 0 1px rgba(253,246,227,0.4);
color: #fdf6e3;
}
/* ── Паспорт узла (iter6) ── */
#passport-section p { font-size: 12px; color: #eee8d5; line-height: 1.6; }
#passport-section p .pp-k { color: #839496; }
/* ── Кнопки режимов в футере (iter6) ── */
.cat-ctl-sep { width: 1px; align-self: stretch; background: #586e75; margin: 0 4px; }
.cat-ctl {
background: #002b36; border: 1px solid #586e75; color: #93a1a1;
border-radius: 4px; padding: 2px 8px; font-size: 11px; cursor: pointer;
transition: background 0.12s, box-shadow 0.12s; user-select: none;
}
.cat-ctl:hover { background: #0d4a5a; color: #fdf6e3; }
.cat-ctl.active {
background: rgba(253,246,227,0.12);
box-shadow: inset 0 0 0 1px rgba(253,246,227,0.4);
color: #fdf6e3;
}
/* ── Панель «Разделы» (функциональная квалификация) ── */
#legend-sections-content { display: flex; flex-direction: column; gap: 10px; }
#legend-sections-title { font-size: 15px; font-weight: 600; color: #fdf6e3; }
.sect-bucket-h { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: #b58900; font-weight: 600; margin: 8px 0 2px; }
.sect-row { background: #073642; border-radius: 6px; padding: 7px 10px; }
.sect-row.sect-empty { opacity: 0.5; }
.sect-name { font-size: 12px; color: #eee8d5; font-weight: 600; }
.sect-name .sect-id { color: #839496; font-weight: 400; }
.sect-cnt { font-size: 11px; color: #839496; }
.sect-empty-mark { font-size: 11px; color: #586e75; font-style: italic; margin-top: 3px; }
.sect-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; }
.sect-chip { font-size: 10px; color: #93a1a1; background: #002b36; border: 1px solid #586e75; border-radius: 3px; padding: 1px 5px; cursor: pointer; }
.sect-chip:hover { background: #0d4a5a; color: #fdf6e3; }
/* ── Панель «Хотелки» (отложенный backlog развития мозга) ── */
#legend-wishlist-content { display: flex; flex-direction: column; gap: 10px; }
#legend-wishlist-title { font-size: 15px; font-weight: 600; color: #fdf6e3; }
.wish-row { background: #073642; border-radius: 6px; padding: 8px 10px; border-left: 3px solid #586e75; }
.wish-row.wish-next { border-left-color: #859900; }
.wish-row.wish-blocked { border-left-color: #b58900; }
.wish-row.wish-idea { border-left-color: #586e75; }
.wish-head { font-size: 12px; color: #eee8d5; font-weight: 600; }
.wish-head .wish-id { color: #839496; font-weight: 400; }
.wish-status { font-size: 11px; margin-top: 3px; }
.wish-note { font-size: 11px; color: #93a1a1; margin-top: 4px; line-height: 1.5; }
.wish-sect { font-size: 10px; color: #586e75; margin-top: 4px; }
.wish-legend { font-size: 10px; color: #586e75; display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
</style>
</head>
<body>
<div id="toolbar">
<h1>🗺 Лидерра — карта автоматизации</h1>
<input id="search" type="text" placeholder="Поиск узла…" autocomplete="off">
<button class="btn" id="btn-freeze">❄ Зафиксировать</button>
<button class="btn" id="btn-unfreeze">▶ Расшевелить</button>
<button class="btn" id="btn-reset">⊙ Сбросить вид</button>
<button class="btn" id="btn-clear">✕ Снять выделение</button>
</div>
<div id="main">
<div id="network"></div>
<div id="legend-panel">
<div id="legend-handle" title="Перетащи, чтобы изменить ширину"></div>
<button id="legend-close">×</button>
<div id="legend-node-content">
<div id="legend-title"></div>
<div id="legend-category"></div>
<div class="legend-section" id="passport-section">
<h4>📇 Паспорт узла</h4>
<p><span class="pp-k">Внедрён:</span> <span id="ld-since"></span></p>
<p><span class="pp-k">Последнее изменение:</span> <span id="ld-changed"></span></p>
<p><span class="pp-k">Раздел:</span> <span id="ld-section"></span></p>
<p><span class="pp-k">Использований за 7 дней:</span> <span id="ld-uses"></span></p>
<p id="ld-dup-row" style="display:none;"><span class="pp-k">Дубль:</span> <span id="ld-dup"></span></p>
</div>
<div class="legend-section"><h4>Что делает</h4><p id="ld-desc"></p></div>
<div class="legend-section"><h4>Когда используется</h4><p id="ld-when"></p></div>
<div class="legend-section"><h4>Ограничения</h4><p id="ld-limits"></p></div>
<div class="legend-section"><h4>Кому подчиняется</h4><ul id="ld-reports"></ul></div>
<div class="legend-section"><h4>Кто подчиняется ему</h4><ul id="ld-manages"></ul></div>
<div class="legend-section"><h4>С кем работает одновременно</h4><ul id="ld-together"></ul></div>
<div class="legend-section" id="conflicts-section">
<h4>⚡ Конфликты</h4>
<div id="ld-conflicts"></div>
</div>
</div>
<div id="legend-edge-content" style="display:none;">
<div id="legend-edge-title"></div>
<div class="legend-section"><h4>Источник запроса</h4><p id="le-from"></p></div>
<div class="legend-section"><h4>Конечный получатель</h4><p id="le-to"></p></div>
<div class="legend-section"><h4>Тип связи</h4><p id="le-type"></p></div>
<div class="legend-section"><h4>Когда срабатывает</h4><p id="le-when"></p></div>
<div class="legend-section"><h4>Что передаёт</h4><p id="le-transfers"></p></div>
<div class="legend-section"><h4>Обязательность</h4><p id="le-mandatory"></p></div>
<div class="legend-section"><h4>Регламент</h4><p id="le-rule"></p></div>
</div>
<div id="legend-sections-content" style="display:none;">
<div id="legend-sections-title">📂 Разделы деятельности Лидерры</div>
<div class="legend-section">
<p>Узлы карты распределены по функциональным разделам. Пустые разделы — будущие домены «мозга», под которые в карте dev-автоматики ещё нет узлов (playbook не наполнен).</p>
</div>
<div id="sect-list"></div>
</div>
<div id="legend-wishlist-content" style="display:none;">
<div id="legend-wishlist-title">💡 Хотелки — отложенный backlog</div>
<div class="legend-section">
<p>Отложенные «хотелки» развития мозга и портала — то, что решили сделать позже, чтобы не забыть. Источник правды — массив WISHLIST в этом HTML-файле; новая хотелка = новый объект.</p>
<div class="wish-legend"><span>▶ к работе</span><span>⏸ ждёт зависимости</span><span>💭 идея</span></div>
</div>
<div id="wish-list"></div>
</div>
</div>
</div>
<!-- data-filter-key атрибуты потребляются SECTION 8 (interactive highlighting) -->
<div id="cat-legend">
<div class="cat-item" data-filter-key="group:rules"><div class="cat-dot" style="background:#268bd2"></div>Правила</div>
<div class="cat-item" data-filter-key="group:plugins"><div class="cat-dot" style="background:#859900"></div>Плагины</div>
<div class="cat-item" data-filter-key="group:skills_sp"><div class="cat-dot" style="background:#6c71c4"></div>Скилы Superpowers</div>
<div class="cat-item" data-filter-key="group:skills_proj"><div class="cat-dot" style="background:#d33682"></div>Скилы проекта</div>
<div class="cat-item" data-filter-key="group:hooks"><div class="cat-dot" style="background:#2aa198"></div>Хуки</div>
<div class="cat-item" data-filter-key="group:agents"><div class="cat-dot" style="background:#b58900"></div>Агенты</div>
<div class="cat-item" data-filter-key="group:mcp"><div class="cat-dot" style="background:#cb4b16"></div>MCP-серверы</div>
<div class="cat-item" data-filter-key="group:lefthook"><div class="cat-dot" style="background:#dc322f"></div>Lefthook jobs</div>
<div class="cat-item" data-filter-key="group:memory"><div class="cat-dot" style="background:#586e75"></div>Memory files</div>
<div class="cat-item" data-filter-key="group:ruflo"><div class="cat-dot" style="background:#555555; border:1px dashed #888888"></div>🔇 ruflo (изолирован 18.05)</div>
<div class="cat-item" data-filter-key="conflict:RED"><div class="cat-dot" style="background:#ff5f57; border:1px dashed #ff5f57"></div>🔴 Не закрыт правилом</div>
<div class="cat-item" data-filter-key="conflict:BLACK"><div class="cat-dot" style="background:#888888; border:1px dashed #888888"></div>⚫ Возник на практике</div>
<div class="cat-item" data-filter-key="conflict:GREEN"><div class="cat-dot" style="background:#859900; border:1px dashed #859900"></div>🟢 Закрыт правилом</div>
<span class="cat-ctl-sep"></span>
<button class="cat-ctl" id="cat-ctl-heat" title="Подсветить узлы по числу вызовов за 7 дней">🔥 По использованию</button>
<button class="cat-ctl" id="cat-ctl-dup" title="Подсветить явные пары дублей (D1–D5, D7)">⧉ Дубли</button>
<button class="cat-ctl" id="cat-ctl-sect" title="Показать функциональные разделы и распределение узлов по ним">📂 Разделы</button>
<button class="cat-ctl" id="cat-ctl-wish" title="Показать отложенные хотелки — backlog развития мозга и портала">💡 Хотелки</button>
</div>
<script>
// ════════════════════════════════════════════════════
// SECTION 1: NODES
// ════════════════════════════════════════════════════
// RADII, pos() — moved to automation-graph-data.js
// NODES — moved to automation-graph-data.js
// ════════════════════════════════════════════════════
// SECTION 2: EDGES
// ════════════════════════════════════════════════════
// CONFLICT_TYPES, E, CONFLICT — moved to automation-graph-data.js
// EDGES — moved to automation-graph-data.js
// ════════════════════════════════════════════════════
// SECTION 3: NODE DETAILS
// ════════════════════════════════════════════════════
// CATEGORY_LABELS — moved to automation-graph-data.js
function nd(desc, when, limits, reportsTo, manages, together, conflicts) {
// Backward-compat: old 5-arg signature was nd(desc, reportsTo, manages, together, conflicts).
// If 2nd arg is an array, treat as old-style call and shift args.
if (Array.isArray(when)) {
return {
desc,
when: '',
limits: '',
reportsTo: when,
manages: limits,
together: reportsTo,
conflicts: (manages || []),
};
}
return { desc, when: when || '', limits: limits || '', reportsTo, manages, together, conflicts: conflicts || [] };
}
const NODE_DETAILS = {
// ── ПРАВИЛА ──────────────────────────────────────
pravila: nd(
'Главный свод правил работы Клода — кто чем командует, что запрещено, какие обязательные действия.',
'Действует всегда — Клод читает его при старте каждой сессии.',
'§12 Superpowers — hard-rule уровня 0 цепочки приоритетов: скил инвокируется первым, §9 «Отступления» не применяется, economy-режим §12 не отменяет. Расходимость с другими документами — нарушение §7.',
[],
[
{ name: 'CLAUDE.md', cond: 'подчинён, уровень 2a в цепочке приоритетов' },
{ name: 'PSR_v1', cond: 'подчинён, уровень 3 в цепочке приоритетов' },
{ name: 'плагин Superpowers', cond: '§12 обязывает запускать скил первым' },
{ name: 'Все компоненты', cond: 'через цепочку приоритетов §1' }
],
[{ name: 'CLAUDE.md', cond: 'оба читаются при старте сессии' }],
[{ name: 'ruflo Queen', desc: 'iter4iter5: нормативка декларировала ruflo Queen-led routing уровнем 1 (overlord) — расходилось с рантаймом (рой idle, 0 задач). Реколлаж 16.05.2026 (Pravila v1.16 / CLAUDE.md v2.2 / PSR_v1 v3.2 / Tooling v2.2) привёл нормативку к факту: ruflo переописан в advisory/automation-подсистему, уровень −1 убран. Конфликт «декларация ≠ рантайм» закрыт.', type: 'GREEN' }]
),
claude_md: nd(
'Оперативная карта проекта — технологии, команды, фазы, 7-уровневая цепочка приоритетов (§1, уровни 0–6) и §3.5 — ruflo как advisory/automation-подсистема, ссылки на документы.',
'Читается при старте каждой сессии; обновляется при новом инструменте или новой фазе.',
'Править можно только через скил `/claude-md-management:claude-md-improver` или `:revise-claude-md` (правило §5 п.10). Прямые Edit/Write блокируются хуком предупреждения.',
[{ name: 'Pravila', cond: 'всегда подчинён (уровень 2a)' }],
[
{ name: 'Tooling v2.22', cond: 'ссылается как на реестр инструментов' },
{ name: 'плагин claude-md-management', cond: 'правило §5 п.10 — единственный канал правок' }
],
[
{ name: 'Pravila', cond: 'оба читаются при старте сессии' },
{ name: 'Tooling', cond: 'оба — оперативные карты уровня 2' }
],
[{ name: 'PSR_v1', desc: 'Правило §5 п.10 запрещает прямые правки, но PSR_v1 это явно не повторяет — есть риск Edit без скила', type: 'GREEN' }]
),
psr_v1: nd(
'Правила совместной работы плагинов — кто с кем работает, какая процедура обязательна. R0 — головной фильтр выбора плагинов (с реколлажа 16.05.2026 снова на вершине стека, не sub-policy под ruflo).',
'При выборе UI-инструмента (плагин Frontend Design против плагина UI UX Pro Max против MCP-сервера 21st Magic), при координации парных плагинов, при включении дополнительного MCP (внешнего сервиса-инструмента Claude) вне основных фаз.',
'Обязательное правило R14.5: плагины UI UX Pro Max, 21st Magic, Frontend Design — нельзя использовать одновременно. Обязательное правило R6.0 (фильтр стека) и R6.1 (палитра Forest) — нужно соблюдать при UI-выводе плагинов.',
[{ name: 'Pravila', cond: 'подчинён, уровень 3 в цепочке' }],
[
{ name: 'плагин Superpowers + плагин Frontend Design', cond: 'координирует как пару плагинов' },
{ name: 'плагин UI UX Pro Max', cond: 'R14.3: включается только через процедуру' },
{ name: 'MCP-сервер 21st Magic', cond: 'R14.4: включается только через процедуру' }
],
[{ name: 'CLAUDE.md', cond: 'обе — оперативные карты, правятся согласованно' }],
[{ name: 'CLAUDE.md', desc: 'CLAUDE.md §5 п.10 требует править только через скил claude-md-management, а PSR_v1 это ограничение не повторяет — риск прямых Edit', type: 'GREEN' }]
),
tooling: nd(
'Реестр 93 позиций — 73 формализованных инструментов + 20 ruflo-плагинов; §4.10 — ruflo как advisory/automation-подсистема. Когда что использовать, команды установки, конфликты.',
'При выборе инструмента для фазы (нулевая документация / первая backend / вторая frontend / третья перед запуском в боевую среду), при добавлении нового инструмента, при обновлении версий.',
'При прямом конфликте с CLAUDE.md побеждает CLAUDE.md (оперативная карта уровня 2a). Любая правка требует синхронизации с CLAUDE.md §3.',
[
{ name: 'Pravila', cond: 'уровень 2b — оперативная карта рядом с CLAUDE.md' },
{ name: 'CLAUDE.md', cond: 'при прямом конфликте побеждает CLAUDE.md' }
],
[],
[{ name: 'CLAUDE.md', cond: 'обе — оперативные карты, правятся синхронно' }]
),
// ── ПЛАГИНЫ ──────────────────────────────────────
superpowers: nd(
'Плагин поведения Клода — 14 скилов для тестов, отладки, планирования, параллельной работы.',
'При творческих, отладочных, тестовых и многошаговых задачах: скил brainstorming (продумать варианты) / скил TDD (разработка через тесты — failing test first) / скил systematic-debugging / скил verification-before-completion (обязательная проверка готовности) / скил writing-plans / скил parallel-work / скил worktree / скил finishing-PR (запрос на слияние кода) / скил subagent-driven-development / скил writing-skills (карта типов в §12.2 Pravila).',
'§12 Superpowers — hard-rule уровня 0 цепочки приоритетов: скил инвокируется первым. Единственная отмена — явная просьба заказчика «не используй superpowers сейчас» на текущее действие; §9 «Отступления» к §12 не применяется; economy-режим §12 не отменяет.',
[
{ name: 'Pravila §12', cond: 'обязательное правило: скил запускается первым' },
{ name: 'PSR_v1', cond: 'координирует как пару с плагином Frontend Design' }
],
[{ name: 'Все 14 скилов Superpowers', cond: 'содержит' }],
[{ name: 'плагин Frontend Design', cond: 'пара — работают вместе при UI-задачах' }],
[{ name: 'хук economy-mode', desc: 'Режим экономии 100% теоретически может «сэкономить» запуск скила — §12 (hard-rule уровня 0) economy-режим не отменяет.', type: 'GREEN' }]
),
fd_plugin: nd(
'Плагин знаний о UI — Vue, Vuetify, доступность (accessibility), паттерны компонентов для Лидерры.',
'При UI/UX задачах — компоненты, экраны, паттерны взаимодействия; в паре с плагином Superpowers (даёт процесс).',
'Фильтр стека R6.0: срезать React/Tailwind/shadcn/JSX в Vue 3 + Vuetify 3. Обязательное правило палитры Forest R6.1 для цветов, шрифтов и иконок.',
[{ name: 'PSR_v1', cond: 'R5: подчинён как часть пары плагинов' }],
[],
[{ name: 'плагин Superpowers', cond: 'пара — Frontend Design даёт UI-знания, Superpowers даёт процесс' }],
[
{ name: 'плагин UI UX Pro Max', desc: 'Правило PSR_v1 R14.5: нельзя одновременно — оба включены в настройках, но должны чередоваться', type: 'GREEN' },
{ name: 'MCP-сервер 21st Magic', desc: 'Правило PSR_v1 R14.5: нельзя одновременно с Frontend Design — оба потенциально доступны', type: 'GREEN' }
]
),
upm: nd(
'Резервная библиотека UI — 50+ стилей, 161 палитра, 99 правил-подсказок UX. Только по процедуре.',
'Только по процедуре PSR_v1 R14.3: запасной вариант к плагину Frontend Design ИЛИ «третий вариант» в архитектурном решении.',
'R14.5: нельзя одновременно с плагином Frontend Design / MCP-сервером 21st Magic. Фильтр стека R6.0 и обязательное правило палитры Forest R6.1 — обязательны. Проверка доступности Pa11y (автопроверка accessibility — доступности) перед выкаткой.',
[{ name: 'PSR_v1', cond: 'R14.3: включается только через процедуру, не сам по себе' }],
[],
[],
[{ name: 'плагин Frontend Design', desc: 'Правило PSR_v1 R14.5: нельзя одновременно — UI UX Pro Max как материал, Frontend Design как решатель; риск смешать роли', type: 'GREEN' }]
),
claude_md_mgmt: nd(
'Единственный разрешённый способ править CLAUDE.md — через скил claude-md-improver или revise-claude-md.',
'При любой правке CLAUDE.md (новая фаза, новый инструмент, смена версии, новые особенности — всё через скил).',
'Правило PSR_v1 R10.1 блок 1 (инфраструктурная категория). Внутри процедуры продолжают действовать §4 правил Клода (синхронизация Pravila + Tooling).',
[{ name: 'PSR_v1', cond: 'R10.1 Блок 1: инфраструктурная категория' }],
[{ name: 'CLAUDE.md', cond: 'правило §5 п.10: единственный канал правок' }],
[{ name: 'скил q-item-add', cond: 'скил делегирует правки CLAUDE.md через этот плагин' }]
),
hookify_plugin: nd(
'Плагин создания хуков — анализирует разговоры и предлагает новые автоматизации в виде хуков.',
'При запросе «давай повесим хук на это поведение» или после серии повторяющихся ошибок — анализ через агента conversation-analyzer.',
'PSR_v1 R10.1 блок 1 #58 (authoring-tooling). HK1 hard-rule: только по явному /hookify, не проактивно; перед генерацией хука — обязательный pre-check на коллизию с зарегистрированными хуками settings.json; перезапись 6-компонентной economy/skill-discipline архитектуры запрещена. ADR-010.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1 #58: authoring-tooling, HK1 pre-check (ADR-010)' }],
[{ name: 'агент hookify:conversation-analyzer', cond: 'запускает анализ разговоров' }],
[{ name: 'агент hookify:conversation-analyzer', cond: 'плагин и агент работают в паре' }],
[
{ name: 'хук pre-claude-warn', desc: 'Закрыто правилом HK1 (ADR-010): hookify — только по явному /hookify, перед генерацией хука обязательный pre-check на коллизию с существующими хуками settings.json; перезапись 6-компонентной economy/skill-discipline архитектуры запрещена', type: 'GREEN' }
]
),
// ── A6 ARCHITECTURE-TOOLING (17.05.2026) ─────────
adr_kit: nd(
'Плагин ADR (Architecture Decision Records) — фиксация закрытых архитектурных решений в docs/adr/ (формат Nygard, 7 секций) + декларативный энфорсер adr-judge.',
'При фиксации архитектурного решения — стек, паттерн, граница слоёв; ADR-NNN в docs/adr/. Открытые вопросы — не сюда, они в реестре Открытые_вопросы.',
'Правило PSR_v1 R10.1 блок 1 (architecture-tooling, off-phase). adr-judge врезан в lefthook pre-commit job 9 — декларативный regex без --llm, 0 вызовов Claude API. init/install-hooks НЕ запускаются (конфликт-аудит AK1/AK2). Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.11, CLAUDE.md §3.3 #36.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: architecture-tooling' }, { name: 'Tooling', cond: '§4.11 #36 — реестр' }],
[{ name: 'lefthook job 9 (adr-judge)', cond: 'врезан как pre-commit проверка Enforcement-блоков' }],
[{ name: 'docs/adr/', cond: 'хранилище ADR — ADR-000/001/002' }]
),
arch_patterns: nd(
'Скил-плагин — справочник архитектурных паттернов (Clean / Hexagonal / layered architecture, Domain-Driven Design). Knowledge-only, не решатель.',
'При архитектурном вопросе «какой паттерн подходит» — playbook паттернов; код не генерирует, файлы не правит.',
'Правило PSR_v1 R10.1 блок 1 (architecture-tooling, off-phase). Knowledge-only — без хуков и машинерии. Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.13, CLAUDE.md §3.3 #38.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: architecture-tooling' }, { name: 'Tooling', cond: '§4.13 #38 — реестр' }],
[],
[]
),
mermaid_skill: nd(
'Вендоренный standalone-скил (.claude/skills/mermaid/) — генерирует исходник Mermaid-диаграмм (23 типа, включая C4/architecture). Рендера mmdc/Chromium не требует.',
'При построении C4/архитектурной диаграммы — результат в docs/architecture/ (Mermaid рендерится на GitHub нативно).',
'Вендорен — не плагин, не marketplace, не подсистема (конфликт-аудит CC1: иммунен к потере апстрима). lefthook markdownlint+cspell исключают .claude/skills/mermaid/** (MK1). Tooling §4.12, CLAUDE.md §3.3 #37.',
[{ name: 'Tooling', cond: '§4.12 #37 — реестр' }],
[],
[{ name: 'docs/architecture/', cond: 'C4-диаграммы → c4-context.md' }]
),
deptrac: nd(
'Composer dev-dependency deptrac/deptrac v4.6.1 (BSD-3) — статический анализ направления зависимостей между слоями App\\ (Controller/Service/Model/Job/…). Чистый PHP, 0 вызовов LLM.',
'Архитектурный fitness-гейт: проверяет, что код не нарушает границы слоёв. Конфиг app/deptrac.yaml (13 слоёв) + ruleset; запускается автоматически как lefthook pre-commit job 10 на staged app/**/*.php.',
'Правило PSR_v1 R10.1 блок 1 note (architecture-tooling, off-phase — composer dev-dep, не marketplace-плагин). Первый прогон 0 нарушений → baseline-файл не нужен (red-green доказан). Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.18, CLAUDE.md §3.3 #43.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1 note: architecture-tooling' }, { name: 'Tooling', cond: '§4.18 #43 — реестр' }],
[{ name: 'lefthook job 10 (deptrac)', cond: 'врезан как pre-commit гейт направления зависимостей' }],
[{ name: 'docs/architecture/', cond: 'mermaidjs-форматтер → c4-component-layers.md' }]
),
// ── A4 DESIGN-TOOLING (17.05.2026) ──────────────
mcp_figma: nd(
'MCP Figma (#44) — DEFERRED, precondition: Figma-аккаунт. Extract-only (ADR-006): извлечение токенов/variables из источника дизайна. FD #30 остаётся единственным UI-решателем.',
'При наличии Figma-аккаунта и нужде извлечь дизайн-токены/variables напрямую из Figma-файла.',
'DEFERRED — не установлен, требует Figma-аккаунт (Б-1). Не UI-решатель → вне R6.0/R6.1/R14. Extract-only, не генерирует UI-решения. PSR_v1 R10.1 блок 3.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 3: design-tooling (DEFERRED)' }],
[],
[{ name: 'Frontend Design', cond: 'FD остаётся единственным UI-решателем; Figma MCP — только источник токенов' }]
),
mcp_icons: nd(
'MCP Universal Icons (#45) — поиск/вставка SVG-иконок, 10 коллекций включая Lucide. Tools search_icons/get_icon. SVG framework-neutral (R6.0).',
'При поиске иконки из коллекции Lucide или других (Heroicons, Tabler, Phosphor и др.) — получить SVG-исходник для вставки в Vue-компонент.',
'SVG framework-neutral — результат нужно обернуть в Vue-компонент вручную. R6.0 фильтр не блокирует (SVG — не React/Tailwind). Lucide — branded иконочный стек проекта (CLAUDE.md §2). PSR_v1 R10.1 блок 3.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 3: design-tooling' }],
[],
[{ name: 'Frontend Design', cond: 'FD использует результат поиска как материал при UI-задачах' }]
),
design_plugin: nd(
'Плагин Design (#46, Anthropic Verified) — дизайн-критика, a11y-аудит дизайн-уровня, UX-копирайт, research synthesis. Pre-code (ADR-006); Pa11y остаётся техническим a11y SoT.',
'При дизайн-критике макета или компонента, при UX-анализе flow, при написании UX-копирайта, при synthesis пользовательских исследований.',
'Pre-code инструмент (ADR-006) — не генерирует финальный код, даёт дизайн-рекомендации. Pa11y остаётся техническим a11y SoT (§5 п.3). Не UI-решатель в смысле PSR_v1 R14 → вне R14 pipeline. PSR_v1 R10.1 блок 1.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: design-tooling' }],
[],
[{ name: 'Frontend Design', cond: 'Design plugin — pre-code критика; FD — post-spec UI-решатель; разные фазы' }]
),
// ── C9 PROJECT-MANAGEMENT-TOOLING (17.05.2026) ──
ccpm: nd(
'Vendored-скил CCPM: PRD→эпик→GitHub-issue→код с трассируемостью. Раздел C9 опирается также на reuse — GitHub MCP (issues/Projects v2) + Superpowers writing-plans + q-item-add.',
'При создании PRD, декомпозиции на эпики/задачи, связывании с GitHub Issues — стек CCPM + GitHub MCP + writing-plans.',
'Вендорен в .claude/skills/ccpm/ (C9 project-management-tooling, off-phase). Раздел C9. Tooling #41, CLAUDE.md §3.3 #41.',
[{ name: 'Tooling', cond: '§4.16 #41 — реестр' }],
[],
[{ name: 'GitHub MCP', cond: 'CCPM использует GitHub Issues/Projects v2 как source-of-truth' }]
),
product_mgmt: nd(
'Плагин Anthropic (product-management@knowledge-work-plugins): PRD/роадмап/метрики — product-strategy церемонии.',
'При написании PRD, обновлении роадмапа, анализе метрик продукта — skills product-management плагина.',
'Правило PSR_v1 R10.1 блок 1 (project-management-tooling, off-phase). Не UI → вне фильтров R6.0/R6.1/R14. Tooling #42, CLAUDE.md §3.3 #42.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: project-management-tooling' }, { name: 'Tooling', cond: '§4.17 #42 — реестр' }],
[],
[]
),
// ── D3 AUDIT-SECURITY (17.05.2026) ───────────────
tob_skills: nd(
'Marketplace-плагин Trail of Bits (`trailofbits/skills`) — курированный субсет 8 audit-плагинов: `differential-review`, `audit-context-building`, `supply-chain-risk-auditor`, `insecure-defaults`, `sharp-edges`, `static-analysis`, `variant-analysis`, `agentic-actions-auditor`. Глубокие on-demand аудит-кампании. Раздел D3. Tooling #39, off-phase audit-security.',
'При глубоком аудите безопасности: diff-ревью, supply-chain риски зависимостей, поиск вариантов уязвимостей, статический анализ (SARIF). Глубокие кампании — не inline SAST.',
'Правило PSR_v1 R10.1 блок 1 (audit-security, off-phase). Граница TB1: Semgrep MCP (#25) = inline SAST, ToB = глубокие on-demand кампании. CC-BY-SA-4.0 — не вендорено, лицензионный триггер TB4 не применяется. Не UI → вне R6.0/R6.1/R14. Tooling §4.14, CLAUDE.md §3.3 #39.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: audit-security' }, { name: 'Tooling', cond: '§4.14 #39 — реестр' }],
[],
[{ name: 'MCP: semgrep', cond: 'TB1: граница — Semgrep inline SAST, ToB глубокие кампании' }],
[{ name: 'MCP: semgrep', desc: 'TB1: граница разграничена регламентом — Semgrep = inline SAST в процессе работы, Trail of Bits = глубокие аудит-кампании по запросу. Параллельное использование разрешено при разных сценариях.', type: 'GREEN' }]
),
sec_guidance: nd(
'Anthropic-плагин (`security-guidance@claude-plugins-official`, Anthropic Verified) — один блокирующий PreToolUse-хук, inline-предупреждения уязвимостей при правке кода (8 контентных правил + 1 path-правило). При первом за сессию совпадении уязвимого паттерна в файле — sys.exit(2), блокирует правку (одноразовый speed-bump, retry проходит). Раздел D3. Tooling #40.',
'Активен автоматически при каждом Write/Edit/MultiEdit — при уязвимом паттерне печатает предупреждение и блокирует первую такую правку файла за сессию; повторная попытка проходит.',
'Правило PSR_v1 R10.1 блок 1 (audit-security, off-phase). SG1: 5-й PreToolUse-хук, блокирующий (sys.exit 2), одноразовый per «файл+правило» за сессию — economy/ruflo-цепочка не нарушается, +~34 мс/правку. SG2: Windows-починка — bundled hooks.json зовёт python3 (нет в PATH), решено python3.exe-шимом в каталоге Python. Не UI → вне R6.0/R6.1/R14. Tooling §4.15, CLAUDE.md §3.3 #40.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: audit-security' }, { name: 'Tooling', cond: '§4.15 #40 — реестр' }],
[],
[{ name: 'скил security-review', cond: 'оба — D3 audit-security; sec_guidance inline, sk_security_review ручной' }]
),
sk_security_review: nd(
'Кастомизированная Anthropic-команда `/security-review` — копия в `.claude/commands/security-review.md` с проектным FP-фильтром (RLS / ПДн / economy-хуки). Раздел D3. D3 #2.',
'При ручном security-review кода или PR — запуск `/security-review` с проектным контекстом для фильтрации ложных срабатываний.',
'Проектный FP-фильтр: RLS-политики / ПДн-поля / economy-хуки — не считаются уязвимостями. Раздел D3.',
[{ name: 'Tooling', cond: 'D3 #2 — проектная кастомизация' }],
[],
[{ name: 'sec_guidance', cond: 'оба D3 audit-security; sec_guidance — inline warn, security-review — ручной аудит' }, { name: 'audit-portal', cond: 'sk_audit_portal оркеструет security-review как фазу аудита' }]
),
sk_audit_portal: nd(
'Проектный скил — 14-фазный портальный аудит (дистилляция аудитов #1/#2/#3). Покрывает: PHP/Vue статический анализ, RLS, a11y, coverage, зависимости, secrets. Раздел D3.',
'При проведении полного аудита портала — запуск 14 фаз последовательно с документированием находок P0/P1/P2/P3.',
'Раздел D3. Оркеструет несколько инструментов: sk_security_review, Trail of Bits Skills, regression-скил. Находки фиксируются в docs/superpowers/audits/.',
[{ name: 'Tooling', cond: 'D3 — проектный аудит-скил' }],
[{ name: 'скил security-review', cond: 'оркеструет как security-фазу аудита' }, { name: 'Trail of Bits Skills', cond: 'оркеструет для глубоких кампаний' }, { name: 'скил regression', cond: 'использует на фазе тестов' }],
[{ name: 'скил security-review', cond: 'пара в D3 audit-security' }]
),
// ── A3 INTEGRATION-TOOLING (17.05.2026) ──────────
ag_apidocs: nd(
'Агент claude-flow — генерирует OpenAPI-спеку REST API по роутам и контроллерам Laravel. Pattern learning. 0 установки — агент доступен в сессии.',
'При фиксации контракта REST API: генерация/обновление OpenAPI-спеки группы эндпоинтов. Результат — docs/api/.',
'Sub-агент claude-flow — узел карты, но без отдельного номера в реестре Tooling Прил. Н (реестр — plugin-grain; 11 agent-узлов карты так же без Tooling-номеров). Не UI → вне фильтров R6.0/R6.1/R14.',
[{ name: 'CLAUDE.md', cond: '§3.3 — упомянут при #47 openapi-mcp' }],
[],
[{ name: 'MCP: openapi', cond: 'генерирует спеку → openapi-mcp отдаёт её как MCP-ресурс' }]
),
mcp_openapi: nd(
'MCP-сервер (npm, stdio) — отдаёт OpenAPI-спеку как MCP-ресурс/тулы; introspection своей и чужих API при интеграционной разработке.',
'При работе с интеграциями (API/вебхуки) — обращение к структуре OpenAPI-спеки из сессии Claude. READ-ONLY introspection.',
'Правило PSR_v1 R10.1 блок 3 (integration-tooling, off-phase — 9-я подкатегория). stdio-режим, без port-conflict. Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.22 #47, CLAUDE.md §3.3 #47.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 3: integration-tooling' }, { name: 'Tooling', cond: '§4.22 #47 — реестр' }],
[],
[{ name: 'docs/api/', cond: 'источник OpenAPI-спеки' }]
),
// ── A11 ML-AI-TOOLING (17.05.2026) ──────────────
claude_api: nd(
'Скил сборки AI-фич на Anthropic SDK (prompt-кэш). Reuse — раздел A11 опирается также на context7 MCP (доки) и Sentry MCP (LLM-наблюдаемость).',
'При разработке AI-фич на Anthropic API / Claude SDK — скил задаёт паттерны prompt-кэша, batch-запросов, tool use.',
'Reuse-узел раздела A11 (ml-ai-tooling). claude-api — встроенный скил Claude Code, не нумерованная Tooling-позиция; регистрация — Tooling «built-in skills» + PSR_v1 R10.1 блок 2. В A11 — reuse-слой (CLAUDE.md §6). Не UI → вне фильтров R6.0/R6.1/R14.',
[{ name: 'Tooling', cond: 'built-in skill — PSR_v1 R10.1 блок 2 (reuse)' }],
[],
[{ name: 'context7 MCP', cond: 'документация Anthropic SDK' }, { name: 'Sentry MCP', cond: 'LLM-наблюдаемость (off-phase reuse)' }]
),
promptfoo: nd(
'npm-CLI eval LLM-промптов: ассерты, регрессия, red-team. Запуск вручную/CI — не в хуках (платные LLM-вызовы).',
'При разработке и проверке AI-промптов — запуск test-suite promptfoo вручную или в CI. Никогда в pre-commit хук (ML1: платные вызовы).',
'Правило PSR_v1 R10.1 блок 1 note (ml-ai-tooling, off-phase). npm devDependency, тяжёлый (~1090 пакетов). Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.23 #48, CLAUDE.md §3.3 #48.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: ml-ai-tooling' }],
[{ name: 'ML1', cond: 'никогда в хук/pre-commit — платные LLM-вызовы' }],
[]
),
data_scientist: nd(
'Vendored-скил: классический ML-воркфлоу — выбор алгоритма, feature engineering, оценка модели.',
'При ML-задаче (выбор алгоритма, feature engineering, валидация модели) — knowledge-only playbook без генерации кода.',
'Вендорен в .claude/skills/data-scientist/ (ML3 — lefthook markdownlint+cspell исключают через job-exclude). Knowledge-only, не решатель. Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.24 #49, CLAUDE.md §3.3 #49.',
[{ name: 'Tooling', cond: '§4.24 #49 — реестр' }],
[],
[]
),
// ── C10 BUSINESS-PROCESS (17.05.2026) ────────────
ops_plugin: nd(
'Плагин Anthropic operations — 9 скилов бизнес-процессов: документирование, оптимизация, change-management, capacity-планирование.',
'При работе с бизнес-процессом — документировать процесс, спланировать change-request, рассчитать capacity. Marketplace-плагин, 0 lifecycle-хуков.',
'Правило PSR_v1 R10.1 блок 1 (business-process, off-phase). Marketplace `operations@knowledge-work-plugins` v1.2.0, тот же marketplace что #42/#46. Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.26 #51, CLAUDE.md §3.3 #51.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: business-process' }],
[{ name: 'OPS1', cond: 'process-doc → Mermaid-исходник; рендер за mermaid' }, { name: 'OPS5', cond: 'generic ↔ self-authored stack-grounded скилы' }],
[{ name: 'mermaid', cond: 'рендер диаграмм процесса' }]
),
process_modeling: nd(
'Self-authored скил: моделирование to-be бизнес-процесса — BPMN 2.0, карты процессов, RACI, state-машины.',
'При проектировании бизнес-процесса — выбрать артефакт (BPMN/swimlane/journey/RACI), построить модель. Рендер делегируется скилу mermaid.',
'Свой project-скил в .claude/skills/process-modeling/ (не вендоренный → линтуется, конфликт-аудит LINT1). Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.27 #52, CLAUDE.md §3.3 #52.',
[{ name: 'Tooling', cond: '§4.27 #52 — реестр' }],
[{ name: 'BPMN1', cond: 'нотация process-modeling ≠ mermaid рендер' }],
[{ name: 'mermaid', cond: 'рендер BPMN/диаграмм' }, { name: 'process-analysis', cond: 'as-is ↔ to-be пара' }]
),
process_analysis: nd(
'Self-authored скил: анализ as-is бизнес-процесса — discovery из кода Laravel, узкие места, трассировка, метрики.',
'При вскрытии существующего процесса — реконструировать из routes/jobs/audit-логов, найти узкие места, посчитать KPI.',
'Свой project-скил в .claude/skills/process-analysis/ (не вендоренный → линтуется, LINT1). Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.28 #53, CLAUDE.md §3.3 #53.',
[{ name: 'Tooling', cond: '§4.28 #53 — реестр' }],
[{ name: 'PA1', cond: 'процессные узкие места ≠ runtime (perf-analyzer)' }],
[{ name: 'process-modeling', cond: 'as-is ↔ to-be пара' }]
),
// ── DISCOVERY-TOOLING (18.05.2026) ────────────
discovery_interview: nd(
'Self-authored скил: структурированное интервью-discovery до проектирования — FEATURE (JTBD-интервью заказчика) + SYSTEM (ориентация по мета-слою проекта).',
'При расплывчатом проблемном запросе — провести JTBD-интервью, отдать discovery-brief в brainstorming; при «сориентируй по проекту» — синтез по карте/CLAUDE.md/MEMORY/Открытые_вопросы/Tooling.',
'Свой project-скил в .claude/skills/discovery-interview/ (не вендоренный → линтуется, LINT1). Не UI → вне фильтров R6.0/R6.1/R14. Триггер-eval 20/20. Tooling §4.30 #55, CLAUDE.md §3.3 #55, ADR-009.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1 note: discovery-tooling' }, { name: 'Tooling', cond: '§4.30 #55 — реестр' }],
[{ name: 'DI2', cond: 'разрез по слою-источнику с process-analysis (ADR-009)' }],
[{ name: 'process-analysis', cond: 'граница: app-код ↔ голова заказчика/мета-слой' }, { name: 'brainstorming', cond: 'хэндофф FEATURE-brief' }]
),
// ── FINANCE-TOOLING C6+C7 (20.05.2026, ADR-012) ──
finance_plugin: nd(
'Marketplace-плагин (Anthropic): финансы/бухгалтерия — сверка, variance-анализ, US-GAAP-отчётность, закрытие периода, проводки. 8 скилов.',
'При учётно-финансовой работе C7 (и сверке/variance для C6). РФ: reconciliation/variance ✅; US-GAAP-скилы ⚠️; SOX ❌; warehouse-MCP DEFERRED.',
'plugin finance@knowledge-work-plugins (enabledPlugins). Категория finance-tooling, homed C7. Не UI → вне R6/R14. Tooling §4.36 #61, CLAUDE.md §3.3 #61, ADR-012.',
[{ name: 'Tooling', cond: '§4.36 #61 — реестр' }],
[{ name: 'FIN2', cond: 'SOX not-applicable РФ' }, { name: 'FIN3', cond: 'граница с operations #51' }],
[{ name: 'ru-tax-accounting', cond: 'РФ-специфика поверх US-механики' }]
),
billing_audit: nd(
'Self-authored скил: аудит денежных инвариантов биллинга Лидерры — сумма (bcmath), идемпотентность, tier-резолюция, дрейф reconcile, charge_source.',
'При правке/ревью кода Billing — проверить денежную корректность начисления.',
'Свой project-скил .claude/skills/billing-audit/ (линтуется, LINT1). Не UI → вне R6/R14. Tooling §4.37 #62, CLAUDE.md §3.3 #62, ADR-012.',
[{ name: 'Tooling', cond: '§4.37 #62 — реестр' }],
[{ name: 'FIN5', cond: 'объект ≠ process-*/D3/ru-tax' }],
[{ name: 'Pest', cond: 'инварианты через тесты' }, { name: 'Boost', cond: 'модели биллинга' }]
),
ru_tax: nd(
'Self-authored скил: РСБУ/НК РФ контекст для выручки Лидерры — НДС/УСН, налоговая база, налогооблагаемое событие, выгрузки бухгалтеру.',
'При «как учесть/обложить по РФ» — перевод billing-выручки (выход C6) в учётно-налоговый контекст C7.',
'Свой project-скил .claude/skills/ru-tax-accounting/ (линтуется, LINT1). Закрывает РФ-gap US-плагина finance. Не UI → вне R6/R14. Tooling §4.38 #63, CLAUDE.md §3.3 #63, ADR-012.',
[{ name: 'Tooling', cond: '§4.38 #63 — реестр' }],
[{ name: 'FIN6', cond: '≠ finance plugin/billing-audit/D1/D2' }],
[{ name: 'billing-audit', cond: 'выручка C6 → налог.база C7' }, { name: 'finance plugin', cond: 'US-механика' }]
),
// ── A1 BACKEND-TOOLING (20.05.2026, ADR-013) ──
rector: nd(
'Composer dev-dep (Rector + rector-laravel): авто-рефакторинг и version-upgrade PHP-кода — dead-code, code-quality наборы, апгрейды под версию Laravel.',
'При «обнови/почини/рефактори backend-код», апгрейде Laravel-версии, удалении мёртвого кода. Запуск manual/CI (composer rector / rector:fix).',
'Composer dev-dep, app/rector.php (deadCode+codeQuality conservative). manual/CI — НЕ блокирующий lefthook (dry-run baseline 16 файлов). Не UI → вне R6/R14. Tooling §4.39 #64, CLAUDE.md §3.3 #64, ADR-013.',
[{ name: 'Tooling', cond: '§4.39 #64 — реестр' }],
[{ name: 'BT1', cond: '↔ Pint трансформация vs стиль' }, { name: 'BT2', cond: '↔ Larastan чинит vs находит' }, { name: 'BT3', cond: '↔ deptrac vs граф слоёв' }],
[{ name: 'PHP Insights', cond: 'backend-quality chain L14' }, { name: 'Larastan', cond: 'L14 типы' }]
),
php_insights: nd(
'Composer dev-dep: метрики качества кода — complexity / architecture / maintainability (cyclomatic, code smells, распределение архитектуры).',
'При «оцени качество/сложность кода», «где код запутан», в портальном аудите. on-demand/CI (composer insights).',
'Composer dev-dep, app/config/insights.php (SyntaxCheck removed — Windows-краш, style-ось off — владелец Pint). on-demand/CI — НЕ блокирующий (BT9). Не UI → вне R6/R14. Tooling §4.40 #65, CLAUDE.md §3.3 #65, ADR-013.',
[{ name: 'Tooling', cond: '§4.40 #65 — реестр' }],
[{ name: 'BT4', cond: 'style/code оси off — уникум complexity+architecture' }, { name: 'BT9', cond: 'не блокирующий — без четверного гейта' }],
[{ name: 'Rector', cond: 'backend-quality chain L14' }, { name: 'Larastan', cond: 'L14 типы' }]
),
backend_patterns: nd(
'Self-authored скил: backend-конвенции Лидерры — слоистость controller→service→job, RLS-aware Eloquent, деньги bcmath/LedgerService, идемпотентные джобы, partition-aware запросы.',
'При «как писать backend в Лидерре», «паттерн контроллера/сервиса/джоба», scaffolding новой backend-фичи.',
'Свой project-скил .claude/skills/laravel-backend-patterns/ (линтуется, LINT1). Не UI → вне R6/R14. Tooling §4.41 #66, CLAUDE.md §3.3 #66, ADR-013.',
[{ name: 'Tooling', cond: '§4.41 #66 — реестр' }],
[{ name: 'BT5', cond: '≠ architecture-patterns #38 (generic)' }, { name: 'BT6', cond: '≠ billing-audit #62 (аудит)' }],
[{ name: 'billing-audit', cond: '«как писать» ↔ «аудит денег»' }, { name: 'Boost', cond: 'Eloquent-контекст' }]
),
nightowl: nd(
'Self-hosted runtime-телеметрия (laravel/nightwatch + nightowl-agent): коррелированный трейс request↔job↔query↔cache в свой PostgreSQL. DEFERRED.',
'DEFERRED — при появлении Linux/боевого сервера (Б-1). Сейчас не маршрутизировать (нет pcntl/posix на Windows, OSS без MCP, hosted = 152-ФЗ).',
'DEFERRED pending-слот (как Sentry #34 / Figma #44 / Jupyter #50). Spike docs/backend/nightowl-spike.md. Не UI → вне R6/R14. Tooling §4.42 #67, CLAUDE.md §3.3 #67, ADR-013.',
[{ name: 'Tooling', cond: '§4.42 #67 — реестр' }],
[{ name: 'BT7', cond: '↔ Sentry трейс vs ошибки' }, { name: 'BT8', cond: '↔ Pail/Boost трейс vs tail/снапшот' }],
[{ name: 'Sentry', cond: 'трейс ↔ ошибки (ADR-013)' }]
),
// ── СКИЛЫ SUPERPOWERS ────────────────────────────
sk_brainstorm: nd(
'Продумывает задачу вместе с заказчиком, формулирует варианты A/B/C и согласует дизайн до написания кода.',
'При творческой задаче — новая фича, непростой рефакторинг, дизайн-решение; ДО любого кода или плана.',
'Обязательно явное «Approved» от заказчика до перехода к скилу writing-plans. Запрет: нельзя запускать скилы реализации до утверждения дизайна.',
[{ name: 'плагин Superpowers', cond: 'содержит' }, { name: 'Pravila §12', cond: 'обязательное правило (см. §12) для творческих задач' }],
[],
[{ name: 'скил writing-plans', cond: 'вызывается сразу после brainstorming для создания плана' }]
),
sk_tdd: nd(
'Ведёт разработку через написание падающего теста до кода: сначала RED (тест провален), потом GREEN (тест проходит). TDD (разработка через тесты — failing test first).',
'При любом новом боевом коде — backend (Pest) и frontend (Vitest).',
'Падающий тест пишется ДО реализации; формулировка «код должен работать» без проверенного теста — нарушение правила §12.',
[{ name: 'плагин Superpowers', cond: 'содержит' }, { name: 'Pravila §12', cond: 'обязательное правило (см. §12) для любого нового кода' }],
[],
[{ name: 'скил executing-plans', cond: 'TDD встроен в каждый шаг плана выполнения' }]
),
sk_debug: nd(
'Системная отладка: минимум 3 гипотезы, опровержение каждой реальной проверкой до того, как править код — никаких «должно работать».',
'При неожиданном поведении, падении теста, ошибке во время работы, неожиданном выводе.',
'Минимум 3 гипотезы. Опровержение через реальные команды и тесты, а не «логика подсказывает». Никаких «попробую исправить» без подтверждённой причины.',
[{ name: 'плагин Superpowers', cond: 'содержит' }, { name: 'Pravila §12', cond: 'обязательное правило (см. §12) при неожиданном поведении' }],
[],
[{ name: 'MCP-сервер redis', cond: 'используется для отладки очередей Redis' }]
),
sk_wplans: nd(
'Создаёт детальный план реализации с полным кодом, командами и шагами по 2-5 минут.',
'После скила brainstorming (творческая задача) или сразу для многошаговой (≥3 шагов) технической задачи.',
'Никаких заглушек (TBD, TODO, «add validation»). Каждый шаг — реальный код или команда. Покрытие спецификации обязательно.',
[{ name: 'плагин Superpowers', cond: 'содержит' }, { name: 'Pravila §12', cond: 'обязательное правило (см. §12) для многошаговых задач (≥3 шагов)' }],
[],
[{ name: 'скил executing-plans или скил subagent-driven', cond: 'план передаётся в один из них для выполнения' }]
),
sk_eplans: nd(
'Выполняет готовый план шаг за шагом, отмечает чекбоксы, делает коммиты после каждого шага.',
'После скила writing-plans (если выбрано выполнение в текущей сессии); альтернатива — скил subagent-driven-development в той же сессии.',
'Каждый шаг отмечается галочкой, коммиты не объединяются — атомарно по одному за шаг (Pravila §4.2).',
[{ name: 'плагин Superpowers', cond: 'содержит' }],
[],
[{ name: 'скил writing-plans', cond: 'получает план от writing-plans' }, { name: 'скил subagent-driven-development', cond: 'альтернатива — зависит от выбора пользователя' }]
),
sk_verify: nd(
'Обязательная проверка перед заявлением «готово»: запускает тесты, видит реальный вывод, никаких предположений.',
'Перед любым заявлением «готово»/«passed»/«closed»/«merged» — обязательно (правило §12 + экономия 0% как жёсткое требование).',
'Реальный запуск, не «должно пройти». Вывод тестов виден полностью. Выборочные результаты запрещены («tests pass» = ровно столько, сколько действительно прошло).',
[{ name: 'плагин Superpowers', cond: 'содержит' }, { name: 'Pravila §12', cond: 'обязательное правило (см. §12) перед любым заявлением «готово»' }],
[],
[{ name: 'MCP-сервер laravel-boost', cond: 'запросы к БД для проверки данных' }]
),
sk_parallel: nd(
'Разбивает независимые задачи на параллельные потоки с изоляцией через git worktrees (отдельные рабочие копии репозитория).',
'При нескольких независимых рабочих фронтах одновременно (CTO-задача + Plan-задача + audit-fix).',
'Изоляция через worktree обязательна — никакого «работаю в одной директории на 3 ветках сразу». Иначе риск конфликта файлов и веток.',
[{ name: 'плагин Superpowers', cond: 'содержит' }],
[],
[{ name: 'скил worktree', cond: 'parallel-work использует worktree для изоляции' }],
[{ name: 'MCP-сервер playwright', desc: 'Профили per-cwd hash (квирк #95) → worktrees получают разные mcp-chrome-{hash} директории, не конфликтуют. Same-dir parallel — редкий runtime, регулируется Pravila §15.2 claim', type: 'GREEN' }]
),
sk_worktree: nd(
'Создаёт изолированную копию репозитория (worktree) для рискованной или параллельной работы.',
'При параллельной работе нескольких задач или при рискованной работе (рефакторинг с возможным откатом, ветка экспериментов).',
'Очистка через ExitWorktree обязательна. Не оставлять забытые worktree — захламляют файловую систему.',
[{ name: 'плагин Superpowers', cond: 'содержит' }],
[],
[{ name: 'скил parallel-work', cond: 'worktree — инструмент для parallel-work' }]
),
sk_pr: nd(
'Чеклист финальной готовности PR (запроса на слияние кода): тесты, документация, чистота кода, проверки перед push.',
'Перед `gh pr create` или `git push` в общую ветку — обязательная проверка готовности.',
'Проверки перед push (job gitleaks в lefthook по всей истории + job lychee) — не обходить через `--no-verify`. Pravila §4.2.',
[{ name: 'плагин Superpowers', cond: 'содержит' }],
[],
[{ name: 'MCP-сервер github', cond: 'создаёт PR (запрос на слияние кода) через GitHub' }, { name: 'lefthook (job-набор перед push)', cond: 'запускает job gitleaks + job lychee' }]
),
sk_subagent: nd(
'Запускает суб-агентов для крупных задач — каждый в отдельном контексте без накопленного шума.',
'При выполнении плана в той же сессии ИЛИ при делегировании поиска/анализа большого объёма.',
'Суб-агент в режиме экономии 0%: запрашивать полный сырой вывод, а не сводку — решения принимать самому.',
[{ name: 'плагин Superpowers', cond: 'содержит' }],
[
{ name: 'агент Explore', cond: 'запускает для поиска по кодовой базе' },
{ name: 'агент general-purpose', cond: 'запускает для сложных задач' },
{ name: 'агент Plan', cond: 'запускает для архитектурного планирования' }
],
[{ name: 'скил writing-plans', cond: 'subagent-driven — основной способ выполнения планов' }]
),
sk_wskills: nd(
'Создаёт новые скилы по стандартному шаблону: файл SKILL.md, заголовочный блок (frontmatter), описание процесса.',
'При формализации повторяющегося процесса в переиспользуемый скил (после 2-3 примеров одинаковой работы).',
'Шаблон SKILL.md, заголовочный блок (name, description, when_to_use, allowed-tools), описание процесса с DOT-диаграммой.',
[{ name: 'плагин Superpowers', cond: 'содержит' }],
[],
[{ name: 'агент plugin-dev:skill-reviewer', cond: 'агент проверяет созданный скил' }]
),
sk_spreview: nd(
'Проверяет документ-спецификацию на полноту, противоречия, заглушки и объём работ.',
'После скила writing-plans на отдельном этапе ДО реализации — для крупных планов с несколькими задачами.',
'Самопроверка прямо в скиле brainstorming достаточна для небольшой спецификации; для крупных — отдельный запуск этого скила.',
[{ name: 'плагин Superpowers', cond: 'содержит' }],
[],
[{ name: 'скил brainstorming', cond: 'spec-review вызывается в конце brainstorming после записи спека' }]
),
sk_coderev: nd(
'Систематический разбор кода — безопасность, тесты, архитектура, соответствие правилам.',
'Перед слиянием PR (запроса на слияние кода); после крупной серии коммитов; при подготовке к релизу; при подозрении на регрессию (возврат к старому багу).',
'Без выборочности: разбор всех изменений, а не только подозрительных. SAST (статический анализ кода на уязвимости, через MCP-сервер semgrep) включается обязательно.',
[{ name: 'плагин Superpowers', cond: 'содержит' }],
[],
[{ name: 'MCP-сервер semgrep', cond: 'SAST-проверка при ревью кода' }]
),
sk_elements: nd(
'Улучшает написание текстов и документации — ясность, лаконичность, без воды.',
'При написании спецификации/плана/CHANGELOG/описания PR (запроса на слияние кода) — для общения с командой.',
'Без воды. Без «легко», «просто», «всего лишь». Каждое утверждение измеримо.',
[{ name: 'плагин Superpowers', cond: 'содержит' }],
[],
[]
),
// ── СКИЛЫ ПРОЕКТА ────────────────────────────────
sk_rls: nd(
'7-шаговый чеклист RLS (защита строк по тенанту) для новой таблицы: tenant_id, ENABLE RLS (включение защиты строк), политики, права для 5 ролей, CHANGELOG, проверка через squawk, дымовой тест (быстрая проверка функциональности).',
'При создании новой таблицы в db/schema.sql ИЛИ при правках существующих политик RLS (защиты строк по тенанту).',
'Права для 5 ролей обязательны (crm_app_user / crm_app_admin / crm_supplier_worker BYPASSRLS (право обходить защиту строк — для системных задач) / crm_readonly / crm_migrator). Запись в CHANGELOG_schema.md обязательна.',
[{ name: 'Tooling §3.2', cond: 'использует squawk (#15) и команды grep' }],
[],
[{ name: 'MCP-сервер laravel-boost', cond: 'SQL запросы к schema.sql для проверки' }],
[{ name: 'агент rls-reviewer', desc: 'граница задана регламентом (spec 2026-05-16): скил — ручная проверка одной названной таблицы + живой дымовой тест; агент — разбор diff/ветки/PR. См. секцию «Граница с агентом rls-reviewer» в SKILL.md.', type: 'GREEN' }]
),
sk_qitem: nd(
'Добавляет новый открытый вопрос в реестр Открытые_вопросы_v8_3.md с обновлением счётчиков §0 и версии.',
'При появлении нового открытого вопроса (Биз-/CTO-/Ю-/Диз-/DO-/OPEN-) — формальная запись в реестр.',
'Категория (Биз-/CTO-/...) обязательна. Связанные документы (CLAUDE.md/Pravila/PSR_v1/Tooling) — синхронизируются.',
[],
[],
[{ name: 'плагин claude-md-management', cond: 'скил делегирует правку CLAUDE.md через плагин (правило §5п.10 в нормативке)' }]
),
// ── ХУКИ ─────────────────────────────────────────
hk_pre_claude: nd(
'Блокирует прямое редактирование CLAUDE.md — срабатывает на Edit/Write по этому файлу.',
'PreToolUse (перед каждым вызовом инструмента) — перед каждым Edit/Write, фильтр путей нацелен на CLAUDE.md.',
'Обход запрещён. Единственный способ редактировать — плагин claude-md-management (правило §5п.10 в нормативке).',
[{ name: '.claude/settings.json', cond: 'описан как хук PreToolUse' }],
[],
[],
[{ name: 'плагин hookify', desc: 'плагин hookify динамически создаёт новые хуки PreToolUse — может перезаписать или конкурировать с этим хуком', type: 'RED' }]
),
hk_post_md: nd(
'После каждого Edit .md-файла запускает markdownlint --fix автоматически.',
'PostToolUse (после каждого вызова инструмента) — после Edit/Write на *.md (кроме корневого CLAUDE.md, чтобы не зациклить).',
'Не правит CLAUDE.md (исключён из фильтра путей). При неисправимой ошибке (например, битая ссылка) — предупреждение, не блокировка.',
[{ name: '.claude/settings.json', cond: 'описан как хук PostToolUse' }],
[],
[{ name: 'job markdownlint в lefthook', cond: 'дублируют задачу: хук — в сессии, lefthook — при коммите' }]
),
hk_post_schema: nd(
'После правки db/schema.sql напоминает обновить db/CHANGELOG_schema.md.',
'PostToolUse (после каждого вызова инструмента) — после Edit/Write на `db/schema.sql`.',
'Напоминание, не блокировка. Дисциплина ведения CHANGELOG_schema — на разработчике (§4.2 Pravila).',
[{ name: '.claude/settings.json', cond: 'описан как хук PostToolUse' }],
[],
[{ name: 'job squawk в lefthook', cond: 'оба реагируют на изменения SQL' }]
),
hk_session: nd(
'При старте каждой сессии подгружает CLAUDE.md, Pravila и ключевые memory-файлы в контекст.',
'SessionStart (при старте сессии) — единожды при инициализации сессии Claude Code.',
'Список memory-файлов фиксированный — для расширения править настройку хука. Не читает 80+ квирков целиком — выборочно по релевантности.',
[{ name: '.claude/settings.json', cond: 'описан как хук SessionStart' }],
[
{ name: 'память user_profile', cond: 'читает' },
{ name: 'память feedback_environment', cond: 'читает' },
{ name: 'память project_state', cond: 'читает' },
{ name: 'память feedback_superpowers_hard_rule', cond: 'читает' },
{ name: 'память feedback_plugin_paired_stack', cond: 'читает' }
],
[]
),
hk_economy: nd(
'Перед каждым промптом разбирает «экономия X%» и выставляет режим строгости (0% = максимальное качество, 100% = по умолчанию).',
'UserPromptSubmit (перед отправкой промпта пользователя) — ищет шаблон /экономия\\s*(\\d+)%/.',
'§12 Superpowers — hard-rule уровня 0, economy-режим §12 НЕ отменяет ни на каком уровне. Действует только на текущую задачу — следующий промпт разбирается заново.',
[{ name: '.claude/settings.json', cond: 'описан как хук UserPromptSubmit' }],
[],
[],
[{ name: 'плагин Superpowers (§12)', desc: 'Экономия=100% теоретически может «сэкономить» вызов скила — §12 (hard-rule уровня 0) economy-режим не отменяет ни на каком уровне (Pravila §12.4).', type: 'GREEN' }]
),
// ── АГЕНТЫ ───────────────────────────────────────
ag_pest: nd(
'Разбирает падения тестов Pest --parallel: отличает настоящую ошибку от известных квирков (73/77 и др.; квирк 72 устранён 16.05.2026 — commit 0fa1a73).',
'При падении Pest --parallel ИЛИ при дымовом тесте (быстрой проверке функциональности) только из подкаталога (как в аудите Phase 3 SyncSupplierProjectsJobTest).',
'READ-ONLY (только чтение — только читает код, ничего не правит). Каждую гипотезу подтверждает реальным запуском, а не «похоже на квирк».',
[{ name: 'CLAUDE.md §6', cond: 'описывает когда вызывать' }],
[],
[{ name: 'MCP-сервер redis', cond: 'читает Redis для отладки квирка 72 (гонка supplier:session)' }],
[
{ name: 'MCP-сервер redis', desc: 'Квирк 72 (гонка с кэшем Redis при Pest --parallel из подкаталога) устранён 16.05.2026 — commit 0fa1a73, array-стор в тестах. Конфликт закрыт.', type: 'GREEN' },
{ name: 'демон ruflo', desc: 'Worker-jitter фонового демона ruflo (PM2) усиливает частоту Pest-квирков 73/77. Квирк 72 устранён 16.05 — его jitter больше не усиливает. На baseline-регрессии — `pm2 stop ruflo-daemon` (квирк #93, переоценён).', type: 'BLACK' }
]
),
ag_rls: nd(
'Проверяет миграции на соответствие RLS (защите строк по тенанту) — 7 пунктов чеклиста с реальными командами, только чтение.',
'При создании/правке миграции в db/migrations/ ИЛИ при правке db/schema.sql ИЛИ при ревью PR (запроса на слияние кода) с изменениями БД.',
'READ-ONLY (только чтение — только Read/Grep/Glob/Bash) — код не пишет. Не замена скилу rls-check — у них разные сценарии.',
[{ name: 'CLAUDE.md', cond: 'описывает в §6 и в директории агентов' }],
[],
[{ name: 'MCP-сервер laravel-boost', cond: 'SQL запросы к db/schema.sql' }],
[{ name: 'скил rls-check', desc: 'граница задана регламентом (spec 2026-05-16): агент — разбор diff/ветки/PR со статическим 7-пунктовым чеклистом; скил — ручная проверка одной названной таблицы + живой дымовой тест. См. секцию «Граница со скилом /rls-check» в rls-reviewer.md.', type: 'GREEN' }]
),
ag_statusline: nd(
'Настраивает строку состояния Claude Code через правку файла настроек.',
'При запросе пользователя «настрой строку состояния» — редкая разовая задача.',
'Правит только секцию statusline в settings.json, другие части файла не трогает.',
[],
[],
[]
),
ag_guide: nd(
'Отвечает на вопросы про API Claude Code, SDK, MCP-серверы (внешние сервисы-инструменты Claude), хуки, slash-команды.',
'При вопросе про возможности Claude Code/SDK/API — «Can Claude...», «How do I...», «Does Claude...».',
'READ-ONLY (только чтение): ищет в документации, код не правит. Не для отладки кода — только вопросы о платформе.',
[],
[],
[{ name: 'MCP-сервер github', cond: 'при необходимости ищет примеры в репозитории' }]
),
ag_explore: nd(
'Быстрый поиск файлов по шаблону имени или по символу — только чтение, без анализа.',
'При точечном поиске — найти файл по имени или сделать grep по символу/ключевому слову.',
'Не для свободного исследования. Не для ревью/аудита (читает отрывки, теряет контекст за пределами окна чтения).',
[{ name: 'скил subagent-driven-development', cond: 'запускается для задач поиска' }],
[],
[]
),
ag_general: nd(
'Универсальный агент для сложных многошаговых исследований — с полным набором инструментов.',
'При сложных многошаговых задачах исследования или написания кода, когда агента Explore не хватает (нужен анализ, не только поиск).',
'Полный набор инструментов — может писать код. Дороже Explore по токенам.',
[{ name: 'скил subagent-driven-development', cond: 'запускается для основных задач' }],
[],
[]
),
ag_plan: nd(
'Архитектор: разрабатывает планы реализации, находит критичные файлы, разбирает компромиссы.',
'При архитектурном планировании задачи из нескольких компонентов (не для мелкого рефакторинга).',
'READ-ONLY (только чтение — без Edit/Write/NotebookEdit). Возвращает план, сам его не реализует.',
[{ name: 'скил subagent-driven-development', cond: 'запускается для архитектурных задач' }],
[],
[{ name: 'скил writing-plans', cond: 'агент Plan и скил writing-plans решают похожую задачу в разных контекстах' }]
),
ag_hookify: nd(
'Разбирает транскрипты диалогов и ищет поведение, которое стоит предотвратить хуком.',
'При триггере /hookify без аргументов ИЛИ при запросе «look back at this conversation, what mistakes to prevent».',
'READ-ONLY (только чтение — только Read+Grep). Рекомендует хуки, сам их не создаёт — передаёт в плагин hookify.',
[],
[],
[{ name: 'плагин hookify', cond: 'агент передаёт рекомендации в плагин' }]
),
ag_pcreator: nd(
'Создаёт настройку новых агентов по описанию от пользователя.',
'При запросе «create an agent that...» — генерация agent.md по описанию функциональности.',
'Только инструменты Write/Read. Созданного агента сам не проверяет — передаёт в агент plugin-validator.',
[],
[],
[{ name: 'агент plugin-dev:plugin-validator', cond: 'валидатор проверяет созданного агента' }]
),
ag_pvalid: nd(
'Проверяет структуру плагина на корректность — plugin.json, инструменты, манифест.',
'После создания/правки plugin.json или компонентов плагина — превентивная проверка.',
'READ-ONLY (только чтение — Read/Grep/Glob/Bash). Сам не правит — выдаёт список нарушений.',
[],
[],
[{ name: 'агент plugin-dev:agent-creator', cond: 'валидатор и создатель работают в паре' }]
),
ag_skreview: nd(
'Оценивает качество написанного скила — описание, ход работы, примеры, лучшие практики.',
'После создания/правки скила через скил writing-skills — превентивное ревью.',
'READ-ONLY (только чтение — Read/Grep/Glob). Не правит — выдаёт рекомендации.',
[],
[],
[{ name: 'скил writing-skills', cond: 'skill-reviewer проверяет то, что создал writing-skills' }]
),
// ── MCP-СЕРВЕРЫ ──────────────────────────────────
mcp_pw: nd(
'Управляет браузером — снимает скриншоты, кликает, заполняет формы для smoke- и a11y-тестов.',
'При визуальной проверке прототипов (фаза 0), при a11y smoke (axe-core), при UI integration smoke.',
'Не для боевых пользователей. Профиль persistent кэшируется per-cwd hash (квирк #95 в memory) → разные worktrees получают разные mcp-chrome-{hash} директории и не конфликтуют. Конфликт остаётся только при same-dir parallel (две Claude-сессии в одной dir одновременно вызывают browser).',
[{ name: 'CLAUDE.md §3.1 #2', cond: 'активен с фазы 0' }],
[],
[{ name: 'SessionStart хук', cond: 'используется для визуальной проверки прототипов' }],
[{ name: 'parallel-work скил', desc: 'Профили per-cwd hash → worktrees не конфликтуют (квирк #95). Same-dir parallel регулируется Pravila §15.2 claim в CURRENT.md', type: 'GREEN' }]
),
mcp_gh: nd(
'GitHub API — читает/создаёт PR, issues, коммиты, ветки в репозитории CoralMinister/lidpotok.',
'При работе с PR/issues, при поиске в репозитории, при создании PR через скил finishing-pr.',
'Не делать push в main без явного одобрения. Pravila §4: атомарные коммиты, не объединять их через MCP.',
[{ name: 'CLAUDE.md §3.1 #3', cond: 'активен с фазы 0' }],
[],
[{ name: 'finishing-pr скил', cond: 'создаёт PR через этот MCP' }]
),
mcp_boost: nd(
'Laravel Boost — SQL-запросы к dev-БД, схема таблиц, журналы ошибок, поиск по документации Laravel.',
'С первой фазы (старт backend) и далее — при SQL-запросах, поиске в документации Laravel, работе с моделями Eloquent.',
'**READ-ONLY к prod** — `.env.production` не должен попадать в локальный конфиг. Не использовать правила-подсказки Inertia/Livewire/Tailwind/Filament.',
[{ name: 'CLAUDE.md §3.2 #10', cond: 'активен с фазы 1, доступ к prod только на чтение' }],
[],
[{ name: 'ag_rls агент', cond: 'rls-reviewer использует Boost для SQL' }, { name: 'sk_rls скил', cond: 'rls-check использует Boost' }]
),
mcp_semgrep: nd(
'SAST (статический анализ кода на уязвимости) — ищет уязвимости, XSS (внедрение JS), SQLi (внедрение SQL), нарушения правил по шаблонам.',
'На третьей фазе проекта (перед запуском в боевую среду) — при просмотре кода через скил code-review и при автозапуске проверок CI (continuous integration) перед выпуском новой версии.',
'Настройка в .semgrep.yml. Ложные срабатывания документируются прямо в коде.',
[{ name: 'CLAUDE.md §3.4 #25', cond: 'третья фаза — перед запуском в боевую среду' }],
[],
[{ name: 'скил code-review', cond: 'MCP-сервер semgrep используется при разборе кода' }]
),
mcp_sentry: nd(
'Читает ошибки из self-hosted Sentry в Yandex Cloud — события, стектрейсы, метрики. READ-ONLY (только чтение).',
'При расследовании ошибок боевой среды во время работы (после развёртывания Б-1).',
'**READ-ONLY** (org:read/project:read/event:read). Ждёт развёртывания инстанса Sentry по Б-1 (зависит от регистрации ООО).',
[{ name: 'CLAUDE.md §3.3 #34', cond: 'вне основных фаз (для отладки во время работы); ждёт развёртывания Sentry по Б-1' }],
[],
[]
),
mcp_redis: nd(
'Читает Redis/Memurai — ключи, очереди, кэш для отладки гонок (race conditions). СТРОГО READ-ONLY.',
'При отладке очередей Redis (Pest --parallel квирки 73/77), при анализе инвалидации кэша.',
'**СТРОГО READ-ONLY** — никаких DEL/FLUSHDB/SET/LPUSH из Claude (только GET/KEYS/LIST). Источник Anthropic устарел — миграция post-MVP.',
[{ name: 'CLAUDE.md §3.3 #35', cond: 'вне основных фаз (для отладки во время работы); PSR_v1 R10.1 блок 3' }],
[],
[{ name: 'pest-parallel-debugger агент', cond: 'агент использует для квирка 72 (гонка в Redis)' }],
[{ name: 'агент pest-parallel-debugger', desc: 'Квирк 72 (гонка с кэшем Redis при Pest --parallel из подкаталога) устранён 16.05.2026 — commit 0fa1a73. Конфликт закрыт.', type: 'GREEN' }]
),
mcp_21st: nd(
'Генератор стартовых шаблонов UI-компонентов через LLM. Активация только через процедуру R14.4.',
'Только через процедуру PSR_v1 R14.4: предпроверка из 9 условий (брендовый App*? есть аналог в Vuetify? есть уже существующий компонент?).',
'R14.5: не запускать параллельно с FD/UPM. Обязательны: JSX→Vue, Tailwind→utility, shadcn→Vuetify. Pa11y a11y на готовом виде.',
[{ name: 'PSR_v1 R14.4', cond: 'строгая предпроверка: 9 условий перед активацией' }],
[],
[],
[{ name: 'Frontend Design', desc: 'PSR_v1 R14.5: нельзя параллельно — 21st как генератор материала, FD как решатель; риск смешать роли и нарушить R6 (фильтр стека)', type: 'GREEN' }]
),
// ── LEFTHOOK JOBS ─────────────────────────────────
lh_gitleaks: nd(
'Ищет ПДн (персональные данные), токены и API-ключи в файлах, готовых к коммиту. Если находит — коммит блокируется.',
'Перед каждым коммитом — проверяет только те файлы, что добавлены через `git add`.',
'Обход через `--no-verify` запрещён (правило §4.2 Pravila). Находка = блокирующая ошибка, не предупреждение. Известные ложные срабатывания — в файл `.gitleaksignore`.',
[{ name: 'lefthook.yml', cond: 'задача №1 в наборе перед коммитом, без параллельного запуска' }],
[],
[{ name: 'lefthook:gitleaks pre-push', cond: 'версия для push сканирует всю историю' }]
),
lh_mdlint: nd(
'Проверяет и авто-исправляет стиль файлов Markdown перед коммитом.',
'Перед каждым коммитом, когда в нём есть файлы `.md`.',
'Настройки в `.markdownlint-cli2.cjs`. Авто-исправление включено (исправленные файлы авто-сохраняются обратно в коммит).',
[{ name: 'lefthook.yml', cond: 'задача №2 в наборе перед коммитом' }],
[],
[{ name: 'PostToolUse:markdownlint-fix хук', cond: 'делают одно и то же — хук сразу после правки, lefthook при коммите' }]
),
lh_cspell: nd(
'Проверяет орфографию в `.md` файлах по словарю `cspell-words.txt`.',
'Перед каждым коммитом, когда в нём есть файлы `.md`.',
'Словарь: `cspell-words.txt`. Кириллица — в нижнем регистре. Не обходить через `--no-verify` — добавлять валидные слова в словарь.',
[{ name: 'lefthook.yml', cond: 'задача №3 в наборе перед коммитом' }],
[],
[]
),
lh_stylelint: nd(
'Линтует CSS в HTML-прототипах (`web/v8/*.html`).',
'Перед каждым коммитом, когда в нём есть файлы `.html`/`.css`.',
'Настройки Stylelint в `.stylelintrc`. Устаревшие свойства (например `word-break: break-word`) блокируют коммит.',
[{ name: 'lefthook.yml', cond: 'задача №4 в наборе перед коммитом' }],
[],
[]
),
lh_pint: nd(
'Авто-форматирует код PHP по PSR-стандарту (стиль кода PHP). Исправленные файлы авто-сохраняются обратно в коммит.',
'Перед каждым коммитом — на каждый файл `.php` в директории `app/`.',
'Авто-исправление включено. Настройки в `app/pint.json`.',
[{ name: 'lefthook.yml', cond: 'задача №5 в наборе перед коммитом, корень `app/`' }],
[],
[]
),
lh_larastan: nd(
'Статический анализ PHP (Larastan, уровень L9) — находит ошибки типов выше базового уровня.',
'Перед каждым коммитом, когда в нём есть файлы `.php` в `app/`.',
'Базовый уровень `phpstan-baseline.neon` зафиксирован — новые ошибки блокируют коммит. Не повышать базовый уровень без обоснования.',
[{ name: 'lefthook.yml', cond: 'задача №6 в наборе перед коммитом, корень `app/`' }],
[],
[{ name: 'MCP: laravel-boost', cond: 'Boost даёт контекст типов через IDE-подсказки' }]
),
lh_squawk: nd(
'Линтер SQL-миграций — проверяет безопасные шаблоны (без блокировки таблиц, параллельный CREATE INDEX и т.п.).',
'Перед каждым коммитом, когда в нём есть файлы миграций (`database/migrations/*.php` или `db/*.sql`).',
'Настройки в `squawk.toml`. Небезопасные миграции (`ALTER TABLE ADD COLUMN NOT NULL DEFAULT`) запрещены без явной метки `-- squawk-ignore`.',
[{ name: 'lefthook.yml', cond: 'задача №7 в наборе перед коммитом, фильтр путей `*.sql`' }],
[],
[{ name: 'Tooling #15 squawk', cond: 'соответствует записи §3.2 в Tooling' }]
),
lh_eslint: nd(
'Линтует файлы Vue/TypeScript в `app/resources/js/`.',
'Перед каждым коммитом, когда в нём есть файлы `.vue`/`.ts`/`.tsx` в `app/`.',
'Flat-config ESLint 10 + plugin-vue 10. Обход через `--no-verify` запрещён. Ошибки блокируют коммит, предупреждения допустимы.',
[{ name: 'lefthook.yml', cond: 'задача №8 в наборе перед коммитом, корень `app/`' }],
[],
[]
),
lh_gitleaks2: nd(
'Полный скан всей истории коммитов на секреты — строже задачи перед коммитом.',
'pre-push (перед `git push` на удалённый репозиторий) — сканирует историю новых коммитов целиком.',
'Обход через `--no-verify` запрещён (правило §4.2 Pravila). На больших push (200+ коммитов) занимает 30+ секунд.',
[{ name: 'lefthook.yml', cond: 'задача в наборе перед push' }],
[],
[{ name: 'lefthook:gitleaks', cond: 'версия для push строже: проверяет всю историю, а не только staged' }]
),
lh_lychee: nd(
'Проверяет все ссылки в `.md` файлах на битые (`docs/**/*.md`, `db/**/*.md`, корневые `*.md`).',
'pre-push (перед `git push`) — проверяет ссылки во всех `.md` файлах репозитория.',
'Внешние ссылки проверяются с таймаутом 10 секунд; при отсутствии интернета — ошибка. Настройки в `lychee.toml`. Обход через `--no-verify` запрещён.',
[{ name: 'lefthook.yml', cond: 'задача в наборе перед push' }],
[],
[{ name: 'CLAUDE.md', cond: 'проверяет ссылки в CLAUDE.md в том числе' }]
),
// ── MEMORY FILES ─────────────────────────────────
mem_user: nd(
'Профиль заказчика: Дмитрий, Windows Server 2022, VSCode, русский язык, путь к проекту.',
'Читается при старте каждой сессии через хук SessionStart — для языка и предпочтений.',
'Не содержит секретов. При смене заказчика — переписать полностью.',
[],
[],
[{ name: 'SessionStart хук', cond: 'читается при старте каждой сессии' }]
),
mem_comm: nd(
'Стиль общения: короткие команды («а», «б», «делай»), варианты A/B/C, явная фиксация переоткрытий.',
'Читается при работе с заказчиком — чтобы соответствовать его стилю общения.',
'Это не код, а правила коммуникации. Корректировки — только через явный отзыв заказчика.',
[],
[],
[{ name: 'memory:user_profile', cond: 'дополняет профиль заказчика' }]
),
mem_env: nd(
'80+ квирков (особенностей) окружения: специфика Windows Server, баги инструментов, обходные пути.',
'При неожиданном поведении — сначала проверить memory:env на известный квирк.',
'Не дублировать — добавлять только новые квирки. Счётчик квирков актуализируется в `project_state.md`.',
[],
[],
[
{ name: 'pest-parallel-debugger агент', cond: 'квирки 73/77 используются агентом' },
{ name: 'SessionStart хук', cond: 'читается при старте' }
]
),
mem_sp: nd(
'Правило §12 (hard-rule уровня 0) + архитектура хука economy из 6 компонентов — дисциплина вызова скилов.',
'При работе со скилами — для соответствия обязательному правилу §12 Pravila.',
'Описывает архитектуру хука economy (6 компонентов) — менять только при изменении самого хука.',
[],
[],
[{ name: 'economy-mode хук', cond: 'memory описывает архитектуру хука' }]
),
mem_plugins: nd(
'Правила парного стека плагинов, MCP-серверы для отладки, уровневая структура PSR_v1.',
'При работе с плагинами FD/UPM/21st/Sentry/Redis MCP — для уровневого разделения по PSR_v1.',
'Синхронизируется с PSR_v1 — изменения в memory только если изменился сам PSR_v1.',
[],
[],
[{ name: 'PSR_v1', cond: 'memory отражает актуальные версии PSR_v1' }]
),
mem_state: nd(
'Текущее состояние проекта: ветка, тесты (Pest/Vitest), последние коммиты, активные задачи.',
'Читается при старте сессии — для быстрого контекста; обновляется после крупных вех.',
'Может устаревать — перечитать при сомнении. Не доверять данным старше 2-3 дней без проверки.',
[],
[],
[{ name: 'SessionStart хук', cond: 'читается при старте для быстрого контекста' }],
[{ name: 'память ruflo', desc: 'Два хранилища памяти не синхронизированы: проектные `memory/*.md` (16 файлов) и база ruflo `.swarm/memory.db`. Фактически память ruflo почти пуста — MCP-сервер репортит 0 записей (+2 тестовых призрака от alpha-бага HNSW #1122). Recall-хук срабатывает на каждый промпт, но извлекать ему почти нечего.', type: 'BLACK' }]
),
mem_phase1: nd(
'Стратегия фазы 1: нативный стек Windows без Docker, расширение pg_partman заменено Artisan-задачей в cron.',
'При работе с инфраструктурой фазы 1 (PG/Redis/PHP-CLI нативно на Windows).',
'Стратегия зафиксирована до закрытия блокера Б-1 (Managed PG в Yandex Cloud) или 6 месяцев — пересмотр указан в файле.',
[],
[],
[]
),
mem_archive: nd(
'Карта источников истины: версии всех 13+ ключевых документов проекта.',
'При вопросах «какая версия документа X» или «где источник истины для Y».',
'Синхронизируется с §0 CLAUDE.md. Изменения в memory — только если изменился §0 CLAUDE.md.',
[],
[],
[{ name: 'CLAUDE.md', cond: 'memory синхронизирует версии с §0 CLAUDE.md' }]
),
mem_github: nd(
'Репозиторий GitHub CoralMinister/lidpotok: HEAD, хуки, правила push.',
'При работе с GitHub — push, PR, операции с ветками.',
'Не делать `push --force` на main (предупреждение в Pravila). Хуки перед push обязательны.',
[],
[],
[{ name: 'MCP: github', cond: 'MCP и memory дополняют друг друга для работы с GitHub' }]
),
mem_handoff: nd(
'Дизайн-передача от Платона: что из `liderra_v8_handoff/` используем, что нет.',
'При UI/дизайн-задачах — для разделения «брендбук используем» vs «состав фич — по ТЗ v8.5».',
'Передача — только дизайн/токены/компоненты. Функционал и состав экранов — НЕ из передачи (берём из ТЗ).',
[],
[],
[]
),
mem_audit: nd(
'Полный аудит портала 13.05.2026: 38 находок, вердикт жёлтый, 10 отложенных вопросов закрыты.',
'При вопросах про аудит или его последствия (Q.DEFER, распределение P0/P1/P2).',
'Это снимок состояния — не редактировать при последующих аудитах, создавать новые memory-файлы.',
[],
[],
[]
),
mem_supplier: nd(
'Прогресс интеграции с поставщиком лидов (планы 1-5): задачи, коммиты, блокеры.',
'При работе с интеграцией поставщика (планы 1-5) — для текущего состояния и блокеров.',
'Блокеры (Б-1, доступы) — внешние, не разрешаются силами Claude. Только отслеживание.',
[],
[],
[]
),
mem_brain: nd(
'Claude Brain v1.0 — отдельный репозиторий «мозга», тег `brain-v1.0`, скрипт `install.sh`.',
'При работе с репозиторием brain или скриптом `install.sh` для синхронизации потребителей.',
'Push на репозиторий brain в GitHub заблокирован (вопрос 8.2). Не пытаться push без разрешения.',
[],
[],
[]
),
mem_redesign: nd(
'Редизайн Quiet Luxury: 20 коммитов, базовый CSS + composables + переписанный AppSidebar.',
'При работе с редизайном портала (frontend, AppSidebar, базовый CSS).',
'Бэклог итерации I2 — в §15 спека, 10 пунктов отложены. Не реализовывать пункты I2 без явного запроса.',
[],
[],
[]
),
mem_devindices: nd(
'Dev Element Indices — временная фича обратной связи для разработки; к удалению в продакшене.',
'При работе с dev-фидбеком (например «1030 измени цвет») — для соответствия номер → элемент.',
'**ВРЕМЕННАЯ** — заказчик прямо сказал «уберём в конечном релизе». Не вкладываться в долгосрочную инфраструктуру.',
[],
[],
[]
),
// ── RUFLO ОРКЕСТРАТОР (фактический реколлаж iter5) ──
ruflo_queen: nd(
'Queen оркестратора ruflo v3.7.0-alpha.38 — стратегическая «королева» роя hive-mind (топология hierarchical-mesh, консенсус byzantine). С реколлажа 16.05.2026 (CLAUDE.md §3.5, Tooling §4.10) — advisory/automation-подсистема, не entry-point: рой работает параллельно, Клод — напрямую.',
'Запускается только по триггеру queen/королева в промпте (Pravila §14 — hard-rule маршрутизации). Без триггера рой простаивает — Клод работает напрямую.',
'Фактическая инспекция рантайма 15.05.2026: Queen активна (term 1, нагрузка 0), но за всё время — 0 задач, 0 раундов консенсуса, 0 общей памяти. Реколлаж 16.05.2026 привёл нормативку к этому факту — ruflo переописан в advisory/automation-подсистему, декларация уровня −1 убрана. Alpha-версия, LLM API-ключей нет.',
[
{ name: 'Pravila §14', cond: 'queen-триггер — hard-rule маршрутизации задачи через Queen' },
{ name: 'CLAUDE.md §3.5 / Tooling §4.10', cond: 'нормативно описан как advisory/automation-подсистема' }
],
[
{ name: '10 воркеров hive-mind', cond: 'координирует рой — все 10 простаивают' },
{ name: 'каталог агентов ruflo', cond: 'ruflo init высыпал в .claude/agents/ — не задействовано' },
{ name: 'slash-команды ruflo', cond: 'ruflo init высыпал в .claude/commands/ — не задействовано' },
{ name: 'плагины ruflo', cond: 'установлено 0 из ~20 в реестре' }
],
[
{ name: 'memory:project_ruflo_integration', cond: 'memory-файл документирует интеграцию' },
{ name: 'ruflo MCP', cond: 'MCP-сервер экспонирует инструменты управления роем' }
],
[{ name: 'Pravila', desc: 'iter4iter5: нормативка декларировала ruflo Queen-led routing уровнем 1 (overlord над всей иерархией) — расходилось с рантаймом (рой idle, 0 задач, 0 раундов консенсуса). Реколлаж 16.05.2026 (Pravila v1.16 / CLAUDE.md v2.2 / PSR_v1 v3.2 / Tooling v2.2) привёл нормативку к факту: ruflo переописан в advisory/automation-подсистему, уровень −1 убран. Конфликт «декларация ≠ рантайм» закрыт.', type: 'GREEN' }]
),
ruflo_workers: nd(
'Рабочие агенты роя hive-mind ruflo — 10 штук. Все одного типа (generic worker), без специализации. На карте до iter5 рисовались 9 «ролей» (Архитектор/Кодер/…) — таких ролей в рантайме не существует.',
'По задумке — Queen раздаёт воркерам подзадачи. Фактически — ни разу.',
'Инспекция рантайма 15.05.2026: 10 воркеров, все в статусе «простаивает» (idle), у каждого 0 выполненных задач. LLM API-ключей нет → реальная агентская работа невозможна. Последний рой запущен с тестовой задачей «ответь словом READY и ничего не меняй».',
[{ name: 'ruflo Queen', cond: 'подчинены — Queen-led иерархия hive-mind' }],
[],
[]
),
ruflo_daemon: nd(
'Фоновый демон ruflo под управлением PM2 (процесс `ruflo-daemon`). По расписанию запускает 5 воркеров: map (каждые 15 мин), audit (10 мин), optimize (15 мин), consolidate (30 мин), testgaps (20 мин). Переживает перезагрузку через планировщик задач Windows.',
'Работает постоянно в фоне.',
'Инспекция рантайма 15.05.2026: воркеры audit / optimize / testgaps пытаются запустить `claude` и КАЖДЫЙ РАЗ падают с ошибкой «файл не найден» (spawn claude ENOENT) — результат пустой (за сутки: audit 29 запусков, optimize 19, testgaps 14 — все пустые). Журнал демона при этом помечает их «успешными» — расхождение метрики и факта. Локально работают только воркеры map и consolidate (без вызова `claude`). Worker-jitter усиливает частоту Pest-квирков 73/77 (квирк 72 устранён 16.05) — на baseline-регрессии нужно `pm2 stop ruflo-daemon`.',
[],
[],
[{ name: 'память ruflo', cond: 'воркер consolidate обращается к хранилищу' }],
[{ name: 'агент pest-parallel-debugger', desc: 'Worker-jitter фонового демона ruflo (PM2) усиливает частоту Pest-квирков 73/77. Квирк 72 устранён 16.05 (commit 0fa1a73) — его jitter больше не усиливает. На baseline-регрессии — `pm2 stop ruflo-daemon` (квирк #93, переоценён).', type: 'BLACK' }]
),
ruflo_mcp: nd(
'MCP-сервер ruflo (внешний сервис-инструмент Клода) — отдельный процесс `ruflo mcp start`, режим stdio, 7-й сервер в `.mcp.json`. Экспонирует ~210 инструментов (агенты / память / рой / хуки / нейросеть и др.).',
'Инструменты доступны Клоду постоянно. Это единственная по-настоящему рабочая точка интеграции ruflo — через неё и собрана фактическая инспекция 15.05.2026.',
'Клод НЕ обязан вызывать ruflo-инструменты — отсюда статус ruflo как параллельной подсистемы, а не overlord. Память, опрашиваемая через MCP, почти пуста (0 записей). Alpha-версия.',
[],
[],
[
{ name: 'ruflo Queen', cond: 'экспонирует инструменты управления роем' },
{ name: 'память ruflo', cond: 'читает/пишет через инструменты memory_*' }
]
),
ruflo_memory: nd(
'Хранилище памяти ruflo — файл базы `.swarm/memory.db` (SQLite через sql.js) + векторный индекс HNSW с реальными embeddings Xenova/all-MiniLM-L6-v2 (384 измерения).',
'Должно накапливать факты между сессиями; recall-хук и MCP-инструменты обращаются к нему.',
'Инспекция рантайма 15.05.2026: MCP-сервер репортит 0 записей. В базе — 2 тестовых «призрака» (h7-fixed-verify, hook-e2e) от alpha-бага HNSW #1122 (`memory delete` убирает строку, но не вектор). Проектные `memory/*.md` (16 файлов) в неё не проиндексированы. Наполняется только вручную через `ruflo memory store`.',
[],
[],
[
{ name: 'ruflo MCP', cond: 'MCP читает/пишет через инструменты memory_*' },
{ name: 'хук recall', cond: 'recall-хук запускает поиск по памяти' },
{ name: 'демон ruflo', cond: 'воркер consolidate обращается к памяти' }
],
[{ name: 'memory:project_state', desc: 'Два хранилища памяти не синхронизированы: проектные `memory/*.md` (16 файлов) и база ruflo `.swarm/memory.db`. Фактически память ruflo почти пуста — MCP-сервер репортит 0 записей (+2 тестовых призрака от alpha-бага HNSW #1122). Recall-хук срабатывает на каждый промпт, но извлекать ему почти нечего.', type: 'BLACK' }]
),
ruflo_recall_hook: nd(
'Хук типа UserPromptSubmit, зарегистрирован в `.claude/settings.json` — скрипт `tools/ruflo-recall-hook.mjs`. На каждый промпт пользователя запускает `ruflo memory search` и подмешивает топ-3 найденных записи в контекст. Единственный хук ruflo, реально вшитый в сессию Claude Code.',
'Перед каждым промптом пользователя.',
'Срабатывает «мягко» (fail-open): при ошибке/таймауте — пустой инжект, промпт не блокируется. Хук работает (виден в системных напоминаниях этой сессии), но память ruflo почти пуста — извлекать почти нечего (recall возвращает 2 тестовых призрака). У самого ruflo есть 26-27 внутренних хуков, но в `.claude/settings.json` вшит только этот один.',
[],
[],
[{ name: 'память ruflo', cond: 'запускает поиск по памяти ruflo' }]
),
ruflo_agents_catalog: nd(
'Каталог определений агентов, который `ruflo init` высыпал в `.claude/agents/` — 100 файлов в 23 категориях (core, consensus, sparc, github, v3, flow-nexus, optimization, sublinear, templates и др.). Из них 98 — от ruflo, 2 — проектные (rls-reviewer, pest-parallel-debugger).',
'Определения видны Claude Code как доступные типы суб-агентов.',
'Статичные файлы-заготовки. «ruflo использует каталог» — нет: воркеры роя безымянные generic, у демона свой набор воркеров. Каталог просто лежит.',
[{ name: 'ruflo Queen', cond: 'высыпан установкой ruflo init' }],
[],
[]
),
ruflo_commands: nd(
'Slash-команды, которые `ruflo init` высыпал в `.claude/commands/` — 88 файлов (категории sparc, github, hooks, analysis, automation, optimization, monitoring).',
'Доступны как slash-команды Claude Code.',
'Статичные файлы. Это ближайший аналог «скилов» у ruflo — но НЕ Claude Code скилы; в `.claude/skills/` ruflo не положил ничего (там только 2 проектных скила). Команды лежат, в работе проекта не задействуются.',
[{ name: 'ruflo Queen', cond: 'высыпаны установкой ruflo init' }],
[],
[]
),
ruflo_plugins: nd(
'Плагины ruflo. В IPFS-реестре оркестратора заявлено ~20 плагинов. Установлено — 0.',
'Никогда — ни один плагин не подключён.',
'Фактически: папки `.claude-flow/plugins/` не существует, ни один плагин не установлен. `ruflo plugins list` зависает на IPFS-discovery (реестр недоступен — Pinata/Cloudflare не отвечают). Отдельно: Claude Code скилов ruflo не привнёс — в `.claude/skills/` только 2 проектных (rls-check, q-item-add). Прямой ответ на вопрос «какие плагины и скилы использует ruflo»: ноль и ноль.',
[{ name: 'ruflo Queen', cond: 'часть установки ruflo' }],
[],
[]
),
mem_ruflo: nd(
'Memory-файл `project_ruflo_integration` — история ruflo big-bang (установка, нормативная инверсия, активация рантайма) и alpha-баги.',
'При работе с ruflo — для контекста интеграции и известных alpha-багов.',
'Снимок на 15.05.2026. iter5 карты опирается на фактическую инспекцию рантайма, а не только на этот файл.',
[],
[],
[{ name: 'ruflo Queen', cond: 'документирует ruflo-интеграцию' }]
),
// ── АУДИТ-АКТУАЛИЗАЦИЯ 16.05.2026 ────────────────
skill_creator: nd(
'Плагин Anthropic для создания новых скилов — eval-driven подход: датасеты сценариев, train/test split, бенчмарк-цикл.',
'При формализации повторяющегося процесса в скил с проверяемым выводом (генерация кода, преобразование файлов).',
'Включён в настройках (~/.claude/settings.json). Для discipline-скилов (TDD-типа) предпочтительнее скил writing-skills плагина Superpowers — у них разные философии.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1 #56: authoring-tooling (ADR-010)' }],
[],
[{ name: 'скил writing-skills', cond: 'обе создают скилы — skill-creator eval-driven, writing-skills через TDD' }]
),
claude_setup: nd(
'Плагин Anthropic — рекомендатель автоматизаций (claude-automation-recommender): анализирует репозиторий и советует, какие MCP-серверы, скилы, хуки, суб-агентов добавить.',
'При настройке/ревизии автоматизации проекта — «чего не хватает в тулчейне».',
'Включён в настройках (~/.claude/settings.json). Рекомендации — совещательные, решение за заказчиком.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1 #59: dev-support — рекомендации фильтруются R0/R10.1 (CCS1, ADR-010)' }],
[],
[]
),
plugin_dev: nd(
'Плагин Anthropic для разработки плагинов Claude Code — 7 скилов (структура плагина, разработка скилов / агентов / хуков / команд, интеграция MCP, настройки).',
'При создании или правке плагина и его компонентов.',
'Включён в настройках. Содержит 3 агента, уже представленные на карте (agent-creator / plugin-validator / skill-reviewer).',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1 #57: authoring-tooling — только для marketplace-плагинов, не для вендоренного/self-authored (PD1, ADR-010)' }],
[
{ name: 'агент plugin-dev:agent-creator', cond: 'входит в плагин' },
{ name: 'агент plugin-dev:plugin-validator', cond: 'входит в плагин' },
{ name: 'агент plugin-dev:skill-reviewer', cond: 'входит в плагин' }
],
[]
),
context7: nd(
'Плагин Anthropic — актуальная документация библиотек / фреймворков / API через MCP-инструменты query-docs и resolve-library-id.',
'При вопросах по библиотеке / фреймворку / SDK / CLI — синтаксис API, конфигурация, миграция версий. Предпочтительнее веб-поиска для документации библиотек.',
'Включён в настройках. Не для рефакторинга / отладки бизнес-логики / ревью — только документация.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1 #60: dev-support — первый выбор для документации библиотек; WebFetch/WebSearch как fallback (CTX1, ADR-010)' }],
[],
[]
),
hk_self_check: nd(
'Хук SessionStart — economy-self-check.py: при старте сессии восстанавливает уровень экономии из файла состояния и проверяет согласованность.',
'SessionStart — единожды при инициализации сессии.',
'Часть архитектуры economy-хука из 6 компонентов (memory feedback_superpowers_hard_rule). §12 economy-режим не отменяет.',
[{ name: '~/.claude/settings.json', cond: 'хук SessionStart (user-level)' }],
[],
[{ name: 'хук economy-mode', cond: 'оба — часть системы экономии' }]
),
hk_skill_marker: nd(
'Хук PreToolUse на вызов Skill — skill-marker.py: фиксирует факт инвокации скила в состоянии сессии (пара со skill-check).',
'PreToolUse, matcher Skill — перед каждым вызовом скила.',
'Часть пары skill-marker / skill-check — runtime-энфорсмент §12 (скил инвокируется первым).',
[{ name: '~/.claude/settings.json', cond: 'хук PreToolUse (user-level)' }],
[],
[{ name: 'хук skill-check', cond: 'пара: marker фиксирует вызов скила, check проверяет' }]
),
hk_skill_check: nd(
'Хук PreToolUse на Edit/Write/MultiEdit — skill-check.py: проверяет, был ли инвокирован подходящий скил перед правкой кода (§12 дисциплина).',
'PreToolUse, matcher Edit|Write|MultiEdit.',
'Пара со skill-marker. Энфорсит §12 — нельзя править код, минуя обязательный скил.',
[{ name: '~/.claude/settings.json', cond: 'хук PreToolUse (user-level)' }],
[],
[{ name: 'хук skill-marker', cond: 'пара skill-marker / skill-check' }]
),
hk_state_guard: nd(
'Хук PreToolUse на Edit/Write/MultiEdit/Bash/Agent — economy-state-guard.py: ловит обходы режима экономии (в т.ч. Bash-обход Edit и наследование суб-агентами).',
'PreToolUse, matcher Edit|Write|MultiEdit|Bash|Agent.',
'Часть системы экономии из 6 компонентов. Закрывает bypass-пути (Bash вместо Edit, суб-агенты).',
[{ name: '~/.claude/settings.json', cond: 'хук PreToolUse (user-level)' }],
[],
[{ name: 'хук economy-mode', cond: 'оба — система экономии' }]
),
hk_postcompact: nd(
'Хук PostCompact — economy-postcompact.py: после компактификации сессии переинжектит состояние режима экономии в контекст.',
'PostCompact — после сжатия истории сессии.',
'Часть системы экономии из 6 компонентов — гарантирует, что режим переживает компакт.',
[{ name: '~/.claude/settings.json', cond: 'хук PostCompact (user-level)' }],
[],
[]
),
hk_verifier: nd(
'Хук Stop — агент-верификатор (модель Sonnet 4.6): после ответа проверяет соответствие режиму экономии — заявления «готово» без тестов, правки без тестов, выборочные результаты.',
'Stop — после каждого ответа Claude (кроме тривиальных Q&A-ходов).',
'Решение decision:block при нарушении. На уровне экономии 5 — short-circuit {compliant:true}. Стоит денег (вызовы Sonnet).',
[{ name: '~/.claude/settings.json', cond: 'хук Stop типа agent (user-level)' }],
[],
[{ name: 'скил verification-before-completion', cond: 'верификатор энфорсит то, что требует скил' }]
),
hk_ruflo_queen: nd(
'Хук UserPromptSubmit — ruflo-queen-hook.mjs: при триггер-словах queen/королева в промпте инжектит жёсткую директиву маршрутизации задачи через ruflo Queen (Pravila §14).',
'UserPromptSubmit — перед каждым промптом; срабатывает на queen / королева.',
'Энфорсит §14 Pravila (hard-rule). Перед платным спавном роя — превью. Зарегистрирован в project .claude/settings.json.',
[
{ name: '.claude/settings.json', cond: 'хук UserPromptSubmit (project-level)' },
{ name: 'Pravila §14', cond: 'энфорсит queen-триггер' }
],
[],
[{ name: 'ruflo Queen', cond: 'хук маршрутизирует queen-задачи на Queen' }]
),
sk_regression: nd(
'Проектный скил /regression — единый прогон регрессии: Pest --parallel + Vitest + сборка, парсинг результатов (JSON-first для pest --parallel, см. квирк 94).',
'Перед коммитом/пушем или при запросе полной регрессии — единая сводка по тестам.',
'Реализация — .claude/skills/regression/ (SKILL.md + run.mjs + run.test.mjs). parsePest: JSON.parse строки {"tool":"pest"}, regex — fallback.',
[],
[],
[{ name: 'агент pest-parallel-debugger', cond: 'при падениях Pest --parallel передаёт разбор агенту' }]
),
mem_audit_b: nd(
'Memory-файл audit_B_status — статус и находки аудитов D и B (закрыты, 6 коммитов, 34/34 RLS после хотфикса).',
'При вопросах про аудиты D / B — историческая запись, аудит не запускать повторно.',
'Снимок-история. Не редактировать — новые аудиты в новые файлы.',
[], [], []
),
mem_audit_c: nd(
'Memory-файл audit_C_pending — аудит C, полностью реализован 07.05 (4 коммита).',
'При вопросах про аудит C — историческая запись, не запускать повторно.',
'Снимок-история. Детали — реестр §13.10, CHANGELOG §Y.',
[], [], []
),
mem_suppliercrm: nd(
'Memory-файл supplier_crm — бизнес-модель поставщика лидов crm.bp-gr.ru: B1/B2/B3 = платформы-источники, проекты = каналы, сделки = лиды, 14 статусов.',
'При работе с интеграцией поставщика — для доменной модели.',
'Доменное описание, синхронизировано со схемой Лидерры.',
[], [], []
),
mem_audit12: nd(
'Memory-файл full_audit_2026-05-12 — аудит портала 12.05 + post-audit + закрытия Q.DEFER.003/004.',
'При вопросах про аудит 12.05 и его последствия.',
'Снимок-история. Содержит уроки про axe-core race-condition и mis-attribution квирка 72.',
[], [], []
),
mem_audit14: nd(
'Memory-файл full_audit_2026-05-14 — аудит портала #3 (14 фаз, 26 находок, вердикт зелёный).',
'При вопросах про аудит #3 от 14.05.',
'Снимок-история. Не редактировать при последующих аудитах.',
[], [], []
),
mem_sprint1: nd(
'Memory-файл sprint1_p0_closure — закрытие 5 P0 UI-находок аудита (DealsView, Kanban DnD, BulkActionsBar, Admin-экраны).',
'При вопросах про Sprint 1 / P0-фиксы портала.',
'Снимок-история спринта. 10 атомарных коммитов.',
[], [], []
),
mem_sprint2: nd(
'Memory-файл sprint2_p1_progress — Sprint 2 P1 wave 1: планы A (Auth) / B (Settings) / C (Billing) — закрыты и запушены.',
'При вопросах про Sprint 2 / P1-фиксы.',
'Снимок-история. Содержит коррекцию: SyncSupplierProjectsJobTest — реальный баг времени, не квирк 72.',
[], [], []
),
mem_sprint3: nd(
'Memory-файл sprint3_progress — Sprint 3 (P1 wave 2): под-планы 3A-3F, 3A-3D закрыты и запушены, 3E-3F в ожидании.',
'При вопросах про Sprint 3 / P1 wave 2.',
'Снимок-история, обновляется по ходу спринта.',
[], [], []
),
// ── BRAIN GOVERNANCE iter9 (19.05.2026, ADR-011) ──
router_procedure: nd(
'Единый источник истины процедуры роутера «задача → узел(ы)» — docs/router-procedure.md v1.0. 5 шагов: hard-floor (§12/§14/§15) → классификация → выбор по триггерам (Tooling Прил. Н §4.X) → проверка связок L1-L12 → исполнение. ADR-011.',
'При любой задаче (имплицитно) определяет узел/связку; явно — при разборе routing-решений и в /brain-retro.',
'Не вводит новый реестр — формализует процедуру над существующим (Tooling §4.X). Кэша «проверенных цепочек» нет (router-only). Каждая задача — свежая сборка пути.',
[{ name: 'Pravila §12/§14/§15', cond: 'hard-floor — шаг 1 процедуры' }, { name: 'CLAUDE.md §3.6', cond: 'cross-ref на router-procedure.md' }],
[{ name: 'Tooling Прил. Н §4.X', cond: 'реестр узлов — вход шага 3' }],
[{ name: 'observer (Stop-хук)', cond: 'пишет evidence о routing-решениях' }, { name: '/brain-retro', cond: 'факторный анализ routing' }],
[]
),
observer_stophook: nd(
'Stop-хук observer (tools/observer-stop-hook.mjs, project-level) — пишет один JSONL-эпизод в docs/observer/episodes-YYYY-MM.jsonl в конце каждого хода + routing-gate. Внутри: transcript-parser (схема v2), routing-detector + choice-detector (provenance), pii-filter (маскирование ПДн). ADR-011 + observer factor-analysis.',
'Конец каждого хода (Stop-event). routing-gate: при навязанном методе без routing-тега → decision:block (необойдёмо).',
'Только пишет evidence, не вмешивается в нормативку. При внутреннем отказе — маркер observer_error, не тихий пропуск. HK1 §5.3: сосуществует с economy-verifier на Stop (append-chain).',
[{ name: 'Pravila §16', cond: 'observer + routing-тег-дисциплина' }, { name: '.claude/settings.json', cond: 'зарегистрирован как Stop-хук' }],
[{ name: 'observer-transcript-parser / routing-detector / choice-detector / pii-filter', cond: 'внутренние .mjs модули' }],
[{ name: 'docs/observer/ evidence', cond: 'пишет эпизоды' }, { name: '/brain-retro', cond: 'читает то, что хук пишет' }],
[{ name: 'hk_verifier', desc: 'HK1 §5.3: оба на Stop-event — коллизии нет (append-chain), оба decision:block отрабатываются', type: 'GREEN' }]
),
sk_brain_retro: nd(
'Проектный скил /brain-retro (.claude/skills/brain-retro/) — раз в спринт читает docs/observer/episodes-*.jsonl и строит факторный анализ: распределение path_type, топ-узлы/связки, вывод исхода, факторная матрица (9 осей × outcome). Анализатор tools/brain-retro-analyzer.mjs.',
'Раз в спринт по команде заказчика («брейн-ретро»). Read-only агрегатор.',
'Только читает и предлагает кандидатов на корректировку нормативки — не пишет в логи, не правит Tooling/Pravila/PSR_v1. Решение по правкам — за заказчиком.',
[{ name: 'Pravila §16', cond: 'evidence-loop, раз в спринт' }, { name: 'PSR_v1 R16', cond: 'brain evidence loop' }],
[{ name: 'tools/brain-retro-analyzer.mjs', cond: 'детерминированный анализатор' }],
[{ name: 'docs/observer/ evidence', cond: 'читает эпизоды' }],
[]
),
observer_evidence: nd(
'Хранилище evidence «мозга» — docs/observer/: помесячные episodes-YYYY-MM.jsonl (схема v2), STATUS.md (панель C1-C5), .read-counter.json (для C3), notes/. Визуализируется страницей docs/observer/dashboard.html (Карта/Лента/Разбор/Агрегат/конфликты; кормится из общего automation-graph-data.js).',
'Пишется Stop-хуком (эпизоды) + контролёрами (STATUS.md, счётчик); читается /brain-retro и dashboard.',
'ПДн маскируется pii-filter перед записью (§5.4). Помесячное rotation; архив после 12 месяцев. Память ruflo (.swarm/memory.db) — отдельное хранилище, не связано.',
[{ name: 'observer Stop-хук', cond: 'источник эпизодов' }],
[],
[{ name: '/brain-retro', cond: 'читатель' }, { name: 'C3/C4/C5 контролёры', cond: 'счётчик / STATUS / покрытие' }],
[]
),
lh_l1watcher: nd(
'Контролёр C1 (lefthook pre-commit job 11, tools/l1-watcher.mjs) — детектор «плагин включён в settings.json без формализации в Tooling Прил. Н». Закрывает трижды повторившийся L1-паттерн (UPM/21st, Sentry/Redis, Anthropic dev-tooling). 0 LLM-вызовов.',
'pre-commit при правке .claude/settings.json или docs/Tooling_v8_3.md.',
'STRICT: блокирует коммит при drift. Групповые/human-имена разрешаются через tools/.l1-watcher-aliases.txt. ADR-011 spec §6.1.',
[{ name: 'lefthook.yml', cond: 'job 11 pre-commit' }, { name: 'ADR-011 §6.1', cond: 'C1' }],
[],
[{ name: 'tooling', cond: 'сверяет settings.json ↔ Tooling' }, { name: 'C2 cross-ref', cond: 'оба — нормативная консистентность' }],
[]
),
lh_crossref: nd(
'Контролёр C2 (lefthook pre-commit job 12, tools/cross-ref-checker.mjs) — детектор version drift между нормативными файлами (Tooling v2.11 collision 17.05). Сверяет версии в §0 cross-refs vs шапки целевых файлов. 0 LLM-вызовов.',
'pre-commit при правке Pravila / Tooling / PSR_v1 / CLAUDE.md / MEMORY.md.',
'STRICT: блокирует коммит при расхождении версии. Link-anchored детекция + scope-cut по history-маркерам (исторические «наследие»-цепочки не дают ложных срабатываний). ADR-011 spec §6.2.',
[{ name: 'lefthook.yml', cond: 'job 12 pre-commit' }, { name: 'ADR-011 §6.2', cond: 'C2' }],
[],
[{ name: 'claude_md / pravila / tooling / psr_v1', cond: 'сверяет 5 нормативных файлов' }, { name: 'C1 l1-watcher', cond: 'оба — нормативная консистентность' }],
[]
),
lh_obs_obs: nd(
'Контролёр C3 (lefthook pre-commit job 13, tools/observer-of-observer.mjs) — счётчик чтений docs/observer/ + 54-недельный self-prune. «Кто наблюдает за наблюдателями»: если evidence-loop не читается ≥54 недель — предлагает архивировать observer.',
'pre-commit (каждый коммит) — обновляет/проверяет docs/observer/.read-counter.json.',
'Warn-only (скрипт всегда exit 0) — не блокирует. 54 недели (≈год) — порог осознанно поднят заказчиком с 4 недель. ADR-011 spec §6.3.',
[{ name: 'lefthook.yml', cond: 'job 13 pre-commit' }, { name: 'ADR-011 §6.3', cond: 'C3' }],
[],
[{ name: 'docs/observer/ evidence', cond: 'читает .read-counter.json' }],
[]
),
lh_status_md: nd(
'Контролёр C4 (lefthook post-commit job, tools/status-md-generator.mjs) — генерит docs/observer/STATUS.md (панель: C1-C5 + информационные метрики). Pure JS, Security Guidance #40 compliant.',
'post-commit (после каждого коммита) — перегенерит STATUS.md, git add (для следующего коммита).',
'Через `|| true` — не блокирует. Метрика «N раз использован» — информационная, не алерт (capability-readiness). ADR-011 spec §6.4.',
[{ name: 'lefthook.yml', cond: 'post-commit job' }, { name: 'ADR-011 §6.4', cond: 'C4' }],
[],
[{ name: 'docs/observer/ evidence', cond: 'пишет STATUS.md' }, { name: 'C1/C2/C3', cond: 'агрегирует их сигнал' }],
[]
),
lh_obs_cov: nd(
'Контролёр C5 (lefthook pre-commit job 15, tools/observer-coverage-checker.mjs) — observer factor-analysis spec §5.2. Флагует пропуски покрытия (git-активность есть, эпизодов 0) + поломки регистрации (Stop-хук снят из settings.json, post-commit не установлен).',
'pre-commit (каждый коммит).',
'Warn-only (скрипт всегда exit 0) — не блокирует; находки в docs/observer/STATUS.md строка C5.',
[{ name: 'lefthook.yml', cond: 'job 15 pre-commit' }, { name: 'observer factor-analysis §5.2', cond: 'C5' }],
[],
[{ name: 'docs/observer/ evidence', cond: 'проверяет покрытие + регистрацию' }, { name: 'C4 status-md', cond: 'находки в STATUS.md' }],
[]
),
// ── A8 INFOSEC-TOOLING (#68-73, добавлены 22.05.2026 follow-up к A8-эпику 21.05) ──
mcp_zap: nd(
'MCP-сервер (OWASP ZAP add-on, alpha) — глубокая боевая динамическая проверка работающего портала: обход входа, инъекции (SQL/XSS), проблемы сессий/CSRF на живых endpoint-ах. ZAP 2.17.0 + MCP-аддон mcp-alpha-0.0.1 на portable Temurin JRE 17 (не системная Java).',
'Перед публикацией портала в интернет — для динамического security-gate перед релизом; вызывается скилом security-go-live (#73) как шаг динамики.',
'Цель по умолчанию — локальная копия 127.0.0.1; бой только по явной команде (граничное условие IS8, ADR-014). MCP-аддон в alpha — API может меняться. Требует запущенного ZAP-демона на portable JRE; без демона MCP-режим возвращает PENDING.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 3 — MCP-сервер при включённом демоне' }],
[],
[
{ name: 'скил security-go-live (#73)', cond: 'оркеструет ZAP как шаг динамики; связка L15' },
{ name: 'Nuclei (#69)', cond: 'комплементарны — широта (Nuclei) + глубина (ZAP); ADR-014 IS2' }
],
[]
),
nuclei: nd(
'CLI-инструмент (Go-бинарь bin/nuclei.exe v3.8.0 + 13 060 шаблонов) — широкое быстрое сканирование известных уязвимостей: CVE, дефолтные креды, открытые двери (.env/.git), утечки конфигов, слабый TLS, fingerprint стека. НЕ MCP — nuclei не говорит на MCP, обёртка стала бы доп. attack surface.',
'Регулярный security-scan живого портала; вызывается скилом security-go-live (#73). Срабатывает в задаче «прогнать сканер уязвимостей по порталу».',
'Цель — IP-литерал (127.0.0.1, не localhost — резолвер падает на native-Windows). Низкий rate-limit для однопоточного dev-сервера (-rate-limit 20 -c 5). Безопасный режим: исключать теги fuzz/dos/intrusive/brute-force при сканах боевого. Гард IS8.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1 — CLI-инструмент' }],
[],
[
{ name: 'скил security-go-live (#73)', cond: 'оркеструет Nuclei как шаг широкого сканирования; связка L15' },
{ name: 'OWASP ZAP MCP (#68)', cond: 'комплементарны (широта Nuclei + глубина ZAP); ADR-014 IS2' }
],
[]
),
ward: nd(
'CLI-инструмент (Go-бинарь bin/ward.exe v0.4.1) — сканер misconfig и секретов в Laravel: .env (8 проверок), config/*.php (13), deps через OSV.dev (live), код (7 категорий — secrets/injection/XSS/debug-артефакты/crypto/config CORS-CSRF-mass-assignment/auth). Go-бинарь → не зависит от версии Laravel.',
'Аудит безопасности настроек Laravel при ревью .env/config или подготовке к релизу; вызывается скилом security-go-live (#73).',
'CLI, не MCP, не Composer dev-dep — отдельный путь установки (portable Go SDK). Молодой проект (фев 2026), single-maintainer — bus-factor; митигация — версия-pin v0.4.1 и MIT-форкабельность. Заменил Enlightn (тот abandoned + без поддержки Laravel 13).',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1 — CLI-инструмент' }],
[],
[
{ name: 'скил security-go-live (#73)', cond: 'оркеструет Ward как шаг Laravel-misconfig; связка L15' },
{ name: 'Larastan (#12), Semgrep MCP (#25)', cond: 'комплементарны — Ward бьёт misconfig/secrets/deps, Larastan/Semgrep — типы/паттерны; ADR-014 IS3' }
],
[]
),
sk_pdn_152fz: nd(
'Project-скил — аудит персональных данных и соответствие 152-ФЗ. Два режима: технический (где лежат ПДн в схеме/коде, RLS, маскирование через pg_anonymizer, утечки в логах/CSV-экспортах) + юридический (хранение в РФ, согласия, сроки/удаление, реестр обработки, уведомление РКН, права субъекта).',
'При вопросах «проверь ПДн», «утекают ли персональные данные», «соответствие 152-ФЗ», «где хранятся телефоны лидов», перед публичным запуском. Вызывается также security-go-live (#73) как шаг ПДн.',
'Project-скил (self-authored, .claude/skills/pdn-152fz-audit/). Заземлён в db/schema.sql — даёт оценку, не правит код. Не подменяет юридическое оформление (D2: договоры/политики).',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1 — self-authored project-скил' }],
[],
[
{ name: 'pg_anonymizer (#29)', cond: 'аудит проверяет маскирование, pg_anonymizer — инструмент; ADR-014 IS4' },
{ name: 'скил security-go-live (#73)', cond: 'оркеструет как шаг 152-ФЗ; связка L15' }
],
[]
),
sk_threat_model: nd(
'Project-скил — моделирование угроз портала по STRIDE. Карта точек входа (login/2FA/recovery, supplier webhooks, deals API, админка, impersonation, CSV-импорт; заземлён в app/routes/), что меняется при выходе в интернет, приоритизация защиты. Результат — docs/security/threat-model-<date>.md.',
'Перед публикацией портала в интернет; при вопросах «смоделируй угрозы», «откуда могут атаковать», «карта точек входа». Вызывается также security-go-live (#73).',
'Project-скил под наш портал (не generic STRIDE). Не подменяет deep code-audit (Trail of Bits #39); фокус — атакующая поверхность, не уязвимости в реализации.',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1' }],
[],
[
{ name: 'скил security-go-live (#73)', cond: 'оркеструет STRIDE как шаг приоритизации; связка L15' },
{ name: 'скил pdn-152fz-audit (#71)', cond: 'STRIDE → угрозы на ПДн → ПДн-аудит' }
],
[]
),
sk_security_golive: nd(
'Project-скил — единый go-live security-gate перед публикацией портала в интернет. Оркеструет OWASP ZAP (#68) + Nuclei (#69) + Ward (#70) + pdn-152fz-audit (#71) + threat-model (#72) + Semgrep (#25) / gitleaks (#8) / Trivy (#26) / Trail of Bits (#39) → собирает вердикт GO / NO-GO.',
'Перед каждой публикацией боевого портала или большим релизом; при вопросах «готов ли портал к публикации по безопасности», «финальная проверка безопасности перед релизом».',
'Не подменяет полный 14-фазный audit-portal (тот шире); фокус — security-only срез часть дня. ZAP-шаг возвращает PENDING если ZAP-демон не запущен. Цель по умолчанию локальная (IS8).',
[{ name: 'PSR_v1', cond: 'R10.1 блок 1' }],
[
{ name: 'OWASP ZAP MCP (#68)', cond: 'вызывает как шаг динамики (глубина)' },
{ name: 'Nuclei (#69)', cond: 'вызывает как шаг широкого сканирования' },
{ name: 'Ward (#70)', cond: 'вызывает как шаг Laravel-misconfig' },
{ name: 'скил pdn-152fz-audit (#71)', cond: 'вызывает как шаг ПДн' },
{ name: 'скил threat-model (#72)', cond: 'вызывает как шаг STRIDE' }
],
[{ name: 'Semgrep MCP (#25), gitleaks (#8), Trivy (#26), Trail of Bits (#39)', cond: 'статический слой — выполняются как часть оркестрации; связка L15' }],
[]
),
// ── C1 MARKETING-TOOLING 22.05.2026 ────────────────────────
mkt_plugin: nd(
'Официальный маркетинговый плагин Anthropic (`knowledge-work-plugins/marketing`), 8 скилов: `content-creation`, `draft-content`, `campaign-plan`, `email-sequence`, `seo-audit`, `competitive-brief`, `brand-review`, `performance-report`. Первичный решатель C1 — генерирует контент, планы кампаний, SEO-аудиты, email-цепочки без платных аккаунтов.',
'При любой маркетинговой задаче: «напиши пост», «составь план кампании», «сделай SEO-аудит», «разработай email-цепочку», «подготовь конкурент-бриф». Активируется первым в marketing chain L16 (brainstorming → mkt_plugin → sk_marketing_ru → каналы).',
'Встроенные коннекторы #74 к западным SaaS (HubSpot/Ahrefs/Klaviyo/Amplitude/SimilarWeb/Canva/Figma) — не используются: РФ-аналитика через Метрика (#78)/Директ (#79), визуал — A4. `performance-report` скил относится к метрикам каналов/кампаний (CAC, конверсия, CTR) — не пересекается с product-management `metrics-review` (#42, C9: MRR/retention). Граница C1 vs C2 (sales): плагины `sales`/`small-business` из той же витрины не входят в C1 (MKT1, ADR-015).',
[
{ name: 'PSR_v1', cond: 'R10.1 блок 1 — первичный решатель C1' },
{ name: 'tooling', cond: 'реестр #74' }
],
[
{ name: 'скил marketing-ru (#77)', cond: 'РФ-специфика и каналы — подчинённый узел marketing chain L16' }
],
[
{ name: 'скил brainstorming', cond: 'marketing chain L16: brainstorming предшествует mkt_plugin как вход' },
{ name: 'brand-voice (#76)', cond: 'голос бренда держится при генерации контента mkt_plugin' },
{ name: 'marketingskills (#75)', cond: 'резерв-библиотека фреймворков — обогащает mkt_plugin как материал' },
{ name: 'скил marketing-ru (#77)', cond: 'РФ-адаптация контента и playbook каналов РФ' }
],
[
{ name: 'marketingskills (#75)', desc: 'MKT3 (ADR-015): seo-audit (#74) как решатель vs SEO-скилы marketingskills (#75) как материал — конфликт решён role-разделением (решатель/резерв-библиотека, модель FD vs UPM). Не параллельно, не дублирующие роли.', type: 'GREEN' },
{ name: 'product-management (#42)', desc: 'MKT2 (ADR-015): marketing performance-report (#74) = метрики каналов (CAC/CTR/конверсия); PM metrics-review (#42) = продуктовые метрики (MRR/retention/adoption). Разные объекты, дублирования нет.', type: 'GREEN' }
]
),
mkt_skills: nd(
'Вендоренный community-набор маркетинговых скилов (`coreyhaines31/marketingskills`, ~30k★, MIT), 40 скилов: CRO, копирайтинг, SEO, ai-seo, programmatic-seo, ad-creative, cold-email, lead-magnets, pricing, marketing-psychology и др. Только чистый markdown, без исполняемого кода. Роль: материал/резерв-библиотека фреймворков (модель UPM #31), не решатель.',
'Как дополнительный референс при работе с mkt_plugin (#74) — углублённые фреймворки CRO/SEO/психологии; при задаче «покажи примеры cold-email фреймворков», «дай фреймворк лид-магнита». Не активируется напрямую как первичный решатель.',
'Роль строго «материал» — решатель C1 это mkt_plugin (#74), модель аналогична UPM #31 vs FD #30 (MKT3, ADR-015). Вендорим в `.claude/skills/marketingskills/` — исключается из lefthook markdownlint+cspell (`ignorePaths: .claude/skills/marketingskills/**`) по прецеденту MK1 mermaid #37 / CC1 ccpm #41 (MKT10, ADR-015). IS9-вет пройден (MIT, только markdown, нет исполняемого кода).',
[
{ name: 'PSR_v1', cond: 'R10.1 блок 1 — материал/резерв-библиотека, не решатель' },
{ name: 'tooling', cond: 'реестр #75' }
],
[],
[
{ name: 'mkt_plugin (#74)', cond: 'обогащает как резерв-библиотека; mkt_plugin — решатель, mkt_skills — материал' }
],
[
{ name: 'mkt_plugin (#74)', desc: 'MKT3 (ADR-015): потенциальный SEO-дубль (seo-audit #74 vs SEO-скилы #75) — решён role-разделением: #74 быстрый on-page аудит как воркфлоу; #75 резерв-библиотека фреймворков (материал). Не параллельно, аналогично FD #30 vs UPM #31.', type: 'GREEN' }
]
),
brand_voice: nd(
'Плагин из витрины Anthropic (partner-built, Tribe AI), 3 скила: извлечь голос бренда из текстов, сгенерировать вербальные гайдлайны бренда, держать тон бренда при написании материалов. Закрывает вербальный бренд Лидерры — тон копирайта, голос, стиль коммуникации.',
'При задачах «сформулируй голос бренда», «держи тон Лидерры в тексте», «сгенерируй гайдлайн тона для копирайтера», «проверь этот текст на соответствие бренду». Используется совместно с mkt_plugin (#74) при генерации маркетингового контента.',
'Граница с Brandbook v2: Brandbook = визуальный бренд (палитра Forest, шрифты, лого) — источник истины для дизайна; brand-voice = вербальный бренд (тон, голос, копирайт) — взаимодополняют, не пересекаются (MKT6, ADR-015). brand-voice заземлён в позиционировании Brandbook, не подменяет его.',
[
{ name: 'PSR_v1', cond: 'R10.1 блок 1' },
{ name: 'tooling', cond: 'реестр #76' }
],
[],
[
{ name: 'mkt_plugin (#74)', cond: 'голос бренда применяется при генерации контента marketing plugin' },
{ name: 'скил marketing-ru (#77)', cond: 'marketing-ru несёт РФ-плейбук; brand-voice — тон и стиль независимо от канала' }
],
[
{ name: 'Brandbook v2', desc: 'MKT6 (ADR-015): brand-voice (#76) вербальный бренд vs Brandbook v2 визуальный бренд — взаимодополняющие инструменты; Brandbook остаётся источником истины для визуального слоя, brand-voice его не заменяет и не пересекается.', type: 'GREEN' }
]
),
sk_marketing_ru: nd(
'Self-authored project-скил `.claude/skills/marketing-ru/` — закрывает РФ-специфику маркетинга, которой нет ни в одном готовом инструменте: playbook каналов РФ (Яндекс.Директ, Яндекс.Метрика, Telegram, VK), конверсия реального лендинга Лидерры (заземлён в `лендинг/TZ_landing_v1_0.md`), маркетинг в рамках 152-ФЗ (согласия на рассылки, получение номера телефона/email).',
'При задачах «как запустить кампанию в Яндекс.Директ для Лидерры», «оптимизируй конверсию лендинга», «нужны ли согласия на эту рассылку», «составь план маркетинга в РФ-каналах». Вызывается в marketing chain L16 после mkt_plugin (#74).',
'Project-скил (self-authored, модель billing-audit #62 / threat-model #72 / pdn-152fz-audit #71). Линтуется lefthook в обычном режиме (не в ignorePaths). Несёт 152-ФЗ playbook маркетинговых согласий с cross-ref на pdn-152fz-audit #71 — технический аудит ПДн остаётся за #71, не за этим скилом (MKT9, ADR-015).',
[
{ name: 'mkt_plugin (#74)', cond: 'подчинён как РФ-специализация в marketing chain L16' },
{ name: 'PSR_v1', cond: 'R10.1 блок 1' },
{ name: 'tooling', cond: 'реестр #77' }
],
[
{ name: 'Яндекс.Метрика MCP (#78)', cond: 'marketing-ru даёт playbook; Метрика MCP — данные' },
{ name: 'Яндекс.Директ MCP (#79)', cond: 'marketing-ru несёт стратегию каналов; Директ MCP — Wordstat-данные' },
{ name: 'Telegram MCP (#80)', cond: 'marketing-ru несёт стратегию канала; Telegram MCP — техническое исполнение' },
{ name: 'Postiz (#81)', cond: 'marketing-ru несёт контент-план; Postiz — публикация' }
],
[
{ name: 'mkt_plugin (#74)', cond: 'marketing chain L16: mkt_plugin глобальные фреймворки, marketing-ru РФ-адаптация' },
{ name: 'скил pdn-152fz-audit (#71)', cond: 'cross-ref MKT9: marketing-ru несёт 152-ФЗ согласия на рассылки; #71 — технический аудит всех ПДн портала' }
],
[
{ name: 'скил pdn-152fz-audit (#71)', desc: 'MKT9 (ADR-015): marketing-ru несёт playbook маркетинговых согласий 152-ФЗ (сбор email/телефона для рассылок); #71 несёт полный технический аудит ПДн по портальной схеме. Разные объекты; cross-ref обязателен, не дублирование.', type: 'GREEN' }
]
),
mcp_metrika: nd(
'MCP-сервер Яндекс.Метрики (`atomkraft/yandex-metrika-mcp`, MIT) — чтение веб-аналитики лендинга и портала: визиты, источники трафика, гео, демография, поведение пользователей, конверсии. Только чтение (READ-ONLY), без мутаций.',
'При задачах «покажи источники трафика», «сколько посещений лендинга», «откуда приходят пользователи», «проверь конверсию страницы». Активируется осмысленно при живом лендинге со счётчиком Метрики (⏸ Б-1).',
'READ-ONLY — только чтение аналитики, прецедент Sentry/Redis MCP (MKT8, ADR-015). Токен OAuth бесплатный. Выбран из 3 кандидатов по итогам IS9-вета: `atomkraft/yandex-metrika-mcp` — код верифицирован (только `api-metrika.yandex.net`). SHA-пин в `.mcp.json` обязателен. Установлен (вариант Б), «загорается» при наличии счётчика Метрики на лендинге.',
[
{ name: 'PSR_v1', cond: 'R10.1 блок 3 — внешний MCP READ-ONLY' },
{ name: 'tooling', cond: 'реестр #78' }
],
[],
[
{ name: 'скил marketing-ru (#77)', cond: 'marketing-ru несёт стратегию аналитики; Метрика MCP — данные' },
{ name: 'mkt_plugin (#74)', cond: 'marketing chain L16: данные Метрики обогащают маркетинговый анализ' }
],
[
{ name: 'Яндекс.Директ MCP (#79)', desc: 'Потенциальное дублирование метрик: yandex-mcp (#79) также содержит Metrika-модуль (43 tools). Граница: #78 специализирован на Метрике, #79 активен только для Wordstat; Metrika-модуль #79 не активируется (MKT8, ADR-015) — конфликта нет.', type: 'GREEN' }
]
),
mcp_ya_direct: nd(
'MCP-сервер Яндекс.Директ + Wordstat (`SvechaPVL/yandex-mcp`, MIT, 128 инструментов: Direct 80 + Metrika 43 + Wordstat 5). В текущей конфигурации активированы **только Wordstat 5 read-only инструментов** — подбор ключевых слов, частотность, конкурентность. Direct-модуль (80 mutation-tools для управления кампаниями) и Metrika-модуль (43 tools) не активированы.',
'При задачах «подбери ключевые слова для лендинга», «проверь частотность запросов», «найди SEO-кластер для контента». Wordstat полезен немедленно без активных рекламных кампаний. Мутации Директа (создание/правка кампаний, управление ставками) — только с явным подтверждением заказчика.',
'Direct-мутации (#79 Direct-модуль) **ОТКЛЮЧЕНЫ per IS9 и MKT8 (ADR-015)** — в конфигурации активированы ТОЛЬКО Wordstat 5 read-only tools, OAuth-токен с минимальным scope (Wordstat only). Без авто-расхода бюджета никогда. Урок: отброшенный VK standalone MCP имел профиль «can spend money» — принципиальный вето. Установлен (вариант Б).',
[
{ name: 'PSR_v1', cond: 'R10.1 блок 3 — внешний MCP' },
{ name: 'tooling', cond: 'реестр #79' }
],
[],
[
{ name: 'скил marketing-ru (#77)', cond: 'marketing-ru несёт стратегию каналов; Директ MCP — данные Wordstat' },
{ name: 'mkt_plugin (#74)', cond: 'marketing chain L16: Wordstat-данные обогащают SEO-аудит #74' }
],
[
{ name: 'Яндекс.Метрика MCP (#78)', desc: 'Дублирование Metrika-модуля: yandex-mcp (#79) содержит 43 Metrika-инструмента. Граница: #78 специализирован на Метрике, Metrika-модуль #79 не активируется. При наличии обоих в .mcp.json — Active scope разделён явно.', type: 'GREEN' },
{ name: 'прямой расход бюджета', desc: 'MKT8 (ADR-015): Direct-мутации поведенчески запрещены без явного разрешения заказчика. Нарушение = чёрный конфликт (активация Direct-модуля без подтверждения).', type: 'BLACK' }
]
),
mcp_telegram: nd(
'MCP-сервер Telegram (`chigwell/telegram-mcp`, Apache-2.0, ~1.1k★, 259 коммитов) — управление Telegram через MTProto user-account: постинг в каналы, чтение сообщений, управление группами. Лучший по зрелости среди РФ-релевантных каналов.',
'При задачах «опубликуй пост в Telegram-канал Лидерры», «прочитай последние сообщения канала», «управление Telegram-каналом». Используется совместно с marketing chain L16 для публикации контента.',
'Работает через MTProto user-account (не bot-токен) — `SESSION_STRING` хранить только в `.env` (IS9-условие, ADR-015). Обязателен выделенный аккаунт; при компрометации SESSION_STRING — немедленная ротация. READ-тяжёлый режим предпочтителен; мутации (постинг) — осознанно.',
[
{ name: 'PSR_v1', cond: 'R10.1 блок 3 — внешний MCP' },
{ name: 'tooling', cond: 'реестр #80' }
],
[],
[
{ name: 'скил marketing-ru (#77)', cond: 'marketing-ru несёт стратегию Telegram-канала; Telegram MCP — техническое исполнение' },
{ name: 'Postiz (#81)', cond: 'Postiz планирует и публикует в Telegram как часть мультиканальной очереди; Telegram MCP — прямое управление' }
],
[
{ name: 'Postiz (#81)', desc: 'Перекрытие публикации в Telegram: Postiz #81 покрывает постинг в Telegram в составе 30+ площадок. Telegram MCP #80 — прямое управление (read + post + управление). Граница: Postiz для мультиканального планирования контента; Telegram MCP для прямого API-доступа (чтение, реакции, управление группами). Не параллельны для одной задачи.', type: 'GREEN' }
]
),
postiz: nd(
'Postiz (`gitroomhq/postiz-app`, ~30k★, AGPL-3.0) + `antoniolg/postiz-mcp` — self-hosted планировщик публикаций в 30+ площадок включая VK и Telegram. Закрывает VK-постинг (альтернатива отброшенному VK standalone MCP). Управляется через MCP-клиент после запуска self-hosted экземпляра.',
'При задачах «запланируй посты в соцсети», «опубликуй контент в VK и Telegram», «управление контент-календарём». Используется в marketing chain L16 как публикационный слой после подготовки контента mkt_plugin (#74) и marketing-ru (#77).',
'Лицензия AGPL-3.0 — внутренний self-host без модификаций и дистрибуции допустим (IS9-вет PASS-with-conditions, ADR-015). Если код Postiz будет модифицирован для распространения — возникают AGPL-обязательства по раскрытию исходников (фиксируется как ограничение). Лицензия `antoniolg/postiz-mcp` не верифицирована в рамках вета — проверить перед активацией MCP-клиента. Установка self-host — отдельный шаг при потребности.',
[
{ name: 'PSR_v1', cond: 'R10.1 блок 1 — self-hosted инструмент' },
{ name: 'tooling', cond: 'реестр #81' }
],
[],
[
{ name: 'скил marketing-ru (#77)', cond: 'marketing-ru несёт контент-план; Postiz — публикация' },
{ name: 'mkt_plugin (#74)', cond: 'mkt_plugin генерирует контент; Postiz публикует его по каналам' },
{ name: 'Telegram MCP (#80)', cond: 'Postiz для мультиканальной очереди; Telegram MCP для прямого управления' }
],
[
{ name: 'Telegram MCP (#80)', desc: 'Перекрытие публикации в Telegram: оба умеют публиковать в Telegram. Граница: Postiz = мультиканальный планировщик очереди (контент-календарь); Telegram MCP = прямое API-управление (read/write/groups). Не параллельны для одной публикации.', type: 'GREEN' },
{ name: 'AGPL-3.0', desc: 'Лицензионный риск: если код Postiz будет модифицирован и распространён — AGPL §4/§13 требуют раскрытия исходников. Внутренний self-host без модификаций и дистрибуции: обязательств нет. Зафиксировано как cautionary condition (ADR-015 Consequences).', type: 'GREEN' }
]
),
mcp_dataforseo: nd(
'MCP-сервер DataForSEO (`dataforseo/mcp-server-typescript`, ~204★, официальный) — SERP-данные, ключевые слова, бэклинки, контент-аудит с данными по РФ-выдаче. Единственный отложенный SEO-слот. **DEFERRED**: требует платного аккаунта DataForSEO → активация после Б-1 (прецедент Figma #44 / NightOwl #67).',
'При задачах «SEO-аудит сайта по РФ-выдаче», «анализ бэклинков», «SERP-позиции по ключевым словам» — после появления платного аккаунта DataForSEO. До этого: SEO-аудит через mkt_plugin #74 (`seo-audit`), ключевые слова через Wordstat #79.',
'DEFERRED — физически заблокирован платным аккаунтом; зарегистрирован как pending-слот (прецедент Figma #44, NightOwl #67). Условие активации: Б-1 (регистрация юрлица, платёжная возможность). SEO-покрытие до активации обеспечивают mkt_plugin #74 + Яндекс.Директ Wordstat #79 (ADR-015 §8).',
[
{ name: 'PSR_v1', cond: 'R10.1 блок 3 — внешний MCP, DEFERRED' },
{ name: 'tooling', cond: 'реестр #82' }
],
[],
[
{ name: 'mkt_plugin (#74)', cond: 'при активации #82 дополняет seo-audit #74 данными РФ-выдачи' },
{ name: 'Яндекс.Директ Wordstat (#79)', cond: 'при активации #82 добавляет SERP-данные к Wordstat-ключам #79' }
],
[
{ name: 'mkt_plugin (#74) seo-audit', desc: 'При активации #82: DataForSEO SERP-данные дополняют, а не заменяют seo-audit скил #74 (тот — воркфлоу, #82 — данные). Граница чёткая.', type: 'GREEN' }
]
),
mcp_unisender: nd(
'Unisender Go MCP — своя тонкая MCP-обёртка над API Unisender Go (текущий email-сервис портала): массовые маркетинговые рассылки, управление списками подписчиков, шаблоны писем, статистика доставки. **DEFERRED**: готового качественного upstream-сервера нет (Composio платный, клиентские библиотеки ненадёжны) — написать по потребности.',
'При потребности в массовых маркетинговых рассылках: «отправь кампанию сегментам», «управляй списком подписчиков», «просмотри статистику рассылок». До появления обёртки — использовать Unisender Go API напрямую или через `email-sequence` скил mkt_plugin (#74).',
'DEFERRED — нет готового upstream MCP-сервера; зарегистрирован как pending-слот. Условие активации: появилась потребность в автоматизации массовых рассылок → написать тонкую обёртку над API Unisender Go. Граница с транзакционным email портала (уведомления): транзакционный email = продуктовый код (не C1), маркетинговые рассылки = C1 (MKT5, ADR-015). `email-sequence` (#74) генерирует копии кампаний; отправка = Unisender Go API/#83.',
[
{ name: 'PSR_v1', cond: 'R10.1 блок 1 — self-authored, DEFERRED' },
{ name: 'tooling', cond: 'реестр #83' }
],
[],
[
{ name: 'mkt_plugin (#74)', cond: 'при активации #83: email-sequence #74 генерирует копии кампаний; #83 отправляет через Unisender Go API' }
],
[
{ name: 'транзакционный email портала', desc: 'MKT5 (ADR-015): email-sequence (#74) = черновики копий маркетинговых кампаний; #83 = отправка маркетинговых рассылок; транзакционный email (уведомления портала) = продуктовый код. Три домена не пересекаются.', type: 'GREEN' }
]
),
};
// ════════════════════════════════════════════════════
// SECTION 3.5: EDGE DETAILS (iter2 §5)
// ════════════════════════════════════════════════════
const edgeKey = (from, to) => from + '->' + to;
const EDGE_DETAILS = {
// ── ПРАВИЛА — иерархия ──────────────────────────
'pravila->claude_md': { type: 'подчиняет', when: 'всегда — CLAUDE.md уровень ниже Pravila', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §1 (уровень 1→2a)' },
'pravila->psr_v1': { type: 'подчиняет', when: 'всегда — PSR_v1 уровень 3 ниже Pravila', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §1 (уровень 1→3)' },
'claude_md->tooling': { type: 'документирует', when: 'при правке реестра инструментов', transfers: 'документация', mandatory: 'обязательно', rule: 'CLAUDE.md §0, §3 (ссылка на Прил. Н)' },
'pravila->superpowers': { type: 'подчиняет', when: 'задача попадает под карту §12.2 (14 типов)', transfers: 'контроль', mandatory: 'hard-block', rule: 'Pravila §12 (hard-rule уровня 0; скил первым, §9 не применяется)' },
// ── PSR_v1 координирует плагины ─────────────────
'psr_v1->superpowers': { type: 'координирует', when: 'paired-stack: процесс/решатель', transfers: 'контроль', mandatory: 'обязательно', rule: 'PSR_v1 R5 (paired stack ядро)' },
'psr_v1->fd_plugin': { type: 'координирует', when: 'paired-stack: процесс/решатель', transfers: 'контроль', mandatory: 'обязательно', rule: 'PSR_v1 R5 (paired stack ядро)' },
'psr_v1->upm': { type: 'координирует', when: 'вне основных фаз — активация через процедуру R14.3', transfers: 'контроль', mandatory: 'опционально', rule: 'PSR_v1 R10.1, R11.5, R14.3' },
'psr_v1->mcp_21st': { type: 'координирует', when: 'вне основных фаз — активация через процедуру R14.4', transfers: 'контроль', mandatory: 'опционально', rule: 'PSR_v1 R10.1, R14.4' },
'psr_v1->claude_md_mgmt': { type: 'координирует', when: 'инфраструктурный плагин для CLAUDE.md edits', transfers: 'контроль', mandatory: 'обязательно', rule: 'PSR_v1 R10.1 блок 1' },
// ── CLAUDE.md — документирует ──────────────────
'claude_md->mcp_boost': { type: 'документирует', when: 'фаза 1+ Laravel SQL/Eloquent/docs', transfers: 'документация', mandatory: 'рекомендуется', rule: 'CLAUDE.md §3.2 #10' },
'claude_md->mcp_sentry': { type: 'документирует', when: 'вне основных фаз — для отладки во время работы, ждёт Б-1', transfers: 'документация', mandatory: 'опционально', rule: 'CLAUDE.md §3.3 #34' },
'claude_md->mcp_redis': { type: 'документирует', when: 'вне основных фаз — Memurai только на чтение', transfers: 'документация', mandatory: 'опционально', rule: 'CLAUDE.md §3.3 #35' },
'claude_md->claude_md_mgmt': { type: 'документирует', when: 'единственный канал правок CLAUDE.md', transfers: 'документация', mandatory: 'hard-block', rule: 'CLAUDE.md §5 п.10' },
'claude_md->ag_pest': { type: 'документирует', when: 'агент для Pest TDD задач', transfers: 'документация', mandatory: 'рекомендуется', rule: 'CLAUDE.md §3 (агенты)' },
'claude_md->ag_rls': { type: 'документирует', when: 'агент для RLS-аудита и smoke-тестов', transfers: 'документация', mandatory: 'рекомендуется', rule: 'CLAUDE.md §3 (агенты)' },
// ── HOOKS — триггеры ────────────────────────────
'hk_pre_claude->claude_md': { type: 'триггерит', when: 'PreToolUse Edit/Write CLAUDE.md', transfers: 'проверка', mandatory: 'hard-block', rule: 'settings.json hooks (claude-md-management канал)' },
'hk_post_md->lh_mdlint': { type: 'триггерит', when: 'PostToolUse Edit *.md → markdownlint --fix', transfers: 'триггер', mandatory: 'обязательно', rule: 'settings.json hooks + lefthook' },
'hk_post_schema->claude_md': { type: 'триггерит', when: 'PostToolUse Edit db/schema.sql → напоминание sync', transfers: 'триггер', mandatory: 'обязательно', rule: 'settings.json hooks (§4.2 правил Claude)' },
'hk_session->mem_user': { type: 'триггерит', when: 'SessionStart инжектит memory-блок', transfers: 'данные', mandatory: 'обязательно', rule: 'settings.json hooks (memory inject)' },
'hk_session->mem_env': { type: 'триггерит', when: 'SessionStart инжектит memory-блок', transfers: 'данные', mandatory: 'обязательно', rule: 'settings.json hooks (memory inject)' },
'hk_session->mem_sp': { type: 'триггерит', when: 'SessionStart инжектит memory-блок', transfers: 'данные', mandatory: 'обязательно', rule: 'settings.json hooks (memory inject)' },
'hk_session->mem_plugins': { type: 'триггерит', when: 'SessionStart инжектит memory-блок', transfers: 'данные', mandatory: 'обязательно', rule: 'settings.json hooks (memory inject)' },
'hk_session->mem_state': { type: 'триггерит', when: 'SessionStart инжектит memory-блок', transfers: 'данные', mandatory: 'обязательно', rule: 'settings.json hooks (memory inject)' },
// ── SUPERPOWERS — содержит skills ───────────────
'superpowers->sk_brainstorm': { type: 'содержит', when: 'plugin содержит skill', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12.2 (карта 14 типов)' },
'superpowers->sk_tdd': { type: 'содержит', when: 'plugin содержит skill', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12.2 (карта 14 типов)' },
'superpowers->sk_debug': { type: 'содержит', when: 'plugin содержит skill', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12.2 (карта 14 типов)' },
'superpowers->sk_wplans': { type: 'содержит', when: 'plugin содержит skill', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12.2 (карта 14 типов)' },
'superpowers->sk_eplans': { type: 'содержит', when: 'plugin содержит skill', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12.2 (карта 14 типов)' },
'superpowers->sk_verify': { type: 'содержит', when: 'plugin содержит skill', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12.2 (карта 14 типов)' },
'superpowers->sk_parallel': { type: 'содержит', when: 'plugin содержит skill', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12.2 (карта 14 типов)' },
'superpowers->sk_worktree': { type: 'содержит', when: 'plugin содержит skill', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12.2 (карта 14 типов)' },
'superpowers->sk_pr': { type: 'содержит', when: 'plugin содержит skill', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12.2 (карта 14 типов)' },
'superpowers->sk_subagent': { type: 'содержит', when: 'plugin содержит skill', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12.2 (карта 14 типов)' },
'superpowers->sk_wskills': { type: 'содержит', when: 'plugin содержит skill', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12.2 (карта 14 типов)' },
'superpowers->sk_spreview': { type: 'содержит', when: 'plugin содержит skill', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12.2 (карта 14 типов)' },
'superpowers->sk_coderev': { type: 'содержит', when: 'plugin содержит skill', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12.2 (карта 14 типов)' },
'superpowers->sk_elements': { type: 'содержит', when: 'plugin содержит skill (using-superpowers)', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12 (старт сессии)' },
// ── SKILLS — последовательности ─────────────────
'sk_brainstorm->sk_wplans': { type: 'запускает', when: 'после brainstorming → writing-plans', transfers: 'триггер', mandatory: 'рекомендуется', rule: 'PSR_v1 R5 (paired-stack flow)' },
'sk_wplans->sk_eplans': { type: 'запускает', when: 'plan готов → executing-plans', transfers: 'триггер', mandatory: 'рекомендуется', rule: 'Superpowers process chain' },
'sk_wplans->sk_subagent': { type: 'альтернатива', when: 'если в текущей сессии — subagent-driven-development вместо executing', transfers: 'триггер', mandatory: 'опционально', rule: 'Superpowers process chain (in-session alt)' },
'sk_subagent->ag_explore': { type: 'запускает', when: 'параллельный поиск/исследование', transfers: 'триггер', mandatory: 'опционально', rule: 'subagent-driven-development (Task agent)' },
'sk_subagent->ag_general': { type: 'запускает', when: 'general-purpose subagent для независимых задач', transfers: 'триггер', mandatory: 'опционально', rule: 'subagent-driven-development (Task agent)' },
'sk_subagent->ag_plan': { type: 'запускает', when: 'агент планирования больших задач', transfers: 'триггер', mandatory: 'опционально', rule: 'subagent-driven-development (Task agent)' },
'sk_parallel->sk_worktree': { type: 'запускает', when: 'parallel agents требует изоляцию worktree', transfers: 'триггер', mandatory: 'рекомендуется', rule: 'PSR_v1 R5 (dispatching-parallel-agents)' },
'sk_rls->tooling': { type: 'читает', when: 'RLS-аудит сверяется с реестром инструментов', transfers: 'данные', mandatory: 'обязательно', rule: 'Tooling Прил. Н (squawk/Boost)' },
'sk_rls->mcp_boost': { type: 'запускает', when: 'RLS-смоук читает БД через Boost MCP', transfers: 'данные', mandatory: 'обязательно', rule: 'CLAUDE.md §3.2 #10 (Boost)' },
'sk_qitem->claude_md_mgmt': { type: 'запускает', when: 'правки реестра открытых вопросов → бамп CLAUDE.md', transfers: 'триггер', mandatory: 'обязательно', rule: 'CLAUDE.md §5 п.10 (единственный канал)' },
'claude_md_mgmt->claude_md': { type: 'запускает', when: 'skills claude-md-improver / revise-claude-md правят файл', transfers: 'контроль', mandatory: 'hard-block', rule: 'CLAUDE.md §5 п.10' },
// ── HOOKIFY — генерирует hooks ──────────────────
'ag_hookify->hookify_plugin': { type: 'запускает', when: 'агент hookify создаёт правила через plugin', transfers: 'триггер', mandatory: 'опционально', rule: 'hookify plugin docs' },
'hookify_plugin->hk_economy': { type: 'содержит', when: 'hookify сгенерировал economy hook', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §12 economy hook architecture' },
// ── АГЕНТЫ → MCP/инструменты ────────────────────
'ag_rls->mcp_boost': { type: 'запускает', when: 'RLS-аудит через Boost MCP', transfers: 'данные', mandatory: 'обязательно', rule: 'CLAUDE.md §3.2 #10' },
'ag_guide->mcp_gh': { type: 'запускает', when: 'агент гайдов работает с GitHub issues/PR', transfers: 'триггер', mandatory: 'опционально', rule: 'CLAUDE.md §3.1 #3 (GitHub MCP)' },
// ── LEFTHOOK jobs ───────────────────────────────
'lh_gitleaks->mem_plugins': { type: 'документирует', when: 'gitleaks-job описан в memory о плагинах', transfers: 'документация', mandatory: 'рекомендуется', rule: 'Tooling Прил. Н §8 (lefthook.yml)' },
'lh_larastan->mcp_boost': { type: 'запускает', when: 'Larastan job использует Boost для контекста', transfers: 'проверка', mandatory: 'обязательно', rule: 'lefthook.yml job + Boost' },
'lh_squawk->tooling': { type: 'читает', when: 'squawk-конфиг описан в реестре инструментов', transfers: 'документация', mandatory: 'обязательно', rule: 'Tooling Прил. Н #15' },
'lh_gitleaks2->lh_gitleaks': { type: 'альтернатива', when: 'второй gitleaks-job (pre-push) вариант pre-commit', transfers: 'проверка', mandatory: 'опционально', rule: 'lefthook.yml (дубль pre-push)' },
'lh_lychee->claude_md': { type: 'читает', when: 'lychee валидирует ссылки в CLAUDE.md и docs', transfers: 'проверка', mandatory: 'обязательно', rule: 'CLAUDE.md §4 + lefthook' },
// ── MEMORY → плагины/правила ────────────────────
'mem_env->ag_pest': { type: 'документирует', when: 'memory о квирках окружения нужен для Pest agent', transfers: 'данные', mandatory: 'рекомендуется', rule: 'memory/feedback_environment.md' },
'mem_plugins->psr_v1': { type: 'документирует', when: 'memory о парном стеке отражает PSR_v1', transfers: 'данные', mandatory: 'рекомендуется', rule: 'memory/feedback_plugin_paired_stack.md' },
'mem_archive->claude_md': { type: 'документирует', when: 'memory об архиве содержит refs на CLAUDE.md', transfers: 'данные', mandatory: 'рекомендуется', rule: 'memory/reference_archive.md' },
// ── MCP → агенты/skills ─────────────────────────
'mcp_pw->hk_session': { type: 'триггерит', when: 'Playwright MCP вызывается из SessionStart hook', transfers: 'триггер', mandatory: 'опционально', rule: 'settings.json hooks + Playwright' },
'mcp_gh->sk_pr': { type: 'запускает', when: 'finishing-a-development-branch использует gh-команды', transfers: 'триггер', mandatory: 'обязательно', rule: 'Superpowers finishing-a-development-branch' },
'mcp_boost->ag_rls': { type: 'запускает', when: 'Boost MCP отдаёт данные RLS-агенту', transfers: 'данные', mandatory: 'обязательно', rule: 'CLAUDE.md §3.2 #10' },
// ── КОНФЛИКТЫ (8 рёбер; 3 из них имеют ту же пару from/to, что и обычные — здесь объединены под одним ключом) ─
'sk_rls->ag_rls': { type: 'конфликт', when: 'граница задана: скил — по таблице, агент — по diff/ветке/PR', transfers: 'coverage', mandatory: 'опционально', rule: 'секции «Граница…» в SKILL.md + rls-reviewer.md (spec 2026-05-16)' },
'hookify_plugin->hk_pre_claude': { type: 'конфликт', when: 'hookify plugin генерирует hook — двойное owner-ship vs settings.json', transfers: 'coverage', mandatory: 'опционально', rule: 'нет регламента (plugin vs settings.json)' },
'mcp_pw->sk_parallel': { type: 'конфликт', when: 'Playwright и parallel-agents оба требуют изоляцию', transfers: 'coverage', mandatory: 'опционально', rule: 'GREEN: квирк #95 — профили per-cwd hash → worktrees не конфликтуют; same-dir parallel под Pravila §15.2 claim' },
'ag_pest->mcp_redis': { type: 'конфликт', when: 'Pest --parallel race на Redis cache (quirk 72/77)', transfers: 'coverage', mandatory: 'опционально', rule: 'CLAUDE.md §3.3 #35 (Redis MCP) — race остаётся вне регламента' },
'psr_v1->claude_md': { type: 'конфликт', when: 'PSR_v1 уровень 3 vs CLAUDE.md 2a — приоритет CLAUDE.md', transfers: 'контроль', mandatory: 'hard-block', rule: 'CLAUDE.md §1 (priority chain)' },
'upm->fd_plugin': { type: 'конфликт', when: 'UPM и FD оба претендуют на UI-решения', transfers: 'coverage', mandatory: 'hard-block', rule: 'PSR_v1 R14.5 (не параллельно)' },
'mcp_21st->fd_plugin': { type: 'конфликт', when: '21st Magic и FD оба генераторы UI', transfers: 'coverage', mandatory: 'hard-block', rule: 'PSR_v1 R14.5 (не параллельно)' },
'hk_economy->superpowers': { type: 'конфликт', when: 'economy-режим теоретически может «сэкономить» вызов скила — §12 (hard-rule уровня 0) economy не отменяет', transfers: 'контроль', mandatory: 'hard-block', rule: 'Pravila §12.4 (только явный «не используй»)' },
// ── RUFLO ОРКЕСТРАТОР — реколлаж (iter5 2026-05-15 + нормативный sync 2026-05-16) ──
'ruflo_queen->ruflo_workers': { type: 'подчиняет', when: 'hive-mind активен, но рой ни разу не получал задач', transfers: 'контроль', mandatory: 'опционально (рой idle)', rule: 'Tooling §4.10 (ruflo как advisory-подсистема)' },
'ruflo_queen->ruflo_agents_catalog': { type: 'артефакт', when: '`ruflo init` высыпал каталог в .claude/agents/', transfers: 'ничего (файлы лежат)', mandatory: 'не задействовано', rule: 'артефакт установки ruflo' },
'ruflo_queen->ruflo_commands': { type: 'артефакт', when: '`ruflo init` высыпал команды в .claude/commands/', transfers: 'ничего (файлы лежат)', mandatory: 'не задействовано', rule: 'артефакт установки ruflo' },
'ruflo_queen->ruflo_plugins': { type: 'артефакт', when: 'плагины ruflo — 0 установлено из ~20 в реестре', transfers: 'ничего', mandatory: 'не задействовано', rule: 'артефакт установки ruflo' },
'ruflo_mcp->ruflo_memory': { type: 'читает', when: 'MCP-инструменты memory_* обращаются к хранилищу', transfers: 'данные', mandatory: 'опционально', rule: 'Tooling §4.10' },
'ruflo_recall_hook->ruflo_memory': { type: 'читает', when: 'recall-хук на каждый промпт запускает `ruflo memory search`', transfers: 'данные', mandatory: '«мягко» (fail-open)', rule: '.claude/settings.json (UserPromptSubmit)' },
'ruflo_daemon->ruflo_memory': { type: 'читает', when: 'воркер consolidate демона обращается к памяти', transfers: 'данные', mandatory: 'опционально', rule: '.claude-flow daemon' },
'ruflo_mcp->ruflo_queen': { type: 'экспонирует', when: 'MCP-сервер отдаёт инструменты hive-mind_*/agent_*/swarm_*', transfers: 'инструменты', mandatory: 'опционально', rule: 'Tooling §4.10' },
'ruflo_queen->pravila': { type: 'конфликт', when: 'реколлаж 16.05.2026 привёл нормативку к рантайму — конфликт «декларация ≠ рантайм» закрыт', transfers: 'coverage', mandatory: 'закрыто', rule: 'Закрыто реколлажем: Pravila v1.16 / CLAUDE.md v2.2 / PSR_v1 v3.2 / Tooling v2.2' },
'pravila->ruflo_queen': { type: 'триггерит', when: 'триггер-слова queen/королева в промпте — задача маршрутизируется через ruflo Queen', transfers: 'триггер', mandatory: 'hard-rule', rule: 'Pravila §14 (queen/королева → hive-mind spawn)' },
'claude_md->ruflo_queen': { type: 'документирует', when: 'CLAUDE.md §3.5 описывает ruflo как advisory/automation-подсистему', transfers: 'документация', mandatory: 'рекомендуется', rule: 'CLAUDE.md §3.5' },
'psr_v1->ruflo_queen': { type: 'документирует', when: 'PSR_v1 §14 — cross-ref на queen-триггер Pravila §14', transfers: 'документация', mandatory: 'рекомендуется', rule: 'PSR_v1 §14' },
'tooling->ruflo_queen': { type: 'документирует', when: 'Tooling §4.10 — реестр ruflo как advisory/automation-подсистемы', transfers: 'документация', mandatory: 'рекомендуется', rule: 'Tooling §4.10' },
'mem_ruflo->ruflo_queen': { type: 'документирует', when: 'memory-файл хранит историю ruflo-интеграции', transfers: 'данные', mandatory: 'рекомендуется', rule: 'memory/project_ruflo_integration.md' },
'ruflo_memory->mem_state': { type: 'конфликт', when: 'два хранилища памяти не синхронизированы; память ruflo почти пуста', transfers: 'coverage', mandatory: 'опционально', rule: 'нет регламента синхронизации (alpha-баг HNSW #1122)' },
'ruflo_daemon->ag_pest': { type: 'конфликт', when: 'daemon worker-jitter усиливает частоту Pest-квирка 72', transfers: 'coverage', mandatory: 'опционально', rule: 'memory feedback_environment квирк #93' },
// ── BRAIN GOVERNANCE iter9 (19.05.2026, ADR-011) ──
'claude_md->router_procedure': { type: 'документирует', when: 'CLAUDE.md §3.6 — cross-ref на router-procedure.md v1.0', transfers: 'документация', mandatory: 'обязательно', rule: 'CLAUDE.md §3.6 (single SoT routing procedure)' },
'tooling->router_procedure': { type: 'питает', when: 'реестр Прил. Н §4.X — вход шага 3 процедуры роутера', transfers: 'данные', mandatory: 'обязательно', rule: 'router-procedure.md §4.2 шаг 3' },
'pravila->router_procedure': { type: 'подчиняет', when: 'hard-floor §12/§14/§15 — шаг 1 процедуры роутера', transfers: 'контроль', mandatory: 'hard-floor', rule: 'router-procedure.md §4.2 шаг 1 (Pravila §12/§14/§15)' },
'pravila->observer_stophook': { type: 'подчиняет', when: '§16: observer + routing-тег-дисциплина', transfers: 'контроль', mandatory: 'обязательно', rule: 'Pravila §16.2/§16.7 (ADR-011)' },
'observer_stophook->observer_evidence': { type: 'пишет', when: 'конец каждого хода (Stop-event)', transfers: 'данные (эпизод JSONL)', mandatory: 'обязательно (exit-0-safe)', rule: 'ADR-011 §5.2 (observer scope B)' },
'pravila->sk_brain_retro': { type: 'подчиняет', when: '§16: факторный анализ раз в спринт', transfers: 'контроль', mandatory: 'по команде заказчика', rule: 'Pravila §16 + PSR_v1 R16' },
'sk_brain_retro->observer_evidence': { type: 'читает', when: 'раз в спринт — агрегирует эпизоды', transfers: 'данные', mandatory: 'read-only', rule: 'ADR-011 §5.5 (/brain-retro — читатель)' },
'lh_l1watcher->tooling': { type: 'проверяет', when: 'pre-commit при правке settings.json / Tooling', transfers: 'проверка', mandatory: 'STRICT (блокирует)', rule: 'ADR-011 §6.1 (C1) + lefthook.yml job 11' },
'lh_crossref->claude_md': { type: 'проверяет', when: 'pre-commit при правке любого из 5 нормативных файлов', transfers: 'проверка', mandatory: 'STRICT (блокирует)', rule: 'ADR-011 §6.2 (C2) + lefthook.yml job 12' },
'lh_obs_obs->observer_evidence': { type: 'проверяет', when: 'pre-commit (каждый коммит) — счётчик чтений', transfers: 'проверка', mandatory: 'warn-only', rule: 'ADR-011 §6.3 (C3) + lefthook.yml job 13' },
'lh_status_md->observer_evidence': { type: 'пишет', when: 'post-commit — перегенерит STATUS.md', transfers: 'данные', mandatory: 'не блокирует (|| true)', rule: 'ADR-011 §6.4 (C4) + lefthook.yml post-commit' },
'lh_obs_cov->observer_evidence': { type: 'проверяет', when: 'pre-commit (каждый коммит) — покрытие + регистрация', transfers: 'проверка', mandatory: 'warn-only', rule: 'observer factor-analysis §5.2 (C5) + lefthook.yml job 15' },
};
// ════════════════════════════════════════════════════
// SECTION 3.6: NODE META (iter6 → iter8 — даты, использование, дубли)
// ════════════════════════════════════════════════════
// Данные — фактический снимок: даты из git/changelog/mtime, счётчик uses —
// из разбора транскриптов сессий Claude Code за окно META_WINDOW.
// Методика и воспроизводимость — план iter6, Приложение А.
//
// iter8 (18.05.2026): окно расширено 0916.05 → 0918.05 (10 дней).
// Узлы интеграционных волн 17-18.05 (A6 / D3 / C9 / A4 / A3 / A11 / C10 / discovery /
// ADT) получают baseline 1 = факт интеграции (коммит + plan/spec/ADR + Tooling §4).
// Реальные вызовы (за пределами интеграций) не подсчитаны — транскрипты Claude Code
// не доступны как источник в репо. mcp_figma — uses=0, usesSrc='DEFERRED'.
// null сохраняется только для принципиально неизмеримых: правила, superpowers,
// hookify_plugin, ruflo_daemon, ruflo_memory, фоновые economy/skill-discipline
// хуки (hk_self_check / skill_marker / skill_check / state_guard / postcompact /
// verifier / ruflo_queen) и старые mem_* без активных Read-вызовов в окне.
const META_SNAPSHOT = '20.05.2026'; // дата генерации значений
const META_WINDOW = '0920.05.2026'; // окно подсчёта использования (12 дней)
// uses: number — измеримый узел (0 = реально простаивал); null — измерить нельзя
// (узел-правило / плагин-обёртка / автономный демон / пассивное хранилище) → «нет данных».
// usesSrc: 'скил' | 'агент' | 'MCP' | 'хук' | 'memory-чтение' | 'коммиты' | 'инспекция' | 'интеграция' | 'DEFERRED' | '—'
const NODE_META = {
// ── ПРАВИЛА (4) — узлы-правила, напрямую не вызываются ──
pravila: { since: '06.05.2026', changed: '21.05.2026', uses: null, usesSrc: '—' },
claude_md: { since: '06.05.2026', changed: '22.05.2026', uses: null, usesSrc: '—' },
psr_v1: { since: '09.05.2026', changed: '21.05.2026', uses: null, usesSrc: '—' },
tooling: { since: '06.05.2026', changed: '22.05.2026', uses: null, usesSrc: '—' },
// ── ПЛАГИНЫ (5) ──
superpowers: { since: '09.05.2026', changed: '—', uses: null, usesSrc: '—' },
fd_plugin: { since: '10.05.2026', changed: '—', uses: 1, usesSrc: 'скил' },
upm: { since: '10.05.2026', changed: '—', uses: 0, usesSrc: 'скил' },
claude_md_mgmt: { since: '10.05.2026', changed: '—', uses: 15, usesSrc: 'скил' },
hookify_plugin: { since: '—', changed: '18.05.2026', uses: null, usesSrc: '—' },
// ── СКИЛЫ SUPERPOWERS (14) — связка подключена 09.05.2026 ──
sk_brainstorm: { since: '09.05.2026', changed: '—', uses: 44, usesSrc: 'скил' },
sk_wplans: { since: '09.05.2026', changed: '—', uses: 54, usesSrc: 'скил' },
sk_eplans: { since: '09.05.2026', changed: '—', uses: 11, usesSrc: 'скил' },
sk_subagent: { since: '09.05.2026', changed: '—', uses: 33, usesSrc: 'скил' },
sk_tdd: { since: '09.05.2026', changed: '—', uses: 2, usesSrc: 'скил' },
sk_verify: { since: '09.05.2026', changed: '—', uses: 25, usesSrc: 'скил' },
sk_debug: { since: '09.05.2026', changed: '—', uses: 10, usesSrc: 'скил' },
sk_parallel: { since: '09.05.2026', changed: '—', uses: 3, usesSrc: 'скил' },
sk_worktree: { since: '09.05.2026', changed: '—', uses: 3, usesSrc: 'скил' },
sk_pr: { since: '09.05.2026', changed: '—', uses: 10, usesSrc: 'скил' },
sk_coderev: { since: '09.05.2026', changed: '—', uses: 1, usesSrc: 'скил' },
sk_spreview: { since: '09.05.2026', changed: '—', uses: 0, usesSrc: 'скил' },
sk_wskills: { since: '09.05.2026', changed: '—', uses: 0, usesSrc: 'скил' },
sk_elements: { since: '09.05.2026', changed: '—', uses: 0, usesSrc: 'скил' },
// ── СКИЛЫ ПРОЕКТА (2) — файлы добавлены в репозиторий 13.05.2026 ──
sk_rls: { since: '13.05.2026', changed: '—', uses: 0, usesSrc: 'скил' },
sk_qitem: { since: '13.05.2026', changed: '—', uses: 1, usesSrc: 'скил' },
// ── ХУКИ (5) ──
hk_session: { since: '—', changed: '—', uses: 67, usesSrc: 'хук' },
hk_economy: { since: '10.05.2026', changed: '—', uses: 276, usesSrc: 'хук' },
hk_pre_claude: { since: '13.05.2026', changed: '—', uses: 249, usesSrc: 'хук' },
hk_post_md: { since: '06.05.2026', changed: '—', uses: 1334, usesSrc: 'хук' },
hk_post_schema: { since: '13.05.2026', changed: '—', uses: 22, usesSrc: 'хук' },
// ── АГЕНТЫ (11) ──
ag_explore: { since: 'встроено в Claude Code', changed: '—', uses: 26, usesSrc: 'агент' },
ag_general: { since: 'встроено в Claude Code', changed: '—', uses: 512, usesSrc: 'агент' },
ag_plan: { since: 'встроено в Claude Code', changed: '—', uses: 0, usesSrc: 'агент' },
ag_pest: { since: '13.05.2026', changed: '—', uses: 2, usesSrc: 'агент' },
ag_guide: { since: 'встроено в Claude Code', changed: '—', uses: 4, usesSrc: 'агент' },
ag_statusline: { since: 'встроено в Claude Code', changed: '—', uses: 0, usesSrc: 'агент' },
ag_hookify: { since: '—', changed: '—', uses: 0, usesSrc: 'агент' },
ag_pcreator: { since: '—', changed: '—', uses: 0, usesSrc: 'агент' },
ag_pvalid: { since: '—', changed: '—', uses: 0, usesSrc: 'агент' },
ag_skreview: { since: '—', changed: '—', uses: 0, usesSrc: 'агент' },
ag_rls: { since: '13.05.2026', changed: '—', uses: 2, usesSrc: 'агент' },
// ── MCP-СЕРВЕРЫ (7) ──
mcp_21st: { since: '10.05.2026', changed: '—', uses: 0, usesSrc: 'MCP' },
mcp_pw: { since: '06.05.2026', changed: '—', uses: 706, usesSrc: 'MCP' },
mcp_gh: { since: '06.05.2026', changed: '—', uses: 0, usesSrc: 'MCP' },
mcp_boost: { since: '08.05.2026', changed: '—', uses: 73, usesSrc: 'MCP' },
mcp_redis: { since: '13.05.2026', changed: '—', uses: 0, usesSrc: 'MCP' },
mcp_sentry: { since: '13.05.2026', changed: '—', uses: 0, usesSrc: 'MCP' },
mcp_semgrep: { since: '10.05.2026', changed: '—', uses: 0, usesSrc: 'MCP' },
// ── LEFTHOOK JOBS (10) — uses ≈ число коммитов в окне, фильтр по типу файла ──
lh_mdlint: { since: '06.05.2026', changed: '—', uses: 232, usesSrc: 'коммиты' },
lh_cspell: { since: '06.05.2026', changed: '—', uses: 232, usesSrc: 'коммиты' },
lh_stylelint: { since: '06.05.2026', changed: '—', uses: 43, usesSrc: 'коммиты' },
lh_eslint: { since: '08.05.2026', changed: '—', uses: 120, usesSrc: 'коммиты' },
lh_lychee: { since: '06.05.2026', changed: '—', uses: 56, usesSrc: 'коммиты' },
lh_gitleaks: { since: '06.05.2026', changed: '—', uses: 514, usesSrc: 'коммиты' },
lh_gitleaks2: { since: '06.05.2026', changed: '—', uses: 56, usesSrc: 'коммиты' },
lh_pint: { since: '08.05.2026', changed: '—', uses: 134, usesSrc: 'коммиты' },
lh_larastan: { since: '08.05.2026', changed: '—', uses: 134, usesSrc: 'коммиты' },
lh_squawk: { since: '08.05.2026', changed: '—', uses: 19, usesSrc: 'коммиты' },
// ── MEMORY FILES (15) — uses = вызовы Read; changed = mtime файла ──
mem_user: { since: '07.05.2026', changed: '08.05.2026', uses: 0, usesSrc: 'memory-чтение' },
mem_comm: { since: '07.05.2026', changed: '08.05.2026', uses: 0, usesSrc: 'memory-чтение' },
mem_env: { since: '07.05.2026', changed: '15.05.2026', uses: 41, usesSrc: 'memory-чтение' },
mem_sp: { since: '09.05.2026', changed: '10.05.2026', uses: 1, usesSrc: 'memory-чтение' },
mem_plugins: { since: '09.05.2026', changed: '13.05.2026', uses: 9, usesSrc: 'memory-чтение' },
mem_handoff: { since: '08.05.2026', changed: '—', uses: 0, usesSrc: 'memory-чтение' },
mem_redesign: { since: '12.05.2026', changed: '—', uses: 0, usesSrc: 'memory-чтение' },
mem_devindices: { since: '12.05.2026', changed: '—', uses: 1, usesSrc: 'memory-чтение' },
mem_phase1: { since: '08.05.2026', changed: '—', uses: 1, usesSrc: 'memory-чтение' },
mem_state: { since: '07.05.2026', changed: '15.05.2026', uses: 73, usesSrc: 'memory-чтение' },
mem_brain: { since: '11.05.2026', changed: '—', uses: 1, usesSrc: 'memory-чтение' },
mem_supplier: { since: '10.05.2026', changed: '12.05.2026', uses: 13, usesSrc: 'memory-чтение' },
mem_audit: { since: '13.05.2026', changed: '—', uses: 3, usesSrc: 'memory-чтение' },
mem_archive: { since: '07.05.2026', changed: '15.05.2026', uses: 21, usesSrc: 'memory-чтение' },
mem_github: { since: '07.05.2026', changed: '15.05.2026', uses: 33, usesSrc: 'memory-чтение' },
// ── RUFLO ОРКЕСТРАТОР (9) — все внедрены big-bang'ом 15.05.2026 ──
// 🔇 ИЗОЛИРОВАН 18.05.2026 (Rec2 SYSTEM-аудита): hooks сняты из settings.json,
// MCP удалён из .mcp.json, PM2 daemon stopped+saved-empty. См. Pravila §14.9 /
// Tooling §4.10 / memory feedback_ruflo_isolated.md. uses=0 — реальные вызовы 0.
ruflo_queen: { since: '15.05.2026', changed: '18.05.2026', uses: 0, usesSrc: 'инспекция', isolated: true },
ruflo_plugins: { since: '15.05.2026', changed: '18.05.2026', uses: 0, usesSrc: 'инспекция', isolated: true },
ruflo_workers: { since: '15.05.2026', changed: '18.05.2026', uses: 0, usesSrc: 'инспекция', isolated: true },
ruflo_agents_catalog: { since: '15.05.2026', changed: '18.05.2026', uses: 0, usesSrc: 'инспекция', isolated: true,
dupNote: '100 определений агентов дублируют реестр агентов; каталог буквально содержит 2 проектных агента' },
ruflo_commands: { since: '15.05.2026', changed: '18.05.2026', uses: 0, usesSrc: 'инспекция', isolated: true,
dupNote: '88 slash-команд дублируют роль скилов — именованные вызываемые процедуры; команды инертны' },
ruflo_daemon: { since: '15.05.2026', changed: '18.05.2026', uses: 0, usesSrc: 'pm2 stopped+deleted', isolated: true },
ruflo_memory: { since: '15.05.2026', changed: '18.05.2026', uses: 0, usesSrc: 'не читается', isolated: true,
dupNote: 'дублирует роль 16 memory-файлов проекта — постоянная память между сессиями; ⚫-конфликт с project_state снят изоляцией' },
ruflo_mcp: { since: '15.05.2026', changed: '18.05.2026', uses: 36, usesSrc: 'MCP (был активен 15-17.05; снят 18.05)', isolated: true },
ruflo_recall_hook: { since: '15.05.2026', changed: '18.05.2026', uses: 220, usesSrc: 'хук (был активен 15-17.05; снят 18.05)', isolated: true },
// ── MEMORY +1 (артефакт ruflo big-bang) ──
mem_ruflo: { since: '15.05.2026', changed: '16.05.2026', uses: 18, usesSrc: 'memory-чтение' },
// ── АУДИТ-АКТУАЛИЗАЦИЯ 16.05.2026 + iter8 18.05.2026 ──
// ADT (18.05): baseline 1 = факт формализации в Tooling §4.31–4.35 + интеграционный коммит 515acb6.
skill_creator: { since: '11.05.2026', changed: '18.05.2026', uses: 1, usesSrc: 'интеграция' },
claude_setup: { since: '11.05.2026', changed: '18.05.2026', uses: 1, usesSrc: 'интеграция' },
plugin_dev: { since: '—', changed: '18.05.2026', uses: 1, usesSrc: 'интеграция' },
context7: { since: '—', changed: '18.05.2026', uses: 1, usesSrc: 'интеграция' },
// Фоновые economy/skill-discipline хуки — измерение требует доступа к user-level логам, не репо.
hk_self_check: { since: '10.05.2026', changed: '—', uses: null, usesSrc: '—' },
hk_skill_marker: { since: '10.05.2026', changed: '—', uses: null, usesSrc: '—' },
hk_skill_check: { since: '10.05.2026', changed: '—', uses: null, usesSrc: '—' },
hk_state_guard: { since: '10.05.2026', changed: '—', uses: null, usesSrc: '—' },
hk_postcompact: { since: '10.05.2026', changed: '—', uses: null, usesSrc: '—' },
hk_verifier: { since: '10.05.2026', changed: '—', uses: null, usesSrc: '—' },
hk_ruflo_queen: { since: '15.05.2026', changed: '18.05.2026', uses: 0, usesSrc: 'снят 18.05', isolated: true }, // 🔇 ИЗОЛИРОВАН (см. ruflo блок выше)
sk_regression: { since: '15.05.2026', changed: '—', uses: 2, usesSrc: 'скил' }, // verification в Sprint 1-6
mem_audit_b: { since: '08.05.2026', changed: '—', uses: null, usesSrc: '—' },
mem_audit_c: { since: '07.05.2026', changed: '—', uses: null, usesSrc: '—' },
mem_suppliercrm: { since: '10.05.2026', changed: '—', uses: null, usesSrc: '—' },
mem_audit12: { since: '12.05.2026', changed: '—', uses: null, usesSrc: '—' },
mem_audit14: { since: '14.05.2026', changed: '—', uses: null, usesSrc: '—' },
mem_sprint1: { since: '15.05.2026', changed: '—', uses: null, usesSrc: '—' },
mem_sprint2: { since: '15.05.2026', changed: '—', uses: null, usesSrc: '—' },
mem_sprint3: { since: '16.05.2026', changed: '—', uses: null, usesSrc: '—' },
// ── A6 ARCHITECTURE-TOOLING 17.05.2026 (iter8: baseline 1 = факт интеграции) ──
adr_kit: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
arch_patterns: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
mermaid_skill: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
deptrac: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
// ── D3 AUDIT-SECURITY 17.05.2026 (iter8: baseline 1) ──
tob_skills: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
sec_guidance: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'хук' },
sk_security_review: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
sk_audit_portal: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
// ── C9 PROJECT-MANAGEMENT-TOOLING 17.05.2026 (iter8: baseline 1) ──
ccpm: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
product_mgmt: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
// ── A4 DESIGN-TOOLING 17.05.2026 (iter8: baseline 1, mcp_figma=0 DEFERRED) ──
mcp_figma: { since: '17.05.2026', changed: '—', uses: 0, usesSrc: 'DEFERRED' },
mcp_icons: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'MCP' },
design_plugin:{ since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
// ── A3 INTEGRATION-TOOLING (17.05.2026, iter8: baseline 1) ──
ag_apidocs: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
mcp_openapi: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
// ── A11 ML-AI-TOOLING (17.05.2026, iter8: baseline 1) ──
claude_api: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
promptfoo: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'CLI' },
data_scientist: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
// ── C10 BUSINESS-PROCESS (17.05.2026, iter8: baseline 1) ──
ops_plugin: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
process_modeling: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
process_analysis: { since: '17.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
// ── DISCOVERY-TOOLING (18.05.2026, iter8: factual в сессии) ──
// snapshot 2026-05-18-system-audit-brain.md (утро) + это интервью (вечер) + последующие вызовы
discovery_interview: { since: '18.05.2026', changed: '—', uses: 3, usesSrc: 'скил, factual' },
// ── FINANCE-TOOLING C6+C7 (20.05.2026) ──
finance_plugin: { since: '20.05.2026', changed: '—', uses: 0, usesSrc: 'новый узел' },
billing_audit: { since: '20.05.2026', changed: '—', uses: 0, usesSrc: 'новый узел' },
ru_tax: { since: '20.05.2026', changed: '—', uses: 0, usesSrc: 'новый узел' },
// ── A1 BACKEND-TOOLING (20.05.2026, ADR-013) ──
rector: { since: '20.05.2026', changed: '—', uses: 0, usesSrc: 'новый узел' },
php_insights: { since: '20.05.2026', changed: '—', uses: 0, usesSrc: 'новый узел' },
backend_patterns: { since: '20.05.2026', changed: '—', uses: 0, usesSrc: 'новый узел' },
nightowl: { since: '20.05.2026', changed: '—', uses: 0, usesSrc: 'новый узел (DEFERRED)' },
// ── BRAIN GOVERNANCE iter9 (19.05.2026, ADR-011) ──
// uses: observer_stophook=31 эпизодов; lh_obs_obs/status_md/obs_cov=112 коммитов с 19.05
// (glob-less, каждый коммит); lh_l1watcher=10, lh_crossref=13 (коммиты по glob с 19.05);
// observer_evidence=0 (.read-counter.json — 0 чтений); router_procedure=null (rule-like).
router_procedure: { since: '19.05.2026', changed: '—', uses: null, usesSrc: '—' },
observer_stophook: { since: '19.05.2026', changed: '—', uses: 31, usesSrc: 'хук (эпизоды)' },
sk_brain_retro: { since: '19.05.2026', changed: '—', uses: 1, usesSrc: 'интеграция' },
observer_evidence: { since: '19.05.2026', changed: '—', uses: 0, usesSrc: 'observer counter' },
lh_l1watcher: { since: '19.05.2026', changed: '—', uses: 10, usesSrc: 'коммиты (с 19.05)' },
lh_crossref: { since: '19.05.2026', changed: '—', uses: 13, usesSrc: 'коммиты (с 19.05)' },
lh_obs_obs: { since: '19.05.2026', changed: '—', uses: 112, usesSrc: 'коммиты (с 19.05)' },
lh_status_md: { since: '19.05.2026', changed: '—', uses: 112, usesSrc: 'коммиты (с 19.05)' },
lh_obs_cov: { since: '19.05.2026', changed: '—', uses: 112, usesSrc: 'коммиты (с 19.05)' },
// ── A8 INFOSEC-TOOLING (#68-73, добавлены 22.05.2026 follow-up к A8-эпику 21.05) ──
mcp_zap: { since: '21.05.2026', changed: '22.05.2026', uses: 1, usesSrc: 'интеграция (install)' },
nuclei: { since: '21.05.2026', changed: '22.05.2026', uses: 2, usesSrc: 'инспекция (2 скана)' },
ward: { since: '21.05.2026', changed: '22.05.2026', uses: 1, usesSrc: 'инспекция (smoke app/)' },
sk_pdn_152fz: { since: '21.05.2026', changed: '21.05.2026', uses: 1, usesSrc: 'интеграция' },
sk_threat_model: { since: '21.05.2026', changed: '21.05.2026', uses: 1, usesSrc: 'интеграция (3 эндпоинта)' },
sk_security_golive: { since: '21.05.2026', changed: '22.05.2026', uses: 1, usesSrc: 'скил (orchestration)' },
// ── C1 MARKETING-TOOLING (#74-83, добавлены 22.05.2026) ──
mkt_plugin: { since: '22.05.2026', changed: '22.05.2026', uses: null, usesSrc: 'интеграция' },
mkt_skills: { since: '22.05.2026', changed: '22.05.2026', uses: null, usesSrc: 'интеграция' },
brand_voice: { since: '22.05.2026', changed: '22.05.2026', uses: null, usesSrc: 'интеграция' },
sk_marketing_ru: { since: '22.05.2026', changed: '22.05.2026', uses: null, usesSrc: 'интеграция' },
mcp_metrika: { since: '22.05.2026', changed: '22.05.2026', uses: null, usesSrc: 'интеграция' },
mcp_ya_direct: { since: '22.05.2026', changed: '22.05.2026', uses: null, usesSrc: 'интеграция' },
mcp_telegram: { since: '22.05.2026', changed: '22.05.2026', uses: null, usesSrc: 'интеграция' },
postiz: { since: '22.05.2026', changed: '22.05.2026', uses: null, usesSrc: 'интеграция' },
mcp_dataforseo: { since: '22.05.2026', changed: '22.05.2026', uses: 0, usesSrc: 'DEFERRED' },
mcp_unisender: { since: '22.05.2026', changed: '22.05.2026', uses: 0, usesSrc: 'DEFERRED' },
};
// Явные парные дубли (Фича 3) — попадают в кнопку «⧉ Дубли».
const DUPLICATE_GROUPS = [
{ id: 'D1', pct: 90, members: ['hk_post_md', 'lh_mdlint'],
reason: 'оба запускают markdownlint --fix на .md — разница только в триггере (после правки в сессии против при коммите)' },
{ id: 'D2', pct: 85, members: ['lh_gitleaks', 'lh_gitleaks2'],
reason: 'оба — скан секретов gitleaks; pre-commit проверяет staged-файлы, pre-push — всю историю (надмножество)' },
{ id: 'D3', pct: 85, members: ['sk_rls', 'ag_rls'],
reason: 'оба — 7-пунктовая проверка RLS-соответствия; уже отмечено 🔴-конфликтом «нет регламента кто когда»' },
{ id: 'D4', pct: 80, members: ['sk_eplans', 'sk_subagent'],
reason: 'оба исполняют план writing-plans пошагово; разница — параллельная сессия против суб-агентов в текущей' },
{ id: 'D5', pct: 80, members: ['sk_wplans', 'ag_plan'],
reason: 'оба производят план задачи; скил writing-plans — inline с полным кодом, агент Plan — архитектурный набросок READ-ONLY' },
{ id: 'D7', pct: 80, members: ['ruflo_recall_hook', 'hk_session'],
reason: 'оба инжектят память в контекст Клода; recall-хук — на каждый промпт из базы ruflo, SessionStart — при старте из .md-файлов' },
];
// Производный индекс: nodeId -> { partner, pct, reason } — для рендера легенды и подсветки.
const DUP_BY_NODE = new Map();
DUPLICATE_GROUPS.forEach(g => g.members.forEach(m => {
const partner = g.members.find(x => x !== m);
DUP_BY_NODE.set(m, { partner, pct: g.pct, reason: g.reason });
}));
const DUP_NODE_SET = new Set(DUP_BY_NODE.keys()); // 12 узлов-членов парных дублей
// ════════════════════════════════════════════════════
// SECTION 3.5: ФУНКЦИОНАЛЬНЫЕ РАЗДЕЛЫ (функциональная квалификация)
// ════════════════════════════════════════════════════
// Разделы деятельности Лидерры. Каждый узел карты отнесён к одному разделу
// (NODE_SECTION). Часть разделов пока пустая — это бизнес-домены, под которые
// в карте dev-автоматики ещё нет узлов. Основа будущего «мозга»: 1 раздел =
// 1 playbook «как и что делать».
// SECTION_BUCKETS — moved to automation-graph-data.js
// SECTIONS — moved to automation-graph-data.js
// NODE_SECTION — moved to automation-graph-data.js
// NODE_SECTION_SECONDARY — moved to automation-graph-data.js
// Производные индексы для рендера панели и Паспорта.
const SECTION_BY_ID = new Map(SECTIONS.map(s => [s.id, s]));
const SECTION_NODES = new Map(SECTIONS.map(s => [s.id, []]));
NODES.forEach(n => {
const sid = NODE_SECTION[n.id];
if (sid && SECTION_NODES.has(sid)) SECTION_NODES.get(sid).push(n.id);
(NODE_SECTION_SECONDARY[n.id] || []).forEach(secId => {
if (SECTION_NODES.has(secId)) SECTION_NODES.get(secId).push(n.id);
});
});
// ════════════════════════════════════════════════════
// SECTION 3.5: WISHLIST — отложенный backlog (todo-лист хотелок)
// ════════════════════════════════════════════════════
// Редактируемый список отложенных «хотелок» развития мозга/портала.
// Добавить хотелку = добавить объект. status: 'next' | 'blocked' | 'idea'.
const WISH_STATUS = {
next: { emoji: '▶', label: 'к работе', color: '#859900' },
blocked: { emoji: '⏸', label: 'ждёт зависимости', color: '#b58900' },
idea: { emoji: '💭', label: 'идея', color: '#586e75' },
};
const WISHLIST = [
{ id: 'W1', status: 'next', section: 'E8',
title: 'K7-spike — починка embeddings ruflo',
note: 'Tensor.location / onnxruntime version sync. Гейт жизнеспособности моста claude-mem→ReasoningBank. ~1-2 ч, systematic-debugging, ≥3 гипотезы.' },
{ id: 'W2', status: 'blocked', section: 'E8',
title: 'Мост claude-mem → ReasoningBank',
note: 'Замкнутый self-learning loop: захват (claude-mem) → адаптер+LLM-трансформ → сток (ruflo memory) → recall. Gated на W1.' },
{ id: 'W3', status: 'blocked', section: 'E8',
title: 'claude-mem #1 — установка плагином',
note: 'Слой авто-захвата моста. Ставить как плагин (не npx — риск перезаписи settings.json). Роль решается после W1.' },
{ id: 'W4', status: 'blocked', section: 'E8',
title: 'Двухуровневый ремонтник моста',
note: 'Tier 1 — auto-heal операционки (рестарт демона, re-run h7-patch, retry). Tier 2 — circuit-breaker на семантику (halt, не чинить). Часть W2.' },
];
// ════════════════════════════════════════════════════
// SECTION 4: VIS INIT
// ════════════════════════════════════════════════════
// GROUPS — moved to automation-graph-data.js
const nodesDS = new vis.DataSet(NODES);
const edgesDS = new vis.DataSet(EDGES);
const network = new vis.Network(
document.getElementById('network'),
{ nodes: nodesDS, edges: edgesDS },
{
groups: GROUPS,
nodes: {
shape: 'dot',
borderWidth: 2,
borderWidthSelected: 3,
font: { multi: 'html' },
},
edges: {
smooth: { type: 'continuous', roundness: 0.5 },
selectionWidth: 2,
},
physics: {
enabled: false,
},
interaction: {
hover: true,
tooltipDelay: 400,
multiselect: false,
},
}
);
network.once('afterDrawing', () => {
network.fit({ animation: { duration: 600, easingFunction: 'easeInOutQuad' } });
});
// ════════════════════════════════════════════════════
// SECTION 5: LEGEND PANEL
// ════════════════════════════════════════════════════
function renderLegendItem(item) {
return `<li>${item.name}${item.cond ? ` <span class="cond">— ${item.cond}</span>` : ''}</li>`;
}
function showNodeLegend(nodeId) {
document.getElementById('legend-node-content').style.display = '';
document.getElementById('legend-edge-content').style.display = 'none';
document.getElementById('legend-sections-content').style.display = 'none';
document.getElementById('legend-wishlist-content').style.display = 'none';
const node = NODES.find(n => n.id === nodeId);
const details = NODE_DETAILS[nodeId];
const panel = document.getElementById('legend-panel');
if (!node || !details) { panel.classList.remove('visible'); return; }
document.getElementById('legend-title').textContent = node.label.replace(/\n/g, ' ');
const catLabel = CATEGORY_LABELS[node.group] || node.group;
const catColor = GROUPS[node.group] && GROUPS[node.group].color ? GROUPS[node.group].color.border : '#839496';
document.getElementById('legend-category').innerHTML =
`<span style="color:${catColor}; font-weight:600;">● ${catLabel}</span>`;
// ── Паспорт узла (iter6) ──
const meta = NODE_META[nodeId] || { since: '—', changed: '—', uses: null, usesSrc: '—' };
document.getElementById('ld-since').textContent = meta.since || '—';
document.getElementById('ld-changed').textContent = meta.changed || '—';
const _sec = NODE_SECTION[nodeId] ? SECTION_BY_ID.get(NODE_SECTION[nodeId]) : null;
const _secExtra = (NODE_SECTION_SECONDARY[nodeId] || [])
.map(id => SECTION_BY_ID.get(id)).filter(Boolean);
let _secText = _sec ? `${_sec.id} · ${_sec.label}` : '—';
if (_secExtra.length) _secText += ` (+${_secExtra.map(s => s.id).join(', ')})`;
document.getElementById('ld-section').textContent = _secText;
const usesEl = document.getElementById('ld-uses');
if (meta.uses === null || meta.uses === undefined) {
usesEl.textContent = 'нет данных (узел не вызывается напрямую)';
usesEl.style.color = '#586e75';
} else {
usesEl.innerHTML = `<b>${meta.uses}</b> <span style="color:#839496;">(${meta.usesSrc}) · срез ${META_WINDOW}</span>`;
usesEl.style.color = '';
}
// строка «Дубль» — парный дубль (DUPLICATE_GROUPS) либо ролевая заметка ruflo (dupNote)
const dupRow = document.getElementById('ld-dup-row');
const dupEl = document.getElementById('ld-dup');
const pair = DUP_BY_NODE.get(nodeId);
if (pair) {
const partnerNode = NODES.find(n => n.id === pair.partner);
const partnerLabel = partnerNode ? partnerNode.label.replace(/\n/g, ' ') : pair.partner;
dupEl.innerHTML = `⧉ <b>${partnerLabel}</b> (~${pair.pct}%) — ${pair.reason}`;
dupRow.style.display = '';
} else if (meta.dupNote) {
dupEl.innerHTML = `${meta.dupNote}`;
dupRow.style.display = '';
} else {
dupRow.style.display = 'none';
}
document.getElementById('ld-desc').textContent = details.desc;
document.getElementById('ld-when').textContent = details.when || '—';
document.getElementById('ld-limits').textContent = details.limits || 'без особых ограничений';
const ldReports = document.getElementById('ld-reports');
ldReports.innerHTML = details.reportsTo.length
? details.reportsTo.map(renderLegendItem).join('')
: '<li style="color:#586e75; font-style:italic;">Не подчиняется никому</li>';
const ldManages = document.getElementById('ld-manages');
ldManages.innerHTML = details.manages.length
? details.manages.map(renderLegendItem).join('')
: '<li style="color:#586e75; font-style:italic;">Никто не подчиняется</li>';
const ldTogether = document.getElementById('ld-together');
ldTogether.innerHTML = details.together.length
? details.together.map(renderLegendItem).join('')
: '<li style="color:#586e75; font-style:italic;">Нет особых одновременных связей</li>';
const ldConflicts = document.getElementById('ld-conflicts');
if (details.conflicts && details.conflicts.length) {
const sorted = [...details.conflicts].sort((a, b) =>
(CONFLICT_TYPES[a.type] ? CONFLICT_TYPES[a.type].rank : 999) -
(CONFLICT_TYPES[b.type] ? CONFLICT_TYPES[b.type].rank : 999)
);
ldConflicts.innerHTML = sorted.map(c => {
const t = CONFLICT_TYPES[c.type] || CONFLICT_TYPES.RED;
return `<div class="conflict-item" style="background:${t.bg}">` +
`<div class="cname" style="color:${t.color}">${t.emoji} ${c.name}</div>` +
`<div class="cdesc">${c.desc}</div>` +
`</div>`;
}).join('');
} else {
ldConflicts.innerHTML = '<div id="legend-no-conflicts" style="font-size:12px;color:#586e75;">Конфликтов не выявлено</div>';
}
document.getElementById('conflicts-section').style.display = '';
panel.classList.add('visible');
}
function showEdgeLegend(edgeId) {
const edge = edgesDS.get(edgeId);
if (!edge) return;
const panel = document.getElementById('legend-panel');
document.getElementById('legend-node-content').style.display = 'none';
document.getElementById('legend-edge-content').style.display = '';
document.getElementById('legend-sections-content').style.display = 'none';
document.getElementById('legend-wishlist-content').style.display = 'none';
const fromNode = NODES.find(n => n.id === edge.from);
const toNode = NODES.find(n => n.id === edge.to);
if (!fromNode || !toNode) return;
const fromCat = CATEGORY_LABELS[fromNode.group] || fromNode.group;
const toCat = CATEGORY_LABELS[toNode.group] || toNode.group;
const fromColor = (GROUPS[fromNode.group] && GROUPS[fromNode.group].color) ? GROUPS[fromNode.group].color.border : '#839496';
const toColor = (GROUPS[toNode.group] && GROUPS[toNode.group].color) ? GROUPS[toNode.group].color.border : '#839496';
const edgeColor = (edge.color && edge.color.color) ? edge.color.color : '#586e75';
document.getElementById('legend-edge-title').innerHTML =
'<span style="color:' + fromColor + '">' + fromNode.label.replace(/\n/g, ' ') + '</span>' +
' <span style="color:' + edgeColor + '">→</span> ' +
'<span style="color:' + toColor + '">' + toNode.label.replace(/\n/g, ' ') + '</span>';
document.getElementById('le-from').innerHTML =
fromNode.label.replace(/\n/g, ' ') +
' <span style="color:' + fromColor + ';font-size:11px;text-transform:uppercase;">(' + fromCat + ')</span>';
document.getElementById('le-to').innerHTML =
toNode.label.replace(/\n/g, ' ') +
' <span style="color:' + toColor + ';font-size:11px;text-transform:uppercase;">(' + toCat + ')</span>';
const details = EDGE_DETAILS[edgeKey(edge.from, edge.to)];
if (details) {
document.getElementById('le-type').textContent = details.type;
document.getElementById('le-when').textContent = details.when;
document.getElementById('le-transfers').textContent = details.transfers;
document.getElementById('le-mandatory').textContent = details.mandatory;
document.getElementById('le-rule').textContent = details.rule;
} else {
['le-type','le-when','le-transfers','le-mandatory'].forEach(id =>
document.getElementById(id).textContent = '—');
document.getElementById('le-rule').textContent = 'Регламент не задокументирован';
}
panel.classList.add('visible');
}
// ── Панель «Разделы» — функциональная квалификация (3-й режим легенды) ──
function showSectionsLegend() {
document.getElementById('legend-node-content').style.display = 'none';
document.getElementById('legend-edge-content').style.display = 'none';
document.getElementById('legend-wishlist-content').style.display = 'none';
document.getElementById('legend-sections-content').style.display = '';
let html = '';
for (const bucket of SECTION_BUCKETS) {
html += `<div class="sect-bucket-h">${bucket.id}. ${bucket.label}</div>`;
for (const sec of SECTIONS.filter(s => s.bucket === bucket.id)) {
const nodeIds = SECTION_NODES.get(sec.id) || [];
const empty = nodeIds.length === 0;
html += `<div class="sect-row${empty ? ' sect-empty' : ''}">`;
html += `<div><span class="sect-name"><span class="sect-id">${sec.id}</span> ${sec.label}</span> <span class="sect-cnt">· ${nodeIds.length}</span></div>`;
if (empty) {
html += `<div class="sect-empty-mark">— пусто (playbook ещё не наполнен) —</div>`;
} else {
html += '<div class="sect-chips">' + nodeIds.map(id => {
const node = NODES.find(n => n.id === id);
const lbl = node ? node.label.replace(/\n/g, ' ') : id;
return `<span class="sect-chip" data-node="${id}">${lbl}</span>`;
}).join('') + '</div>';
}
html += '</div>';
}
}
document.getElementById('sect-list').innerHTML = html;
document.getElementById('legend-panel').classList.add('visible');
}
// ── Панель «Хотелки» — отложенный backlog (режим легенды) ──
function showWishlistLegend() {
document.getElementById('legend-node-content').style.display = 'none';
document.getElementById('legend-edge-content').style.display = 'none';
document.getElementById('legend-sections-content').style.display = 'none';
document.getElementById('legend-wishlist-content').style.display = '';
let html = '';
for (const w of WISHLIST) {
const st = WISH_STATUS[w.status] || WISH_STATUS.idea;
html += `<div class="wish-row wish-${w.status}">`;
html += `<div class="wish-head"><span class="wish-id">${w.id}</span> ${w.title}</div>`;
html += `<div class="wish-status" style="color:${st.color}">${st.emoji} ${st.label}</div>`;
html += `<div class="wish-note">${w.note}</div>`;
if (w.section) {
const sec = SECTION_BY_ID.get(w.section);
html += `<div class="wish-sect">Раздел: ${w.section}${sec ? ' · ' + sec.label : ''}</div>`;
}
html += '</div>';
}
document.getElementById('wish-list').innerHTML = html;
document.getElementById('legend-panel').classList.add('visible');
}
network.on('click', params => {
if (params.nodes.length === 1) {
const id = params.nodes[0];
// iter6 — в режиме heat/dup клик открывает паспорт, но не трогает подсветку режима
if (HIGHLIGHT.state.viewMode === null) {
HIGHLIGHT.setSelectedNode(id);
HIGHLIGHT.applyHighlight();
}
// Right panel still shows details of the clicked node (last-clicked, even after toggle-off)
showNodeLegend(id);
} else if (params.edges.length === 1) {
showEdgeLegend(params.edges[0]);
} else if (params.nodes.length === 0 && params.edges.length === 0) {
if (HIGHLIGHT.state.viewMode === null) {
HIGHLIGHT.setSelectedNode(null);
HIGHLIGHT.applyHighlight();
}
document.getElementById('legend-panel').classList.remove('visible');
}
});
document.getElementById('legend-close').addEventListener('click', () => {
document.getElementById('legend-panel').classList.remove('visible');
});
// ════════════════════════════════════════════════════
// SECTION 6: TOOLBAR
// ════════════════════════════════════════════════════
let highlightedNode = null;
document.getElementById('search').addEventListener('input', function () {
// Search is a separate mode — last-wins over highlight state
HIGHLIGHT.clearAll();
HIGHLIGHT.updateLegendVisuals();
const q = this.value.trim().toLowerCase();
if (!q) {
nodesDS.update(NODES.map(n => ({ id: n.id, borderWidth: 2, opacity: 1.0 })));
highlightedNode = null;
return;
}
const matches = NODES.filter(n => n.label.toLowerCase().includes(q));
const updates = NODES.map(n => {
const match = matches.some(m => m.id === n.id);
return {
id: n.id,
borderWidth: match ? 5 : 1,
opacity: match ? 1.0 : 0.25,
};
});
nodesDS.update(updates);
if (matches.length === 1) {
network.focus(matches[0].id, { scale: 1.4, animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
showNodeLegend(matches[0].id);
highlightedNode = matches[0].id;
}
});
document.getElementById('btn-freeze').addEventListener('click', () => {
network.setOptions({ physics: { enabled: false } });
});
document.getElementById('btn-unfreeze').addEventListener('click', () => {
network.setOptions({
physics: {
enabled: true,
solver: 'forceAtlas2Based',
forceAtlas2Based: {
gravitationalConstant: -50,
centralGravity: 0.0,
springLength: 100,
springConstant: 0.02,
damping: 0.6,
avoidOverlap: 0.4,
},
},
});
});
document.getElementById('btn-reset').addEventListener('click', () => {
// (a) Restore radial positions
nodesDS.update(NODES.map(n => ({ id: n.id, x: n.x, y: n.y })));
// (b) Refit camera
network.fit({ animation: { duration: 600, easingFunction: 'easeInOutQuad' } });
});
document.getElementById('btn-clear').addEventListener('click', () => {
document.getElementById('search').value = '';
HIGHLIGHT.clearAll();
HIGHLIGHT.updateLegendVisuals();
HIGHLIGHT.applyHighlight();
nodesDS.update(NODES.map(n => ({ id: n.id, borderWidth: 2 }))); // reset borderWidth used by search-highlight
document.getElementById('legend-panel').classList.remove('visible');
highlightedNode = null;
});
// ════════════════════════════════════════════════════
// SECTION 7: RESIZE HANDLE + LOCALSTORAGE
// ════════════════════════════════════════════════════
const LEGEND_STORAGE_KEY = 'liderra-map-legend-width';
const LEGEND_MIN_W = 300, LEGEND_MAX_W = 900;
let redrawScheduled = false;
function applyLegendWidth(w) {
const clamped = Math.max(LEGEND_MIN_W, Math.min(LEGEND_MAX_W, w));
const panel = document.getElementById('legend-panel');
panel.style.width = clamped + 'px';
panel.style.minWidth = clamped + 'px';
if (typeof network !== 'undefined' && network && !redrawScheduled) {
redrawScheduled = true;
requestAnimationFrame(() => {
redrawScheduled = false;
network.redraw();
});
}
}
function restoreLegendWidth() {
let saved = 300;
try {
saved = parseInt(localStorage.getItem(LEGEND_STORAGE_KEY) || '300', 10);
} catch (e) { /* private mode or quota — keep default */ }
applyLegendWidth(saved);
}
(function setupResizeHandle() {
const handle = document.getElementById('legend-handle');
if (!handle) return;
let dragging = false;
handle.addEventListener('mousedown', e => {
dragging = true;
handle.classList.add('dragging');
document.body.style.userSelect = 'none';
e.preventDefault();
});
document.addEventListener('mousemove', e => {
if (!dragging) return;
const w = window.innerWidth - e.clientX;
applyLegendWidth(w);
});
document.addEventListener('mouseup', () => {
if (!dragging) return;
dragging = false;
handle.classList.remove('dragging');
document.body.style.userSelect = '';
const w = parseInt(document.getElementById('legend-panel').style.width, 10);
try { localStorage.setItem(LEGEND_STORAGE_KEY, String(w)); } catch (e) { /* private mode or quota */ }
});
})();
// ════════════════════════════════════════════════════
// SECTION 8: HIGHLIGHTING (legend filter + node focus)
// ════════════════════════════════════════════════════
const HIGHLIGHT = (function setupHighlight() {
const FILTER_GROUP_PREFIX = 'group:';
const FILTER_CONFLICT_PREFIX = 'conflict:';
const OPACITY_FOCUS = 1.0;
const OPACITY_FILTER = 0.55;
const OPACITY_DIM = 0.15;
const CONFLICT_EDGE_MIN_OPACITY = 0.85;
const state = {
selectedNode: null,
legendFilter: new Set(),
viewMode: null, // null | 'heat' | 'dup' — взаимоисключающие режимы (iter6)
};
// ── Теплокарта использования (iter6) — 4 яруса по NODE_META[id].uses ──
function heatOpacity(nodeId) {
const m = NODE_META[nodeId];
const u = m ? m.uses : null;
if (u === null || u === undefined) return 0.5; // нет данных — нейтрально
if (u >= 21) return 1.0; // часто
if (u >= 6) return 0.65; // иногда
if (u >= 1) return 0.35; // редко
return 0.12; // простаивает (uses === 0)
}
// Узлы верхнего яруса теплокарты получают акцентную рамку.
function heatBorderWidth(nodeId) {
if (state.viewMode !== 'heat') return 2;
const m = NODE_META[nodeId];
const u = m ? m.uses : null;
return (typeof u === 'number' && u >= 21) ? 4 : 2;
}
// Переключатель режима — toggle; включение режима гасит пофильтровую подсветку.
function setViewMode(mode) {
state.viewMode = (state.viewMode === mode) ? null : mode;
if (state.viewMode !== null) {
state.legendFilter.clear();
state.selectedNode = null;
}
}
// ── Pre-computed indices ──────────────────────────
const NODES_BY_ID = new Map();
const NEIGHBOURS = new Map();
const CONFLICT_ENDPOINTS = { RED: new Set(), BLACK: new Set(), GREEN: new Set() };
const CONFLICT_EDGE_TYPE = new Map();
NODES.forEach(n => {
NODES_BY_ID.set(n.id, n);
if (!NEIGHBOURS.has(n.id)) NEIGHBOURS.set(n.id, new Set());
});
edgesDS.get().forEach(edge => {
// Both directions — Q5; conflict edges count as connections — Q6
if (NEIGHBOURS.has(edge.from)) NEIGHBOURS.get(edge.from).add(edge.to);
if (NEIGHBOURS.has(edge.to)) NEIGHBOURS.get(edge.to).add(edge.from);
if (edge.dashes && edge.color && edge.color.color) {
for (const t of ['RED', 'BLACK', 'GREEN']) {
if (CONFLICT_TYPES[t].color === edge.color.color) {
CONFLICT_ENDPOINTS[t].add(edge.from);
CONFLICT_ENDPOINTS[t].add(edge.to);
CONFLICT_EDGE_TYPE.set(edge.id, t);
break;
}
}
}
});
// ── Opacity computations ──────────────────────────
function computeNodeOpacity(nodeId) {
// Row 0: view-mode (iter6) — глобальная картина, поверх focus/filter
if (state.viewMode === 'heat') return heatOpacity(nodeId);
if (state.viewMode === 'dup') return DUP_NODE_SET.has(nodeId) ? OPACITY_FOCUS : OPACITY_DIM;
// Row 1: focus
if (state.selectedNode !== null) {
if (state.selectedNode === nodeId) return OPACITY_FOCUS;
const neigh = NEIGHBOURS.get(state.selectedNode);
if (neigh && neigh.has(nodeId)) return OPACITY_FOCUS;
}
// Row 2: idle
if (state.legendFilter.size === 0 && state.selectedNode === null) return OPACITY_FOCUS;
// Row 3: in filter?
const node = NODES_BY_ID.get(nodeId);
let inFilter = false;
if (node && state.legendFilter.has(FILTER_GROUP_PREFIX + node.group)) inFilter = true;
if (!inFilter) {
for (const t of ['RED', 'BLACK', 'GREEN']) {
if (state.legendFilter.has(FILTER_CONFLICT_PREFIX + t) && CONFLICT_ENDPOINTS[t].has(nodeId)) {
inFilter = true;
break;
}
}
}
if (inFilter) return state.selectedNode ? OPACITY_FILTER : OPACITY_FOCUS;
// Row 4: everything else
return OPACITY_DIM;
}
function computeEdgeOpacity(edge) {
const fromO = computeNodeOpacity(edge.from);
const toO = computeNodeOpacity(edge.to);
const baseline = Math.min(fromO, toO);
// Conflict edge directly selected via 🔴/⚫/🟢 in filter — boost to ≥0.85
const ctype = CONFLICT_EDGE_TYPE.get(edge.id);
if (ctype && state.legendFilter.has(FILTER_CONFLICT_PREFIX + ctype)) {
return Math.max(CONFLICT_EDGE_MIN_OPACITY, baseline);
}
return baseline;
}
function applyHighlight() {
const nodeUpdates = NODES.map(n => ({
id: n.id,
opacity: computeNodeOpacity(n.id),
borderWidth: heatBorderWidth(n.id), // 4 для верхнего яруса теплокарты, иначе 2 (iter6)
}));
const edgeUpdates = edgesDS.get().map(e => ({
id: e.id,
color: Object.assign({}, e.color || {}, { opacity: computeEdgeOpacity(e) }),
}));
nodesDS.update(nodeUpdates);
edgesDS.update(edgeUpdates);
}
// ── State manipulators ────────────────────────────
function toggleFilter(key) {
if (state.legendFilter.has(key)) state.legendFilter.delete(key);
else state.legendFilter.add(key);
}
function setSelectedNode(id) {
if (state.selectedNode === id) state.selectedNode = null; // toggle on repeat
else state.selectedNode = id;
}
function clearAll() {
state.selectedNode = null;
state.legendFilter.clear();
state.viewMode = null;
}
function updateLegendVisuals() {
document.querySelectorAll('#cat-legend .cat-item').forEach(item => {
const key = item.dataset.filterKey;
if (!key) return;
if (state.legendFilter.has(key)) item.classList.add('active');
else item.classList.remove('active');
});
const heatBtn = document.getElementById('cat-ctl-heat');
const dupBtn = document.getElementById('cat-ctl-dup');
if (heatBtn) heatBtn.classList.toggle('active', state.viewMode === 'heat');
if (dupBtn) dupBtn.classList.toggle('active', state.viewMode === 'dup');
}
// ── Legend click delegation ───────────────────────
document.getElementById('cat-legend').addEventListener('click', e => {
// iter6 — клик по кнопке режима heat/dup
const ctl = e.target.closest('.cat-ctl');
if (ctl) {
if (ctl.id === 'cat-ctl-sect') { showSectionsLegend(); return; }
if (ctl.id === 'cat-ctl-wish') { showWishlistLegend(); return; }
setViewMode(ctl.id === 'cat-ctl-heat' ? 'heat' : 'dup');
applyHighlight();
updateLegendVisuals();
return;
}
const item = e.target.closest('.cat-item');
if (!item || !item.dataset.filterKey) return;
toggleFilter(item.dataset.filterKey);
applyHighlight();
updateLegendVisuals();
});
// Expose API (closes over state)
return {
applyHighlight,
toggleFilter,
setSelectedNode,
clearAll,
updateLegendVisuals,
state, // exposed for debug only
};
})();
// Клик по чипу узла в панели «Разделы» — открыть паспорт узла + сфокусировать граф.
document.getElementById('sect-list').addEventListener('click', e => {
const chip = e.target.closest('.sect-chip');
if (!chip) return;
const id = chip.dataset.node;
if (HIGHLIGHT.state.viewMode === null) {
HIGHLIGHT.setSelectedNode(id);
HIGHLIGHT.applyHighlight();
}
network.focus(id, { scale: 1.2, animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
showNodeLegend(id);
});
window.addEventListener('DOMContentLoaded', restoreLegendWidth);
</script>
</body>
</html>