Files
portal/docs/automation-graph.html
T
2026-05-15 15:56:36 +03:00

1793 lines
151 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>
<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;
}
</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"><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>
</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:#ff8800"></div>🌊 ruflo (оркестратор)</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>
</div>
<script>
// ════════════════════════════════════════════════════
// SECTION 1: NODES
// ════════════════════════════════════════════════════
// Радиально-секторная компоновка.
// Сектора (по 90°): N=workflow (090), E=UI (90180), S=infra (180270), W=data/RLS (270360).
const RADII = [0, 220, 400, 600, 800, 1000, 1180];
function pos(ring, angleDeg) {
const r = RADII[ring];
const a = angleDeg * Math.PI / 180;
return { x: Math.round(r * Math.cos(a)), y: Math.round(r * Math.sin(a)) };
}
const NODES = [
// ── ПРАВИЛА (4) ── центр + первое кольцо ───────
{ id: 'pravila', label: 'Pravila v1.14', group: 'rules', size: 38, ring: 0, ...pos(0, 0) },
{ id: 'claude_md', label: 'CLAUDE.md v2.0', group: 'rules', size: 34, ring: 1, ...pos(1, 30) },
{ id: 'psr_v1', label: 'PSR_v1 v3.0', group: 'rules', size: 32, ring: 1, ...pos(1, 150) },
{ id: 'tooling', label: 'Tooling v2.0', group: 'rules', size: 30, ring: 1, ...pos(1, 270) },
// ── ПЛАГИНЫ (5) ── второе кольцо ───────────────
{ id: 'superpowers', label: 'Superpowers v5.1', group: 'plugins', size: 30, ring: 2, ...pos(2, 45) },
{ id: 'fd_plugin', label: 'Frontend Design', group: 'plugins', size: 26, ring: 2, ...pos(2, 135) },
{ id: 'upm', label: 'UI UX Pro Max', group: 'plugins', size: 22, ring: 2, ...pos(2, 165) },
{ id: 'claude_md_mgmt', label: 'claude-md-mgmt', group: 'plugins', size: 22, ring: 2, ...pos(2, 225) },
{ id: 'hookify_plugin', label: 'hookify (плагин)', group: 'plugins', size: 22, ring: 2, ...pos(2, 200) },
// ── СКИЛЫ SUPERPOWERS (14) — N sector (090) ────
{ id: 'sk_brainstorm', label: 'brainstorming', group: 'skills_sp', size: 18, ring: 3, ...pos(3, 5) },
{ id: 'sk_wplans', label: 'writing-plans', group: 'skills_sp', size: 20, ring: 3, ...pos(3, 11) },
{ id: 'sk_eplans', label: 'executing-plans', group: 'skills_sp', size: 18, ring: 3, ...pos(3, 17) },
{ id: 'sk_subagent', label: 'subagent-driven', group: 'skills_sp', size: 20, ring: 3, ...pos(3, 23) },
{ id: 'sk_tdd', label: 'TDD', group: 'skills_sp', size: 18, ring: 3, ...pos(3, 29) },
{ id: 'sk_verify', label: 'verification-before-completion', group: 'skills_sp', size: 18, ring: 3, ...pos(3, 36) },
{ id: 'sk_debug', label: 'systematic-debugging', group: 'skills_sp', size: 18, ring: 3, ...pos(3, 43) },
{ id: 'sk_parallel', label: 'parallel-work', group: 'skills_sp', size: 18, ring: 3, ...pos(3, 50) },
{ id: 'sk_worktree', label: 'worktree', group: 'skills_sp', size: 18, ring: 3, ...pos(3, 57) },
{ id: 'sk_pr', label: 'finishing-pr', group: 'skills_sp', size: 18, ring: 3, ...pos(3, 64) },
{ id: 'sk_coderev', label: 'code-review', group: 'skills_sp', size: 16, ring: 3, ...pos(3, 71) },
{ id: 'sk_spreview', label: 'spec-review', group: 'skills_sp', size: 16, ring: 3, ...pos(3, 78) },
{ id: 'sk_wskills', label: 'writing-skills', group: 'skills_sp', size: 16, ring: 3, ...pos(3, 85) },
{ id: 'sk_elements', label: 'elements-of-style', group: 'skills_sp', size: 16, ring: 3, ...pos(3, 92) },
// ── СКИЛЫ ПРОЕКТА (2) — W sector (RLS) ─────────
{ id: 'sk_rls', label: 'rls-check', group: 'skills_proj', size: 20, ring: 3, ...pos(3, 305) },
{ id: 'sk_qitem', label: 'q-item-add', group: 'skills_proj', size: 20, ring: 3, ...pos(3, 220) },
// ── ХУКИ (5) — S+infra ────────────────────────
{ id: 'hk_session', label: 'SessionStart:\ncontext-inject', group: 'hooks', size: 24, ring: 4, ...pos(4, 100) },
{ id: 'hk_economy', label: 'UserPromptSubmit:\neconomy-mode', group: 'hooks', size: 22, ring: 4, ...pos(4, 95) },
{ id: 'hk_pre_claude', label: 'PreToolUse:\nCLAUDE.md-warn', group: 'hooks', size: 22, ring: 4, ...pos(4, 215) },
{ id: 'hk_post_md', label: 'PostToolUse:\nmarkdownlint', group: 'hooks', size: 20, ring: 4, ...pos(4, 195) },
{ id: 'hk_post_schema', label: 'PostToolUse:\nschema-changelog',group: 'hooks', size: 20, ring: 4, ...pos(4, 300) },
// ── АГЕНТЫ (11) — N (workflow) + W (RLS) ──────
{ id: 'ag_explore', label: 'Explore', group: 'agents', size: 20, ring: 4, ...pos(4, 10) },
{ id: 'ag_general', label: 'general-purpose', group: 'agents', size: 20, ring: 4, ...pos(4, 25) },
{ id: 'ag_plan', label: 'Plan', group: 'agents', size: 20, ring: 4, ...pos(4, 40) },
{ id: 'ag_pest', label: 'pest-parallel-debugger', group: 'agents', size: 24, ring: 4, ...pos(4, 55) },
{ id: 'ag_guide', label: 'claude-code-guide', group: 'agents', size: 18, ring: 4, ...pos(4, 70) },
{ id: 'ag_statusline', label: 'statusline-setup', group: 'agents', size: 18, ring: 4, ...pos(4, 85) },
{ id: 'ag_hookify', label: 'hookify:\nconversation-analyzer', group: 'agents', size: 18, ring: 4, ...pos(4, 230) },
{ id: 'ag_pcreator', label: 'plugin-dev:\nagent-creator', group: 'agents', size: 16, ring: 4, ...pos(4, 245) },
{ id: 'ag_pvalid', label: 'plugin-dev:\nplugin-validator',group: 'agents', size: 16, ring: 4, ...pos(4, 260) },
{ id: 'ag_skreview', label: 'plugin-dev:\nskill-reviewer', group: 'agents', size: 16, ring: 4, ...pos(4, 275) },
{ id: 'ag_rls', label: 'rls-reviewer', group: 'agents', size: 22, ring: 4, ...pos(4, 315) },
// ── MCP-СЕРВЕРЫ (7) — E (UI) + W (data) ───────
{ id: 'mcp_21st', label: 'MCP: 21st.dev Magic', group: 'mcp', size: 20, ring: 5, ...pos(5, 130) },
{ id: 'mcp_pw', label: 'MCP: playwright', group: 'mcp', size: 22, ring: 5, ...pos(5, 110) },
{ id: 'mcp_gh', label: 'MCP: github', group: 'mcp', size: 22, ring: 5, ...pos(5, 75) },
{ id: 'mcp_boost', label: 'MCP: laravel-boost', group: 'mcp', size: 24, ring: 5, ...pos(5, 290) },
{ id: 'mcp_redis', label: 'MCP: redis', group: 'mcp', size: 22, ring: 5, ...pos(5, 310) },
{ id: 'mcp_sentry', label: 'MCP: sentry', group: 'mcp', size: 22, ring: 5, ...pos(5, 330) },
{ id: 'mcp_semgrep', label: 'MCP: semgrep', group: 'mcp', size: 20, ring: 5, ...pos(5, 350) },
// ── LEFTHOOK JOBS (10) — S+W (infra/data) ─────
{ id: 'lh_mdlint', label: 'lefthook:\nmarkdownlint', group: 'lefthook', size: 18, ring: 5, ...pos(5, 185) },
{ id: 'lh_cspell', label: 'lefthook:\ncspell', group: 'lefthook', size: 18, ring: 5, ...pos(5, 200) },
{ id: 'lh_stylelint', label: 'lefthook:\nstylelint', group: 'lefthook', size: 16, ring: 5, ...pos(5, 215) },
{ id: 'lh_eslint', label: 'lefthook:\neslint-vue', group: 'lefthook', size: 18, ring: 5, ...pos(5, 230) },
{ id: 'lh_lychee', label: 'lefthook:\nlychee-links', group: 'lefthook', size: 18, ring: 5, ...pos(5, 245) },
{ id: 'lh_gitleaks', label: 'lefthook:\ngitleaks', group: 'lefthook', size: 18, ring: 5, ...pos(5, 260) },
{ id: 'lh_gitleaks2', label: 'lefthook:\ngitleaks pre-push', group: 'lefthook', size: 18, ring: 5, ...pos(5, 275) },
{ id: 'lh_pint', label: 'lefthook:\npint', group: 'lefthook', size: 18, ring: 5, ...pos(5, 25) },
{ id: 'lh_larastan', label: 'lefthook:\nlarastan', group: 'lefthook', size: 18, ring: 5, ...pos(5, 50) },
{ id: 'lh_squawk', label: 'lefthook:\nsquawk', group: 'lefthook', size: 18, ring: 5, ...pos(5, 320) },
// ── MEMORY FILES (15) — внешнее кольцо ──────────
{ id: 'mem_user', label: 'memory:\nuser_profile', group: 'memory', size: 16, ring: 6, ...pos(6, 0) },
{ id: 'mem_comm', label: 'memory:\nfeedback_comm', group: 'memory', size: 14, ring: 6, ...pos(6, 24) },
{ id: 'mem_env', label: 'memory:\nfeedback_env', group: 'memory', size: 16, ring: 6, ...pos(6, 48) },
{ id: 'mem_sp', label: 'memory:\nfeedback_superpowers',group: 'memory', size: 16, ring: 6, ...pos(6, 72) },
{ id: 'mem_plugins', label: 'memory:\nfeedback_plugins', group: 'memory', size: 16, ring: 6, ...pos(6, 96) },
{ id: 'mem_handoff', label: 'memory:\nreference_handoff', group: 'memory', size: 14, ring: 6, ...pos(6, 120) },
{ id: 'mem_redesign', label: 'memory:\nportal_redesign', group: 'memory', size: 14, ring: 6, ...pos(6, 144) },
{ id: 'mem_devindices', label: 'memory:\ndev_indices', group: 'memory', size: 12, ring: 6, ...pos(6, 168) },
{ id: 'mem_phase1', label: 'memory:\nphase1_strategy', group: 'memory', size: 14, ring: 6, ...pos(6, 192) },
{ id: 'mem_state', label: 'memory:\nproject_state', group: 'memory', size: 16, ring: 6, ...pos(6, 216) },
{ id: 'mem_brain', label: 'memory:\nclaude_brain', group: 'memory', size: 14, ring: 6, ...pos(6, 240) },
{ id: 'mem_supplier', label: 'memory:\nsupplier_integration',group: 'memory', size: 14, ring: 6, ...pos(6, 264) },
{ id: 'mem_audit', label: 'memory:\naudit_2026-05-13', group: 'memory', size: 14, ring: 6, ...pos(6, 288) },
{ id: 'mem_archive', label: 'memory:\nreference_archive', group: 'memory', size: 14, ring: 6, ...pos(6, 312) },
{ id: 'mem_github', label: 'memory:\nreference_github', group: 'memory', size: 14, ring: 6, ...pos(6, 336) },
// ── RUFLO ОРКЕСТРАТОР (11) — кластер вне радиального layout (верх-лево) ──
{ id: 'ruflo_queen', label: 'ruflo Queen\n(Queen-led routing)', group: 'ruflo', size: 44, x: -1300, y: -700 },
{ id: 'ruflo_architect', label: 'Architect', group: 'ruflo', size: 24, x: -1150, y: -870 },
{ id: 'ruflo_coder', label: 'Coder', group: 'ruflo', size: 24, x: -980, y: -860 },
{ id: 'ruflo_security', label: 'Security', group: 'ruflo', size: 24, x: -880, y: -740 },
{ id: 'ruflo_rls', label: 'RLS-reviewer', group: 'ruflo', size: 22, x: -880, y: -580 },
{ id: 'ruflo_qa', label: 'QA', group: 'ruflo', size: 22, x: -980, y: -490 },
{ id: 'ruflo_tester', label: 'Tester', group: 'ruflo', size: 22, x: -1150, y: -490 },
{ id: 'ruflo_reviewer', label: 'Reviewer', group: 'ruflo', size: 22, x: -1300, y: -490 },
{ id: 'ruflo_memory', label: 'Memory-keeper\n(HNSW + SONA)', group: 'ruflo', size: 24, x: -1470, y: -580 },
{ id: 'ruflo_daemon', label: 'Daemon-worker\n(PM2, 5 задач)', group: 'ruflo', size: 22, x: -1470, y: -740 },
{ id: 'ruflo_mcp', label: 'ruflo MCP\n(~210 инструментов)', group: 'ruflo', size: 24, x: -1100, y: -340 },
// ── MEMORY +1 (артефакт ruflo big-bang) ──
{ id: 'mem_ruflo', label: 'memory:\nproject_ruflo_integration', group: 'memory', size: 14, x: -1700, y: -470 },
];
// ════════════════════════════════════════════════════
// SECTION 2: EDGES
// ════════════════════════════════════════════════════
const CONFLICT_TYPES = {
RED: { color: '#ff5f57', bg: '#2d0000', emoji: '🔴', label: 'Не закрыт правилом', rank: 1 },
BLACK: { color: '#888888', bg: '#1a1a1a', emoji: '⚫', label: 'Возник на практике', rank: 2 },
GREEN: { color: '#859900', bg: '#0e1a00', emoji: '🟢', label: 'Закрыт правилом', rank: 3 },
};
const E = (from, to, label) => ({
from, to,
title: label,
color: { color: '#586e75', highlight: '#93a1a1', hover: '#93a1a1' },
arrows: { to: { enabled: true, scaleFactor: 0.6 } },
smooth: { type: 'continuous', roundness: 0.5 }
});
const CONFLICT = (from, to, label, type = 'RED') => ({
from, to,
title: label,
label: CONFLICT_TYPES[type].emoji,
dashes: true,
width: 2,
color: { color: CONFLICT_TYPES[type].color, highlight: '#ff8880', hover: '#ff8880' },
arrows: { to: { enabled: true, scaleFactor: 0.7 }, from: { enabled: true, scaleFactor: 0.7 } },
font: { color: CONFLICT_TYPES[type].color, size: 14, align: 'middle', strokeWidth: 3, strokeColor: '#1e1e2e' },
smooth: { type: 'curvedCW', roundness: 0.35 }
});
const EDGES = [
// ── ПРАВИЛА — иерархия ──────────────────────────
E('pravila', 'claude_md', 'подчиняет\n(уровень 1→2a)'),
E('pravila', 'psr_v1', 'подчиняет\n(уровень 1→3)'),
E('claude_md', 'tooling', 'ссылается\nна реестр'),
E('pravila', 'superpowers', '§12: обязывает\nинвокировать 1-м'),
// ── PSR_v1 координирует плагины ─────────────────
E('psr_v1', 'superpowers', 'R5: координирует\nпарный стек'),
E('psr_v1', 'fd_plugin', 'R5: координирует\nпарный стек'),
E('psr_v1', 'upm', 'R14.3: активирует\nтолько через pipeline'),
E('psr_v1', 'mcp_21st', 'R14.4: активирует\nтолько через pipeline'),
E('psr_v1', 'claude_md_mgmt','R10.1 блок 1:\nинфраструктурный'),
// ── CLAUDE.md ────────────────────────────────────
E('claude_md', 'mcp_boost', 'описывает §3.2'),
E('claude_md', 'mcp_sentry', 'описывает §4.8'),
E('claude_md', 'mcp_redis', 'описывает §4.9'),
E('claude_md', 'claude_md_mgmt', '§5п.10:\nединственный канал'),
E('claude_md', 'ag_pest', 'описывает\nкогда вызывать'),
E('claude_md', 'ag_rls', 'описывает\nкогда вызывать'),
// ── ХУКИ ────────────────────────────────────────
E('hk_pre_claude', 'claude_md', 'проверяет\nпри Edit/Write'),
E('hk_post_md', 'lh_mdlint', 'дублирует задачу\n(локально)'),
E('hk_post_schema', 'claude_md', 'напоминает про\nCHANGELOG_schema'),
E('hk_session', 'mem_user', 'читает\nпри старте'),
E('hk_session', 'mem_env', 'читает\nпри старте'),
E('hk_session', 'mem_sp', 'читает\nпри старте'),
E('hk_session', 'mem_plugins', 'читает\nпри старте'),
E('hk_session', 'mem_state', 'читает\nпри старте'),
E('hk_economy', 'superpowers', 'парсит уровень\nэкономии'),
// ── SUPERPOWERS содержит скилы ──────────────────
E('superpowers', 'sk_brainstorm', 'содержит'),
E('superpowers', 'sk_tdd', 'содержит'),
E('superpowers', 'sk_debug', 'содержит'),
E('superpowers', 'sk_wplans', 'содержит'),
E('superpowers', 'sk_eplans', 'содержит'),
E('superpowers', 'sk_verify', 'содержит'),
E('superpowers', 'sk_parallel', 'содержит'),
E('superpowers', 'sk_worktree', 'содержит'),
E('superpowers', 'sk_pr', 'содержит'),
E('superpowers', 'sk_subagent', 'содержит'),
E('superpowers', 'sk_wskills', 'содержит'),
E('superpowers', 'sk_spreview', 'содержит'),
E('superpowers', 'sk_coderev', 'содержит'),
E('superpowers', 'sk_elements', 'содержит'),
// ── СКИЛЫ вызывают друг друга ───────────────────
E('sk_brainstorm', 'sk_wplans', 'вызывает\nпосле дизайна'),
E('sk_wplans', 'sk_eplans', 'вызывает\nдля выполнения'),
E('sk_wplans', 'sk_subagent','альтернатива\nexecuting-plans'),
E('sk_subagent', 'ag_explore', 'запускает\nдля поиска'),
E('sk_subagent', 'ag_general', 'запускает\nдля задач'),
E('sk_subagent', 'ag_plan', 'запускает\nдля архитектуры'),
E('sk_parallel', 'sk_worktree','использует\nдля изоляции'),
// ── СКИЛЫ ПРОЕКТА ───────────────────────────────
E('sk_rls', 'tooling', 'использует\nsquawk + grep §3.2'),
E('sk_rls', 'mcp_boost', 'SQL запросы\nк схеме'),
E('sk_qitem', 'claude_md_mgmt','делегирует\nправку CLAUDE.md'),
// ── CLAUDE-MD-MGMT ──────────────────────────────
E('claude_md_mgmt', 'claude_md', 'единственный\nканал правок'),
// ── HOOKIFY ─────────────────────────────────────
E('ag_hookify', 'hookify_plugin', 'передаёт\nанализ'),
E('hookify_plugin', 'hk_pre_claude', 'может создавать\nновые хуки'),
E('hookify_plugin', 'hk_economy', 'может создавать\nновые хуки'),
// ── АГЕНТЫ используют MCP ───────────────────────
E('ag_pest', 'mcp_redis', 'читает\nочереди/кэш'),
E('ag_rls', 'mcp_boost', 'SQL запросы\nк БД'),
E('ag_guide', 'mcp_gh', 'ищет\nв репозитории'),
// ── LEFTHOOK вызывается git ──────────────────────
E('lh_gitleaks', 'mem_plugins', 'блокирует коммит\nпри ПДн в staged'),
E('lh_larastan', 'mcp_boost', 'Boost даёт\nконтекст типов'),
E('lh_squawk', 'tooling', 'соответствует\n§3.2 #15'),
E('lh_gitleaks2', 'lh_gitleaks', 'строже:\nвся история'),
E('lh_lychee', 'claude_md', 'проверяет\nссылки в .md'),
// ── MEMORY читается Claude ──────────────────────
E('mem_env', 'ag_pest', 'квирки 72/77\nиспользует агент'),
E('mem_plugins', 'psr_v1', 'отражает\nтекущие версии'),
E('mem_archive', 'claude_md', 'синхронизирует\nверсии доков'),
// ── MCP ─────────────────────────────────────────
E('mcp_pw', 'hk_session', 'используется\nдля a11y smoke'),
E('mcp_gh', 'sk_pr', 'PR, issues\nпри finishing-pr'),
E('mcp_boost', 'ag_rls', 'схема БД\nдля RLS-review'),
// ══════════════════════════════════════════════════
// КОНФЛИКТЫ — 3-color classification (iter2 §4)
// 🔴 не закрыт правилом / ⚫ возник на практике / 🟢 закрыт правилом
// ══════════════════════════════════════════════════
CONFLICT('sk_rls', 'ag_rls', 'RLS compliance: оба покрывают, нет регламента', 'RED'),
CONFLICT('hookify_plugin', 'hk_pre_claude', 'hookify может перезаписать существующий хук', 'RED'),
CONFLICT('mcp_pw', 'sk_parallel', 'Browser is already in use (квирк #2)', 'BLACK'),
CONFLICT('ag_pest', 'mcp_redis', 'Гонка в Redis при Pest --parallel из подкаталога (квирк 72)', 'BLACK'),
CONFLICT('psr_v1', 'claude_md', 'Закрыто §5п.10 CLAUDE.md + хук CLAUDE.md-warn', 'GREEN'),
CONFLICT('upm', 'fd_plugin', 'PSR_v1 R14.5: не параллельно', 'GREEN'),
CONFLICT('mcp_21st', 'fd_plugin', 'PSR_v1 R14.5: не параллельно', 'GREEN'),
CONFLICT('hk_economy', 'superpowers', '§12 — sub-policy под ruflo; economy-режим §12 не отменяет (Pravila §12.4)', 'GREEN'),
// ══════════════════════════════════════════════════
// RUFLO ОРКЕСТРАТОР — наслой ruflo big-bang (2026-05-15)
// ══════════════════════════════════════════════════
// Queen → 9 swarm-ролей
E('ruflo_queen', 'ruflo_architect', 'подчиняет'),
E('ruflo_queen', 'ruflo_coder', 'подчиняет'),
E('ruflo_queen', 'ruflo_security', 'подчиняет'),
E('ruflo_queen', 'ruflo_rls', 'подчиняет'),
E('ruflo_queen', 'ruflo_qa', 'подчиняет'),
E('ruflo_queen', 'ruflo_tester', 'подчиняет'),
E('ruflo_queen', 'ruflo_reviewer', 'подчиняет'),
E('ruflo_queen', 'ruflo_memory', 'подчиняет'),
E('ruflo_queen', 'ruflo_daemon', 'подчиняет'),
// Queen → MCP-сервер ruflo
E('ruflo_queen', 'ruflo_mcp', 'экспонирует\n~210 инструментов'),
// Queen → 4 узла-правила (нормативная декларация уровня −1)
E('ruflo_queen', 'pravila', 'перенял\nsub-policy'),
E('ruflo_queen', 'claude_md', 'перенял\nsub-policy'),
E('ruflo_queen', 'psr_v1', 'перенял\nsub-policy'),
E('ruflo_queen', 'tooling', 'перенял\nsub-policy'),
// swarm → legacy execution-layer
E('ruflo_coder', 'ag_pest', 'делегирует TDD'),
E('ruflo_security', 'ag_rls', 'делегирует RLS'),
E('ruflo_rls', 'sk_rls', 'делегирует\nRLS-скил'),
E('ruflo_memory', 'mem_state', 'reindex HNSW'),
// memory → ruflo
E('mem_ruflo', 'ruflo_queen', 'документирует\nинтеграцию'),
// 3 новых конфликта ruflo (3-color, iter2 §4)
CONFLICT('ruflo_queen', 'pravila', 'Нормативка декларирует ruflo уровнем −1 (overlord) — фактически parallel subsystem', 'RED'),
CONFLICT('ruflo_daemon', 'mem_state', 'Два хранилища памяти (.md и HNSW) рассинхронизированы; memory store не персистит', 'BLACK'),
CONFLICT('ruflo_daemon', 'ag_pest', 'Daemon worker-jitter усиливает частоту Pest квирка 72', 'BLACK'),
];
// ════════════════════════════════════════════════════
// SECTION 3: NODE DETAILS
// ════════════════════════════════════════════════════
const CATEGORY_LABELS = {
rules: 'Правило', plugins: 'Плагин', skills_sp: 'Скил Superpowers',
skills_proj: 'Скил проекта', hooks: 'Хук .claude', agents: 'Агент',
mcp: 'MCP-сервер', lefthook: 'Lefthook job', memory: 'Memory-файл',
ruflo: 'ruflo (оркестратор)'
};
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 стал sub-policy под ruflo Queen-led routing (уровень −1); в уровнях 0–6 цепочки приоритетов §12 остаётся hard-rule (скил инвокируется первым), economy-режим §12 не отменяет. Расходимость с другими документами — нарушение §7.',
[{ name: 'ruflo Queen', cond: 'уровень −1 по нормативке — ruflo Queen-led routing над Pravila' }],
[
{ 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: 'Нормативка декларирует ruflo Queen-led routing уровнем 1 над Pravila — overlord. Фактически ruflo — параллельная подсистема, Claude работает напрямую; механизма enforcement нет (декларация ≠ runtime).', type: 'RED' }]
),
claude_md: nd(
'Оперативная карта проекта — технологии, команды, фазы, 9-уровневая цепочка приоритетов (§1, уровень −1 — ruflo) и §3.5 orchestration layer, ссылки на документы.',
'Читается при старте каждой сессии; обновляется при новом инструменте или новой фазе.',
'Править можно только через скил `/claude-md-management:claude-md-improver` или `:revise-claude-md` (правило §5 п.10). Прямые Edit/Write блокируются хуком предупреждения.',
[{ name: 'Pravila', cond: 'всегда подчинён (уровень 2a)' }],
[
{ name: 'Tooling v2.0', 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 stack-gate переформулирован в sub-policy paired-stack delegation pattern под ruflo Queen-led routing.',
'При выборе 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(
'Реестр 55 позиций — 35 формализованных инструментов + 20 ruflo-плагинов; +§4.10 Orchestration layer. Когда что использовать, команды установки, конфликты.',
'При выборе инструмента для фазы (нулевая документация / первая 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 — sub-policy под ruflo Queen-led routing (уровень −1); в уровнях 0–6 остаётся hard-rule (скил инвокируется первым). Единственная отмена — явная просьба заказчика «не используй 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 (sub-policy под ruflo Queen-led routing) 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. Новые хуки могут конфликтовать с существующими (см. конфликты ниже) — обязательная проверка файла настроек до создания.',
[{ name: 'PSR_v1', cond: 'R10.1: формализован' }],
[{ name: 'агент hookify:conversation-analyzer', cond: 'запускает анализ разговоров' }],
[{ name: 'агент hookify:conversation-analyzer', cond: 'плагин и агент работают в паре' }],
[
{ name: 'хук pre-claude-warn', desc: 'плагин hookify создаёт новые хуки PreToolUse на лету — может перезаписать или конкурировать с этим хуком', type: 'RED' }
]
),
// ── СКИЛЫ 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: 'Браузер уже занят (Browser is already in use) при одновременном запуске нескольких сессий через worktree', type: 'BLACK' }]
),
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: 'оба проверяют соответствие политик RLS (защиты строк по тенанту) — скил для ручной проверки таблицы, агент для разбора PR (запроса на слияние кода) и diff; нет чёткой границы когда какой', type: 'RED' }]
),
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 — sub-policy под ruflo Queen-led routing, но economy-режим §12 НЕ отменяет ни на каком уровне. Действует только на текущую задачу — следующий промпт разбирается заново.',
[{ name: '.claude/settings.json', cond: 'описан как хук UserPromptSubmit' }],
[],
[],
[{ name: 'плагин Superpowers (§12)', desc: 'Экономия=100% теоретически может «сэкономить» вызов скила — §12 (sub-policy под ruflo Queen-led routing) economy-режим не отменяет ни на каком уровне (Pravila §12.4).', type: 'GREEN' }]
),
// ── АГЕНТЫ ───────────────────────────────────────
ag_pest: nd(
'Разбирает падения тестов Pest --parallel: отличает настоящую ошибку от одного из пяти известных квирков (72/73/77...).',
'При падении Pest --parallel ИЛИ при дымовом тесте (быстрой проверке функциональности) только из подкаталога (как в аудите Phase 3 SyncSupplierProjectsJobTest).',
'READ-ONLY (только чтение — только читает код, ничего не правит). Каждую гипотезу подтверждает реальным запуском, а не «похоже на квирк».',
[{ name: 'CLAUDE.md §6', cond: 'описывает когда вызывать' }],
[],
[{ name: 'MCP-сервер redis', cond: 'читает Redis для отладки квирка 72 (гонка supplier:session)' }],
[{ name: 'MCP-сервер redis', desc: 'Pest --parallel — гонка (race condition) с кэшем Redis при запуске из подкаталога (квирк 72)', 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: 'оба покрывают соответствие RLS (защиты строк по тенанту), чёткой границы нет: агент — для PR (запроса на слияние кода) и diff, скил — для ручной проверки таблицы', type: 'RED' }]
),
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.',
'Не для боевых пользователей. На сессию один общий браузер — при parallel-work возможны столкновения (см. квирк #2 в memory).',
[{ name: 'CLAUDE.md §3.1 #2', cond: 'активен с фазы 0' }],
[],
[{ name: 'SessionStart хук', cond: 'используется для визуальной проверки прототипов' }],
[{ name: 'parallel-work скил', desc: 'Один shared browser на сессию — конкуренция при параллельной работе через worktrees (memory квирк #2)', type: 'BLACK' }]
),
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 квирк 72), при анализе инвалидации кэша.',
'**СТРОГО 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: 'Гонка в Redis при Pest --parallel при запуске из подкаталога (квирк 72)', type: 'BLACK' }]
),
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: 'квирки 72/77 используются агентом' },
{ name: 'SessionStart хук', cond: 'читается при старте' }
]
),
mem_sp: nd(
'Правило §12 (sub-policy под ruflo Queen-led routing) + архитектура хука 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: 'читается при старте для быстрого контекста' }]
),
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 ОРКЕСТРАТОР ────────────────────────────
ruflo_queen: nd(
'Оркестратор ruflo v3.7.0-alpha.38 — Queen-led routing: SONA нейро-классификация задач, swarm-топологии (Raft/Byzantine/Gossip), HNSW vector memory. По нормативке — entry-point уровня −1 для всех задач.',
'По нормативке — первичная классификация любой задачи и маршрутизация в sub-policy paired-stack ИЛИ прямое исполнение через swarm.',
'Фактически ruflo НЕ перехватывает Claude Code workflow — Claude работает напрямую (скилы/инструменты). Queen-led routing как entry-point — нормативная декларация, не runtime. Alpha-версия. LLM API-ключи отсутствуют → swarm-агенты idle, $-расход near-zero. Plugin discovery через IPFS нестабилен (Pinata/Cloudflare недоступны 15.05.2026, рабочий только ipfs.io).',
[],
[
{ name: '9 swarm-ролей', cond: 'hive-mind Queen + 9 worker-агентов (idle)' },
{ name: 'ruflo MCP', cond: 'экспонирует ~210 инструментов' },
{ name: '4 узла-правила', cond: 'нормативная декларация уровня −1 (без enforcement)' }
],
[{ name: 'memory:project_ruflo_integration', cond: 'memory-файл документирует интеграцию' }],
[{ name: 'Pravila', desc: 'Нормативка (Pravila §0/§12, CLAUDE.md §1, PSR_v1 R0) декларирует ruflo Queen-led routing уровнем 1 — overlord над всей иерархией. Фактически ruflo — параллельная подсистема: Claude работает напрямую, ruflo daemon/hive-mind крутятся рядом. Механизма enforcement нет — декларация ≠ runtime.', type: 'RED' }]
),
ruflo_architect: nd(
'Swarm-агент роли Architect в hive-mind ruflo — проектирование архитектуры.',
'При routing-decision Queen на архитектурную задачу.',
'idle — LLM API-ключи отсутствуют, реальная агентская работа (spawn --claude) не запускалась. Generic type. Alpha.',
[{ name: 'ruflo Queen', cond: 'подчинён — Queen-led hierarchy' }],
[],
[]
),
ruflo_coder: nd(
'Swarm-агент роли Coder в hive-mind ruflo — написание кода.',
'При routing-decision Queen на задачу реализации кода.',
'idle — LLM API-ключи отсутствуют, spawn --claude не запускался. Generic type. Alpha.',
[{ name: 'ruflo Queen', cond: 'подчинён — Queen-led hierarchy' }],
[{ name: 'агент pest-parallel-debugger', cond: 'делегирует TDD-задачи в legacy execution-layer' }],
[]
),
ruflo_security: nd(
'Swarm-агент роли Security в hive-mind ruflo — аудит безопасности.',
'При routing-decision Queen на задачу безопасности.',
'idle — LLM API-ключи отсутствуют, spawn --claude не запускался. Generic type. Alpha.',
[{ name: 'ruflo Queen', cond: 'подчинён — Queen-led hierarchy' }],
[{ name: 'агент rls-reviewer', cond: 'делегирует RLS-задачи в legacy execution-layer' }],
[]
),
ruflo_rls: nd(
'Swarm-агент роли RLS-reviewer в hive-mind ruflo — проверка RLS (защиты строк по тенанту).',
'При routing-decision Queen на задачу RLS-аудита.',
'idle — LLM API-ключи отсутствуют, spawn --claude не запускался. Generic type. Alpha.',
[{ name: 'ruflo Queen', cond: 'подчинён — Queen-led hierarchy' }],
[{ name: 'скил rls-check', cond: 'делегирует RLS-скил в legacy execution-layer' }],
[]
),
ruflo_qa: nd(
'Swarm-агент роли QA в hive-mind ruflo — контроль качества.',
'При routing-decision Queen на QA-задачу.',
'idle — LLM API-ключи отсутствуют, spawn --claude не запускался. Generic type. Alpha.',
[{ name: 'ruflo Queen', cond: 'подчинён — Queen-led hierarchy' }],
[],
[]
),
ruflo_tester: nd(
'Swarm-агент роли Tester в hive-mind ruflo — прогон тестов.',
'При routing-decision Queen на задачу тестирования.',
'idle — LLM API-ключи отсутствуют, spawn --claude не запускался. Generic type. Alpha.',
[{ name: 'ruflo Queen', cond: 'подчинён — Queen-led hierarchy' }],
[],
[]
),
ruflo_reviewer: nd(
'Swarm-агент роли Reviewer в hive-mind ruflo — ревью кода.',
'При routing-decision Queen на задачу ревью.',
'idle — LLM API-ключи отсутствуют, spawn --claude не запускался. Generic type. Alpha.',
[{ name: 'ruflo Queen', cond: 'подчинён — Queen-led hierarchy' }],
[],
[]
),
ruflo_memory: nd(
'Swarm-агент роли Memory-keeper — HNSW vector index + SONA neural routing. Хранилище `.swarm/memory.db` (sql.js) + реальные embeddings Xenova/all-MiniLM-L6-v2 384-dim.',
'При обращении к памяти ruflo — переиндексация memory-файлов в HNSW-базу.',
'`ruflo memory store` CLI не персистит между запусками процесса (alpha-баг H7 — in-memory sql.js не флашится на диск).',
[{ name: 'ruflo Queen', cond: 'подчинён — Queen-led hierarchy' }],
[],
[{ name: 'memory:project_state', cond: 'переиндексирует memory-файлы в HNSW' }]
),
ruflo_daemon: nd(
'Фоновый демон ruflo под PM2 (`ruflo-daemon`, 5 workers: map/audit/optimize/consolidate/testgaps). Reboot-survival через Windows Task Scheduler.',
'Работает постоянно в фоне — 5 workers в local-fallback mode.',
'Workers в local-fallback (file I/O по `.claude-flow/`, без платных LLM-вызовов). Worker-jitter усиливает частоту Pest --parallel квирка 72 — на baseline-регрессии нужно `pm2 stop ruflo-daemon`.',
[{ name: 'ruflo Queen', cond: 'подчинён — Queen-led hierarchy' }],
[],
[],
[
{ name: 'memory:project_state', desc: 'Два хранилища памяти — статичные memory/*.md и HNSW-база ruflo (.swarm/memory.db) — рассинхронизированы. `ruflo memory store` не персистит между запусками (alpha-баг H7). Дублирование без регламента синхронизации.', type: 'BLACK' },
{ name: 'агент pest-parallel-debugger', desc: 'Daemon worker-jitter усиливает частоту Pest --parallel квирка 72 (гонка в Redis). ruflo не трогает Redis :6379 — лишь timing-amplifier. На baseline-регрессии — `pm2 stop ruflo-daemon` (квирк #93).', type: 'BLACK' }
]
),
ruflo_mcp: nd(
'MCP-сервер ruflo — stdio mode, 7-й сервер в `.mcp.json`, экспонирует ~210 инструментов (agent/memory/swarm/hooks/neural и др.).',
'Инструменты экспонированы постоянно — доступны Claude как MCP-tools.',
'Claude НЕ обязан вызывать ruflo-инструменты — отсюда статус ruflo как parallel subsystem, а не overlord. Alpha-версия.',
[{ name: 'ruflo Queen', cond: 'часть оркестратора ruflo' }],
[],
[]
),
mem_ruflo: nd(
'Memory-файл `project_ruflo_integration` — история ruflo big-bang: Phase 17 + активация рантайма, alpha-баги, операционные ноты.',
'При работе с ruflo — для контекста интеграции и известных alpha-багов.',
'Снимок состояния на 15.05.2026. Описывает alpha-баги (memory-CLI H7, embeddings-fragility) — обновлять при изменении.',
[],
[],
[{ name: 'ruflo Queen', cond: 'документирует ruflo-интеграцию' }]
),
};
// ════════════════════════════════════════════════════
// 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 (sub-policy под ruflo Queen-led routing; в уровнях 06 — hard rule, §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: 'skill и agent оба претендуют на RLS-аудит', transfers: 'coverage', mandatory: 'опционально', rule: 'нет регламента (двойное покрытие)' },
'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: 'нет регламента (изоляция worktree vs MCP)' },
'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 (sub-policy под ruflo) economy не отменяет', transfers: 'контроль', mandatory: 'hard-block', rule: 'Pravila §12.4 (только явный «не используй»)' },
// ── RUFLO ОРКЕСТРАТОР (наслой ruflo big-bang 2026-05-15) ──
'ruflo_queen->ruflo_architect': { type: 'подчиняет', when: 'hive-mind активен — Queen раздаёт задачи swarm-агентам', transfers: 'контроль', mandatory: 'опционально', rule: 'Tooling §4.10 (orchestration layer)' },
'ruflo_queen->ruflo_coder': { type: 'подчиняет', when: 'hive-mind активен — Queen раздаёт задачи swarm-агентам', transfers: 'контроль', mandatory: 'опционально', rule: 'Tooling §4.10 (orchestration layer)' },
'ruflo_queen->ruflo_security': { type: 'подчиняет', when: 'hive-mind активен — Queen раздаёт задачи swarm-агентам', transfers: 'контроль', mandatory: 'опционально', rule: 'Tooling §4.10 (orchestration layer)' },
'ruflo_queen->ruflo_rls': { type: 'подчиняет', when: 'hive-mind активен — Queen раздаёт задачи swarm-агентам', transfers: 'контроль', mandatory: 'опционально', rule: 'Tooling §4.10 (orchestration layer)' },
'ruflo_queen->ruflo_qa': { type: 'подчиняет', when: 'hive-mind активен — Queen раздаёт задачи swarm-агентам', transfers: 'контроль', mandatory: 'опционально', rule: 'Tooling §4.10 (orchestration layer)' },
'ruflo_queen->ruflo_tester': { type: 'подчиняет', when: 'hive-mind активен — Queen раздаёт задачи swarm-агентам', transfers: 'контроль', mandatory: 'опционально', rule: 'Tooling §4.10 (orchestration layer)' },
'ruflo_queen->ruflo_reviewer': { type: 'подчиняет', when: 'hive-mind активен — Queen раздаёт задачи swarm-агентам', transfers: 'контроль', mandatory: 'опционально', rule: 'Tooling §4.10 (orchestration layer)' },
'ruflo_queen->ruflo_memory': { type: 'подчиняет', when: 'hive-mind активен — Queen раздаёт задачи swarm-агентам', transfers: 'контроль', mandatory: 'опционально', rule: 'Tooling §4.10 (orchestration layer)' },
'ruflo_queen->ruflo_daemon': { type: 'подчиняет', when: 'hive-mind активен — Queen раздаёт задачи swarm-агентам', transfers: 'контроль', mandatory: 'опционально', rule: 'Tooling §4.10 (orchestration layer)' },
'ruflo_queen->ruflo_mcp': { type: 'экспонирует', when: 'MCP-сервер ruflo постоянно поднят (stdio, 7-й в .mcp.json)', transfers: 'инструменты', mandatory: 'опционально', rule: 'Tooling §4.10' },
'ruflo_queen->pravila': { type: 'конфликт', when: 'нормативка декларирует уровень −1, фактически parallel subsystem', transfers: 'контроль', mandatory: 'декларативно', rule: 'нет регламента enforcement (Pravila §0/§12, CLAUDE.md §1, PSR_v1 R0)' },
'ruflo_queen->claude_md': { type: 'перенял sub-policy', when: 'нормативная декларация уровня −1 (entry-point)', transfers: 'контроль (декларативно)', mandatory: 'декларативно — без enforcement', rule: 'CLAUDE.md §1 priority chain (уровень 1)' },
'ruflo_queen->psr_v1': { type: 'перенял sub-policy', when: 'нормативная декларация уровня −1', transfers: 'контроль (декларативно)', mandatory: 'декларативно — без enforcement', rule: 'PSR_v1 R0 (sub-policy delegation pattern)' },
'ruflo_queen->tooling': { type: 'перенял sub-policy', when: 'нормативная декларация уровня −1', transfers: 'контроль (декларативно)', mandatory: 'декларативно — без enforcement', rule: 'Tooling §4.10 (orchestration layer)' },
'ruflo_coder->ag_pest': { type: 'делегирует', when: 'при routing-decision Queen (требует LLM API-ключи — сейчас idle)', transfers: 'триггер', mandatory: 'опционально', rule: 'Tooling §4.10' },
'ruflo_security->ag_rls': { type: 'делегирует', when: 'при routing-decision Queen (требует LLM API-ключи — сейчас idle)', transfers: 'триггер', mandatory: 'опционально', rule: 'Tooling §4.10' },
'ruflo_rls->sk_rls': { type: 'делегирует', when: 'при routing-decision Queen (требует LLM API-ключи — сейчас idle)', transfers: 'триггер', mandatory: 'опционально', rule: 'Tooling §4.10' },
'ruflo_memory->mem_state': { type: 'читает', when: 'Memory-keeper переиндексирует memory-файлы в HNSW-базу', transfers: 'данные', mandatory: 'опционально', rule: 'Tooling §4.10 (HNSW vector memory)' },
'mem_ruflo->ruflo_queen': { type: 'документирует', when: 'memory-файл хранит историю ruflo-интеграции (Phase 17 + runtime)', transfers: 'данные', mandatory: 'рекомендуется', rule: 'memory/project_ruflo_integration.md' },
'ruflo_daemon->mem_state': { type: 'конфликт', when: 'два хранилища памяти — статичные .md и HNSW-база ruflo', transfers: 'coverage', mandatory: 'опционально', rule: 'нет регламента синхронизации (alpha-баг H7)' },
'ruflo_daemon->ag_pest': { type: 'конфликт', when: 'daemon worker-jitter усиливает частоту Pest квирка 72', transfers: 'coverage', mandatory: 'опционально', rule: 'memory feedback_environment квирк #93 (pm2 stop на baseline-регрессии)' },
};
// ════════════════════════════════════════════════════
// SECTION 4: VIS INIT
// ════════════════════════════════════════════════════
const GROUPS = {
rules: { color: { background: '#073642', border: '#268bd2', highlight: { border: '#93a1a1', background: '#0d4a5a' } }, font: { color: '#fdf6e3', size: 13, bold: true } },
plugins: { color: { background: '#001a00', border: '#859900', highlight: { border: '#b8cc00', background: '#002600' } }, font: { color: '#fdf6e3', size: 12 } },
skills_sp: { color: { background: '#1a0033', border: '#6c71c4', highlight: { border: '#9b9fea', background: '#250047' } }, font: { color: '#fdf6e3', size: 11 } },
skills_proj: { color: { background: '#2d0020', border: '#d33682', highlight: { border: '#e869a8', background: '#3d0028' } }, font: { color: '#fdf6e3', size: 12 } },
hooks: { color: { background: '#002233', border: '#2aa198', highlight: { border: '#4dd7ce', background: '#003344' } }, font: { color: '#fdf6e3', size: 11 } },
agents: { color: { background: '#1a1200', border: '#b58900', highlight: { border: '#e0ad00', background: '#261a00' } }, font: { color: '#fdf6e3', size: 11 } },
mcp: { color: { background: '#2d1200', border: '#cb4b16', highlight: { border: '#ff6b30', background: '#3d1900' } }, font: { color: '#fdf6e3', size: 11 } },
lefthook: { color: { background: '#2d0000', border: '#dc322f', highlight: { border: '#ff5f5c', background: '#3d0000' } }, font: { color: '#fdf6e3', size: 10 } },
memory: { color: { background: '#112233', border: '#586e75', highlight: { border: '#839496', background: '#1a2f40' } }, font: { color: '#eee8d5', size: 10 } },
ruflo: { color: { background: '#332100', border: '#ff8800', highlight: { border: '#ffaa33', background: '#4d3300' } }, font: { color: '#fdf6e3', size: 12, bold: true } },
};
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';
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>`;
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 = '';
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');
}
network.on('click', params => {
if (params.nodes.length === 1) {
const id = params.nodes[0];
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) {
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(),
};
// ── 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 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),
}));
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();
}
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');
});
}
// ── Legend click delegation ───────────────────────
document.getElementById('cat-legend').addEventListener('click', e => {
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
};
})();
window.addEventListener('DOMContentLoaded', restoreLegendWidth);
</script>
</body>
</html>