Files
portal/docs/automation-graph.html
T
Дмитрий 718a6e6ff3 chore(graph): rewrite group B (skills + hooks, 21 nodes) — plain language
Переписаны nd() блоки для 14 Superpowers-скилов (sk_brainstorm/sk_tdd/sk_debug/sk_wplans/sk_eplans/sk_verify/sk_parallel/sk_worktree/sk_pr/sk_subagent/sk_wskills/sk_spreview/sk_coderev/sk_elements), 2 проектных (sk_rls/sk_qitem), 5 хуков (hk_pre_claude/hk_post_md/hk_post_schema/hk_session/hk_economy). Жаргон-блэклист убран, параграф-ссылки сохранены. Iter2 spec §3 group B.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:42:32 +03:00

1255 lines
89 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-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 { background: #2d0000; border-radius: 4px; padding: 6px 8px; }
.conflict-item .cname { color: #ff5f57; font-weight: 600; font-size: 12px; }
.conflict-item .cdesc { color: #cb9b96; 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; }
</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-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>
<div id="cat-legend">
<div class="cat-item"><div class="cat-dot" style="background:#268bd2"></div>Правила</div>
<div class="cat-item"><div class="cat-dot" style="background:#859900"></div>Плагины</div>
<div class="cat-item"><div class="cat-dot" style="background:#6c71c4"></div>Скилы Superpowers</div>
<div class="cat-item"><div class="cat-dot" style="background:#d33682"></div>Скилы проекта</div>
<div class="cat-item"><div class="cat-dot" style="background:#2aa198"></div>Хуки</div>
<div class="cat-item"><div class="cat-dot" style="background:#b58900"></div>Агенты</div>
<div class="cat-item"><div class="cat-dot" style="background:#cb4b16"></div>MCP-серверы</div>
<div class="cat-item"><div class="cat-dot" style="background:#dc322f"></div>Lefthook jobs</div>
<div class="cat-item"><div class="cat-dot" style="background:#586e75"></div>Memory files</div>
<div class="cat-item"><div class="cat-dot" style="background:#ff5f57; border: 1px dashed #ff5f57;"></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.13', group: 'rules', size: 38, ring: 0, ...pos(0, 0) },
{ id: 'claude_md', label: 'CLAUDE.md v1.92', group: 'rules', size: 34, ring: 1, ...pos(1, 30) },
{ id: 'psr_v1', label: 'PSR_v1 v2.1', group: 'rules', size: 32, ring: 1, ...pos(1, 150) },
{ id: 'tooling', label: 'Tooling v1.17', 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) },
];
// ════════════════════════════════════════════════════
// SECTION 2: EDGES
// ════════════════════════════════════════════════════
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) => ({
from, to,
title: label,
label: '⚡',
dashes: true,
width: 2,
color: { color: '#ff5f57', highlight: '#ff8880', hover: '#ff8880' },
arrows: { to: { enabled: true, scaleFactor: 0.7 }, from: { enabled: true, scaleFactor: 0.7 } },
font: { color: '#ff5f57', 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'),
// ══════════════════════════════════════════════════
// КОНФЛИКТЫ (красные пунктирные рёбра)
// ══════════════════════════════════════════════════
CONFLICT('psr_v1', 'claude_md', '⚡ T2.2'),
CONFLICT('upm', 'fd_plugin', '⚡ R14.5'),
CONFLICT('mcp_21st', 'fd_plugin', '⚡ R14.5'),
CONFLICT('sk_rls', 'ag_rls', '⚡ перекрытие RLS'),
CONFLICT('hookify_plugin','hk_pre_claude', '⚡ может затенить'),
CONFLICT('hk_economy', 'superpowers', '⚡ §12 vs Economy'),
];
// ════════════════════════════════════════════════════
// SECTION 3: NODE DETAILS
// ════════════════════════════════════════════════════
const CATEGORY_LABELS = {
rules: 'Правило', plugins: 'Плагин', skills_sp: 'Скил Superpowers',
skills_proj: 'Скил проекта', hooks: 'Хук .claude', agents: 'Агент',
mcp: 'MCP-сервер', lefthook: 'Lefthook job', memory: 'Memory-файл'
};
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 (обязательные скилы) нельзя отменить — даже режимом экономии или просьбой «не используй сейчас». Расходимость с другими документами — нарушение §7.',
[],
[
{ name: 'CLAUDE.md', cond: 'подчинён, уровень 2a в цепочке приоритетов' },
{ name: 'PSR_v1', cond: 'подчинён, уровень 3 в цепочке приоритетов' },
{ name: 'Superpowers', cond: '§12 обязывает запускать скил первым' },
{ name: 'Все компоненты', cond: 'через цепочку приоритетов §1' }
],
[{ name: 'CLAUDE.md', cond: 'оба читаются при старте сессии' }]
),
claude_md: nd(
'Оперативная карта проекта — список технологий, команд, фаз, ссылок на документы.',
'Читается при старте каждой сессии; обновляется при новом инструменте или новой фазе.',
'Править можно только через скил `/claude-md-management:claude-md-improver` или `:revise-claude-md` (правило §5 п.10). Прямые Edit/Write блокируются хуком предупреждения.',
[{ name: 'Pravila', cond: 'всегда подчинён (уровень 2a)' }],
[
{ name: 'Tooling v1.17', cond: 'ссылается как на реестр инструментов' },
{ name: 'claude-md-mgmt', cond: 'правило §5 п.10 — единственный канал правок' }
],
[
{ name: 'Pravila', cond: 'оба читаются при старте сессии' },
{ name: 'Tooling', cond: 'оба — оперативные карты уровня 2' }
],
[{ name: 'PSR_v1', desc: 'Правило §5 п.10 запрещает прямые правки, но PSR_v1 это явно не повторяет — есть риск Edit без скила' }]
),
psr_v1: nd(
'Правила совместной работы плагинов — кто с кем работает, какая процедура обязательна.',
'При выборе UI-инструмента (FD против UPM против 21st), при координации парных плагинов, при включении off-phase MCP.',
'Обязательное правило R14.5: UPM, 21st, FD — нельзя одновременно. Обязательное правило R6.0 (фильтр стека) и R6.1 (палитра Forest) — нужно соблюдать при UI-выводе плагинов.',
[{ name: 'Pravila', cond: 'подчинён, уровень 3 в цепочке' }],
[
{ name: 'Superpowers + Frontend Design', cond: 'координирует как пару плагинов' },
{ name: 'UI UX Pro Max', cond: 'R14.3: включается только через процедуру' },
{ name: '21st Magic MCP', cond: 'R14.4: включается только через процедуру' }
],
[{ name: 'CLAUDE.md', cond: 'обе — оперативные карты, правятся согласованно' }],
[{ name: 'CLAUDE.md', desc: 'CLAUDE.md §5 п.10 требует править только через скил claude-md-mgmt, а PSR_v1 это ограничение не повторяет — риск прямых Edit' }]
),
tooling: nd(
'Реестр 35 инструментов — когда что использовать, команды установки, конфликты.',
'При выборе инструмента для фазы (0/1/2/3), при добавлении нового инструмента, при обновлении версий.',
'При прямом конфликте с 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 скилов для тестов, отладки, планирования, параллельной работы.',
'При творческих, отладочных, тестовых и многошаговых задачах: brainstorm / TDD / debug / verify / writing-plans / parallel-work / work-tree / finishing-PR / subagent / writing-skills (карта типов в §12.2 Pravila).',
'Обязательное правило §12: единственная отмена — явная просьба заказчика «не используй superpowers сейчас» на текущее действие. Раздел §9 «Отступления» к §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 (§12 нельзя отменить)' }]
),
fd_plugin: nd(
'Плагин знаний о UI — Vue, Vuetify, доступность, паттерны компонентов для Лидерры.',
'При UI/UX задачах — компоненты, экраны, паттерны взаимодействия; в паре с Superpowers (процесс).',
'Фильтр стека R6.0: срезать React/Tailwind/shadcn/JSX в Vue 3 + Vuetify 3. Обязательное правило палитры Forest R6.1 для цветов, шрифтов и иконок.',
[{ name: 'PSR_v1', cond: 'R5: подчинён как часть пары плагинов' }],
[],
[{ name: 'Superpowers', cond: 'пара — FD даёт UI-знания, Superpowers даёт процесс' }],
[
{ name: 'UI UX Pro Max', desc: 'Правило PSR_v1 R14.5: нельзя одновременно — оба включены в настройках, но должны чередоваться' },
{ name: '21st Magic MCP', desc: 'Правило PSR_v1 R14.5: нельзя одновременно с FD — оба потенциально доступны' }
]
),
upm: nd(
'Резервная библиотека UI — 50+ стилей, 161 палитра, 99 правил-подсказок UX. Только по процедуре.',
'Только по процедуре PSR_v1 R14.3: запасной вариант к FD ИЛИ «третий вариант» в архитектурном решении.',
'R14.5: нельзя одновременно с FD/21st. Фильтр стека R6.0 и обязательное правило палитры Forest R6.1 — обязательны. Проверка доступности Pa11y перед выкаткой.',
[{ name: 'PSR_v1', cond: 'R14.3: включается только через процедуру, не сам по себе' }],
[],
[],
[{ name: 'Frontend Design', desc: 'Правило PSR_v1 R14.5: нельзя одновременно — UPM как материал, FD как решатель; риск смешать роли' }]
),
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: 'PreToolUse:CLAUDE.md-warn', desc: 'hookify создаёт новые PreToolUse-хуки на лету — может перезаписать или конкурировать с этим хуком' },
{ name: 'economy-mode хук', desc: 'новые хуки от hookify могут конфликтовать с логикой парсера режима экономии' }
]
),
// ── СКИЛЫ 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 (тест проходит).',
'При любом новом боевом коде — 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 в той же сессии.',
'Каждый шаг отмечается галочкой, коммиты не объединяются — атомарно по одному за шаг (Pravila §4.2).',
[{ name: 'Superpowers', cond: 'содержит' }],
[],
[{ name: 'writing-plans', cond: 'получает план от writing-plans' }, { name: 'subagent-driven', 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 для изоляции' }]
),
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 (gitleaks по всей истории + lychee) — не обходить через `--no-verify`. Pravila §4.2.',
[{ name: 'Superpowers', cond: 'содержит' }],
[],
[{ name: 'MCP: github', cond: 'создаёт PR через GitHub MCP' }, { name: 'lefthook pre-push', cond: 'запускает gitleaks + 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(
'Систематический разбор кода — безопасность, тесты, архитектура, соответствие правилам.',
'Перед merge PR; после крупной серии коммитов; при подготовке к релизу; при подозрении на регрессию.',
'Без выборочности: разбор всех изменений, а не только подозрительных. SAST (статический анализ кода на уязвимости, Semgrep) включается обязательно.',
[{ name: 'Superpowers', cond: 'содержит' }],
[],
[{ name: 'MCP: semgrep', cond: 'SAST-проверка при code review' }]
),
sk_elements: nd(
'Улучшает написание текстов и документации — ясность, лаконичность, без воды.',
'При написании спецификации/плана/CHANGELOG/описания PR — для общения с командой.',
'Без воды. Без «легко», «просто», «всего лишь». Каждое утверждение измеримо.',
[{ name: 'Superpowers', cond: 'содержит' }],
[],
[]
),
// ── СКИЛЫ ПРОЕКТА ────────────────────────────────
sk_rls: nd(
'7-шаговый чеклист RLS (защита строк по тенанту) для новой таблицы: tenant_id, ENABLE RLS (включение защиты), политики, права для 5 ролей, CHANGELOG, squawk, smoke-тест.',
'При создании новой таблицы в db/schema.sql ИЛИ при правках существующих политик RLS.',
'Права для 5 ролей обязательны (crm_app_user / crm_app_admin / crm_supplier_worker BYPASSRLS (право обходить RLS — для системных задач) / 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; нет чёткой границы когда какой' }]
),
sk_qitem: nd(
'Добавляет новый открытый вопрос в реестр Открытые_вопросы_v8_3.md с обновлением счётчиков §0 и версии.',
'При появлении нового открытого вопроса (Биз-/CTO-/Ю-/Диз-/DO-/OPEN-) — формальная запись в реестр.',
'Категория (Биз-/CTO-/...) обязательна. Связанные документы (CLAUDE.md/Pravila/PSR_v1/Tooling) — синхронизируются.',
[],
[],
[{ name: 'claude-md-mgmt', cond: 'скил делегирует правку CLAUDE.md через плагин (правило §5п.10 в нормативке)' }]
),
// ── ХУКИ ─────────────────────────────────────────
hk_pre_claude: nd(
'Блокирует прямое редактирование CLAUDE.md — срабатывает на Edit/Write по этому файлу.',
'PreToolUse — перед каждым вызовом Edit/Write, фильтр путей нацелен на CLAUDE.md.',
'Обход запрещён. Единственный способ редактировать — скил claude-md-mgmt (правило §5п.10 в нормативке).',
[{ name: '.claude/settings.json', cond: 'описан как хук PreToolUse' }],
[],
[],
[{ name: 'hookify (плагин)', desc: 'hookify динамически создаёт новые хуки PreToolUse — может перезаписать или конкурировать с этим хуком' }]
),
hk_post_md: nd(
'После каждого Edit .md-файла запускает markdownlint --fix автоматически.',
'PostToolUse — после Edit/Write на *.md (кроме корневого CLAUDE.md, чтобы не зациклить).',
'Не правит CLAUDE.md (исключён из фильтра путей). При неисправимой ошибке (например, битая ссылка) — предупреждение, не блокировка.',
[{ name: '.claude/settings.json', cond: 'описан как хук PostToolUse' }],
[],
[{ name: 'lefthook:markdownlint', 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: 'lefthook:squawk', cond: 'оба реагируют на изменения SQL' }]
),
hk_session: nd(
'При старте каждой сессии подгружает CLAUDE.md, Pravila и ключевые memory-файлы в контекст.',
'SessionStart — единожды при инициализации сессии Claude Code.',
'Список memory-файлов фиксированный — для расширения править настройку хука. Не читает 80+ квирков целиком — выборочно по релевантности.',
[{ name: '.claude/settings.json', cond: 'описан как хук SessionStart' }],
[
{ name: 'memory:user_profile', cond: 'читает' },
{ name: 'memory:feedback_env', cond: 'читает' },
{ name: 'memory:project_state', cond: 'читает' },
{ name: 'memory:feedback_superpowers', cond: 'читает' },
{ name: 'memory:feedback_plugins', cond: 'читает' }
],
[]
),
hk_economy: nd(
'Перед каждым промптом разбирает «экономия X%» и выставляет режим строгости (0% = максимальное качество, 100% = по умолчанию).',
'UserPromptSubmit — перед каждым промптом пользователя; ищет шаблон /экономия\\s*(\\d+)%/.',
'Правило §12 **НЕ** отменяется этим режимом ни на каком уровне. Действует только на текущую задачу — следующий промпт разбирается заново.',
[{ name: '.claude/settings.json', cond: 'описан как хук UserPromptSubmit' }],
[],
[],
[{ name: 'Superpowers (§12)', desc: 'Экономия=100% теоретически может «сэкономить» вызов скила, нарушая обязательное правило (см. §12) — §12 неотменяем, экономия его не отменяет' }]
),
// ── АГЕНТЫ ───────────────────────────────────────
ag_pest: nd(
'Диагностирует падения Pest --parallel: классифицирует как реальный баг или один из 5 квирков (72-73-77...).',
'При падении Pest --parallel ИЛИ при subdir-only flake (как в audit Phase 3 SyncSupplierProjectsJobTest).',
'READ-ONLY на коде, не правит самостоятельно. Falsify каждую гипотезу через реальный запуск, не «вероятно квирк».',
[{ name: 'CLAUDE.md §6', cond: 'описывает когда вызывать' }],
[],
[{ name: 'MCP: redis', cond: 'читает Redis для дебага quirk 72 (supplier:session race)' }]
),
ag_rls: nd(
'Проверяет RLS compliance в миграциях — 7-item чеклист с реальными запусками команд, READ-ONLY.',
'При создании/правке миграции в db/migrations/ ИЛИ при правке db/schema.sql ИЛИ при PR review с DB-изменениями.',
'READ-ONLY (только Read/Grep/Glob/Bash) — не пишет код. Не альтернатива sk_rls skill, у них разные сценарии.',
[{ name: 'CLAUDE.md', cond: 'описывает в §6 и агенты/ директории' }],
[],
[{ name: 'MCP: laravel-boost', cond: 'SQL запросы к db/schema.sql' }],
[{ name: 'rls-check скил', desc: 'оба покрывают RLS compliance, нет чёткой границы: агент — для PR/diff, скил — для ручной таблицы' }]
),
ag_statusline: nd(
'Настраивает строку состояния Claude Code через редактирование файла конфига.',
'При запросе пользователя «настрой statusline» — редкая разовая задача.',
'Правит только settings.json statusline-секцию, не другие части конфига.',
[],
[],
[]
),
ag_guide: nd(
'Отвечает на вопросы об API Claude Code, SDK, MCP серверах, хуках, slash commands.',
'При вопросе про возможности Claude Code/SDK/API — «Can Claude...», «How do I...», «Does Claude...».',
'READ-ONLY: ищет в документации, не правит код. Не для дебага кода — только для вопросов о platform.',
[],
[],
[{ name: 'MCP: github', cond: 'ищет примеры в репозитории при необходимости' }]
),
ag_explore: nd(
'Быстрый поиск файлов по паттерну или символу — только чтение, без анализа.',
'При targeted lookup — найти файл по имени или grep по символу/keyword.',
'Не для open-ended exploration. Не для review/audit (читает excerpts, миссует content past read window).',
[{ name: 'subagent-driven скил', cond: 'запускается для задач поиска' }],
[],
[]
),
ag_general: nd(
'Универсальный агент для сложных multi-step исследований с полным доступом к инструментам.',
'При сложных multi-step research / coding задачах, когда Explore недостаточно (нужен анализ, не только поиск).',
'Полный доступ к инструментам — может писать код. Дороже Explore по токенам.',
[{ name: 'subagent-driven скил', cond: 'запускается для основных задач' }],
[],
[]
),
ag_plan: nd(
'Архитектор: разрабатывает планы реализации, выявляет критичные файлы, рассматривает trade-offs.',
'При архитектурном планировании multi-component задачи (не для small refactor).',
'READ-ONLY (no Edit/Write/NotebookEdit). Возвращает план, не реализует его.',
[{ name: 'subagent-driven скил', 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 only). Рекомендует хуки, не создаёт их сам — передаёт в hookify plugin.',
[],
[],
[{ name: 'hookify (плагин)', cond: 'агент передаёт рекомендации в плагин' }]
),
ag_pcreator: nd(
'Создаёт конфигурацию новых агентов по описанию пользователя.',
'При запросе «create an agent that...» — генерация agent.md по описанию функциональности.',
'Только Write/Read tools. Не валидирует созданного агента — передаёт в plugin-validator.',
[],
[],
[{ name: 'plugin-dev:plugin-validator', cond: 'validator проверяет созданного агента' }]
),
ag_pvalid: nd(
'Проверяет структуру плагина на корректность — plugin.json, tools, manifest.',
'После создания/изменения plugin.json или plugin component\'ов — proactive validation.',
'READ-ONLY (Read/Grep/Glob/Bash). Сам не правит — даёт список нарушений.',
[],
[],
[{ name: 'plugin-dev:agent-creator', cond: 'validator и creator работают в паре' }]
),
ag_skreview: nd(
'Оценивает качество написанного скила — описание, workflow, примеры, best practices.',
'После создания/изменения скила через writing-skills — proactive review.',
'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.',
'Не для production users. На каждой сессии один shared browser — конфликты при parallel-work (см. квирк #2 в memory).',
[{ name: 'CLAUDE.md §3.1 #2', cond: 'активен с фазы 0' }],
[],
[{ name: 'SessionStart хук', cond: 'используется для визуальной проверки прототипов' }]
),
mcp_gh: nd(
'GitHub API — читает/создаёт PR, issues, коммиты, branches в репозитории CoralMinister/lidpotok.',
'При работе с PR/issues, при поиске в репозитории, при создании PR через finishing-pr skill.',
'Не push на main без явного одобрения. Pravila §4: атомарные коммиты, не batch\'ить через MCP.',
[{ name: 'CLAUDE.md §3.1 #3', cond: 'активен с фазы 0' }],
[],
[{ name: 'finishing-pr скил', cond: 'создаёт PR через этот MCP' }]
),
mcp_boost: nd(
'Laravel Boost — SQL запросы к dev-БД, схема таблиц, логи ошибок, поиск в docs Laravel.',
'Фаза 1+: при SQL запросах, при поиске в Laravel docs, при работе с Eloquent моделями.',
'**READ-ONLY к prod** — `.env.production` не должен попадать в локальный конфиг. Не использовать Inertia/Livewire/Tailwind/Filament guidelines.',
[{ name: 'CLAUDE.md §3.2 #10', cond: 'активен с фазы 1, READ-ONLY к prod запрещено' }],
[],
[{ name: 'ag_rls агент', cond: 'rls-reviewer использует Boost для SQL' }, { name: 'sk_rls скил', cond: 'rls-check использует Boost' }]
),
mcp_semgrep: nd(
'SAST анализ кода — ищет уязвимости, XSS, SQLi, нарушения правил по паттернам.',
'Фаза 3 pre-production: при code review (sk_coderev), при CI перед релизом.',
'Конфигурация в .semgrep.yml. False-positive\'ы документируются inline.',
[{ name: 'CLAUDE.md §3.4 #25', cond: 'фаза 3 pre-production' }],
[],
[{ name: 'code-review скил', cond: 'Semgrep MCP используется при code review' }]
),
mcp_sentry: nd(
'Читает ошибки из self-hosted Sentry в Yandex Cloud — события, стектрейсы, метрики. READ-ONLY.',
'При расследовании production runtime ошибок (после deployment Б-1).',
'**READ-ONLY** scope (org:read/project:read/event:read). Pending Sentry instance deployment Б-1 (зависит от ООО registration).',
[{ name: 'CLAUDE.md §3.3 #34', cond: 'off-phase debug-runtime; pending Sentry deployment Б-1' }],
[],
[]
),
mcp_redis: nd(
'Читает Redis/Memurai — ключи, очереди, кэш для дебага race conditions. СТРОГО READ-ONLY.',
'При дебаге Redis-очередей (Pest --parallel quirk 72), при анализе кэш-инвалидации.',
'**СТРОГО READ-ONLY** — никаких DEL/FLUSHDB/SET/LPUSH из Claude (только GET/KEYS/LIST). Deprecated Anthropic source — миграция post-MVP.',
[{ name: 'CLAUDE.md §3.3 #35', cond: 'off-phase debug-runtime; PSR_v1 R10.1 блок 3' }],
[],
[{ name: 'pest-parallel-debugger агент', cond: 'агент использует для quirk 72 (Redis race)' }]
),
mcp_21st: nd(
'Генератор стартовых шаблонов UI-компонентов через LLM. Активация только через R14.4 pipeline.',
'Только через PSR_v1 R14.4 pipeline: pre-check 9 условий (брендовый App*? Vuetify-эквивалент? существующий компонент?).',
'R14.5: не параллельно с FD/UPM. JSX→Vue, Tailwind→utility, shadcn→Vuetify обязательны. Pa11y a11y на deployable.',
[{ name: 'PSR_v1 R14.4', cond: 'строгий pre-check: 9 условий перед активацией' }],
[],
[],
[{ name: 'Frontend Design', desc: 'PSR_v1 R14.5: нельзя параллельно — 21st как генератор материала, FD как решатель; риск смешивания ролей и нарушения R6 фильтра стека' }]
),
// ── LEFTHOOK JOBS ─────────────────────────────────
lh_gitleaks: nd(
'pre-commit: ищет ПДн, токены и API-ключи в staged-файлах. Блокирует коммит при находке.',
'pre-commit stage: на каждый `git commit` — сканирует только staged файлы.',
'Bypass через `--no-verify` запрещён (Pravila §4.2). Находка = blocking error, не warning. Обновление словаря — `.gitleaksignore` для known-false-positives.',
[{ name: 'lefthook.yml', cond: 'job #1 в pre-commit, parallel:false' }],
[],
[{ name: 'lefthook:gitleaks pre-push', cond: 'pre-push версия сканирует всю историю' }]
),
lh_mdlint: nd(
'pre-commit: проверяет и авто-исправляет стиль Markdown файлов перед коммитом.',
'pre-commit stage: на каждый `git commit` со staged `.md` файлами.',
'Конфиг `.markdownlint-cli2.cjs`. Auto-fix включён (stage_fixed: true) — fixed файлы повторно staged.',
[{ name: 'lefthook.yml', cond: 'job #2 в pre-commit' }],
[],
[{ name: 'PostToolUse:markdownlint-fix хук', cond: 'оба делают одно — хук немедленно, lefthook при коммите' }]
),
lh_cspell: nd(
'pre-commit: проверяет орфографию в .md файлах по словарю cspell-words.txt.',
'pre-commit stage: на каждый `git commit` со staged `.md` файлами.',
'Словарь: `cspell-words.txt`. Кириллица в нижнем регистре. Не bypass\'ить — добавлять валидные слова в словарь.',
[{ name: 'lefthook.yml', cond: 'job #3 в pre-commit' }],
[],
[]
),
lh_stylelint: nd(
'pre-commit: линтует CSS в HTML-прототипах (web/v8/*.html).',
'pre-commit stage: на staged `.html`/`.css` файлах.',
'Stylelint конфиг в `.stylelintrc`. Deprecated keywords (`word-break: break-word`) блокируют commit.',
[{ name: 'lefthook.yml', cond: 'job #4 в pre-commit' }],
[],
[]
),
lh_pint: nd(
'pre-commit: авто-форматирует PHP код по PSR-стандарту, stage_fixed:true.',
'pre-commit stage: на каждый staged `.php` файл (root: app/).',
'Auto-fix включён — fixed файлы повторно staged. Конфиг `app/pint.json`.',
[{ name: 'lefthook.yml', cond: 'job #5 в pre-commit, root:app/' }],
[],
[]
),
lh_larastan: nd(
'pre-commit: статический анализ PHP — находит ошибки типов выше baseline (Larastan L9).',
'pre-commit stage: на staged `.php` файлах (root: app/).',
'Baseline `phpstan-baseline.neon` фиксирован — новые ошибки блокируют commit. Не bump\'ить baseline без обоснования.',
[{ name: 'lefthook.yml', cond: 'job #6 в pre-commit, root:app/' }],
[],
[{ name: 'MCP: laravel-boost', cond: 'Boost даёт контекст типов через IDE stubs' }]
),
lh_squawk: nd(
'pre-commit: линтует SQL миграции на безопасные паттерны (lock-safe, concurrent, etc.).',
'pre-commit stage: на staged `database/migrations/*.php` или `db/*.sql` файлах.',
'Конфиг squawk.toml. Не разрешать UNSAFE миграции (`ALTER TABLE ADD COLUMN NOT NULL DEFAULT`) без явного `-- squawk-ignore`.',
[{ name: 'lefthook.yml', cond: 'job #7 в pre-commit, glob:*.sql' }],
[],
[{ name: 'Tooling #15 squawk', cond: 'соответствует §3.2 entry' }]
),
lh_eslint: nd(
'pre-commit: линтует Vue/TypeScript файлы в app/resources/js/.',
'pre-commit stage: на staged `.vue`/`.ts`/`.tsx` файлах (root: app/).',
'Flat-config eslint 10 + plugin-vue 10. Не bypass\'ить через `--no-verify`. Errors blocking, warnings allowed.',
[{ name: 'lefthook.yml', cond: 'job #8 в pre-commit, root:app/' }],
[],
[]
),
lh_gitleaks2: nd(
'pre-push: полный скан всей истории коммитов на секреты (строже pre-commit job).',
'pre-push stage: перед `git push` к remote — сканирует всю history новых коммитов.',
'Bypass через `--no-verify` запрещён. На больших push (200+ commits) занимает 30+ сек.',
[{ name: 'lefthook.yml', cond: 'job в pre-push' }],
[],
[{ name: 'lefthook:gitleaks', cond: 'pre-push версия строже pre-commit: проверяет всю историю' }]
),
lh_lychee: nd(
'pre-push: проверяет все ссылки в .md файлах на битые (docs/**/*.md, db/**/*.md, *.md).',
'pre-push stage: перед `git push` — проверяет ссылки во всех `.md` файлах в монорепе.',
'Внешние ссылки проверяются с timeout 10s; offline — fail. Конфиг `lychee.toml`. Не bypass\'ить через `--no-verify`.',
[{ name: 'lefthook.yml', cond: 'job в pre-push' }],
[],
[{ name: 'CLAUDE.md', cond: 'проверяет ссылки в CLAUDE.md в том числе' }]
),
// ── MEMORY FILES ─────────────────────────────────
mem_user: nd(
'Профиль заказчика: Дмитрий, Windows Server 2022, VSCode, русский язык, путь к проекту.',
'Читается при старте каждой сессии через SessionStart хук — для language/preferences.',
'Не содержит секретов. При смене заказчика — переписать полностью.',
[],
[],
[{ name: 'SessionStart хук', cond: 'читается при старте каждой сессии' }]
),
mem_comm: nd(
'Стиль общения: короткие команды («а», «б», «делай»), варианты A/B/C, фиксация переоткрытий.',
'Читается при работе с пользователем — для соответствия стилю общения.',
'Не путь к code, а meta-rules коммуникации. Корректировки через явное feedback от заказчика.',
[],
[],
[{ name: 'memory:user_profile', cond: 'дополняет профиль пользователя' }]
),
mem_env: nd(
'80+ квирков окружения: специфика Windows Server, баги инструментов, митигации.',
'При unexpected behavior — сначала проверка memory_env на известный квирк.',
'Не дубль — добавлять только новые квирки. Quirk count актуализируется в `project_state.md`.',
[],
[],
[
{ name: 'pest-parallel-debugger агент', cond: 'квирки 72/77 используются агентом' },
{ name: 'SessionStart хук', cond: 'читается при старте' }
]
),
mem_sp: nd(
'Hard rule §12 + economy hook architecture из 6 компонентов — дисциплина инвокации скилов.',
'При работе со скилами — для соответствия §12 hard rule.',
'Описывает архитектуру economy hook (6 компонентов) — менять только при изменении самого хука.',
[],
[],
[{ name: 'economy-mode хук', cond: 'связаны: memory описывает архитектуру хука' }]
),
mem_plugins: nd(
'Правила парного стека плагинов, debug-runtime MCP, tier-структура PSR_v1.',
'При работе с плагинами FD/UPM/21st/Sentry/Redis MCP — для tier-разделения.',
'Синхронизируется с PSR_v1 — изменения в memory только если изменился сам PSR_v1.',
[],
[],
[{ name: 'PSR_v1', cond: 'memory отражает актуальные версии PSR_v1' }]
),
mem_state: nd(
'Текущее состояние: ветка, тесты (Pest/Vitest), последние коммиты, активные задачи.',
'Читается при старте сессии — для быстрого контекста; обновляется после крупных вех.',
'Может становиться stale — re-Read при сомнении. Не доверять данным более 2-3 дней без verify.',
[],
[],
[{ name: 'SessionStart хук', cond: 'читается при старте для быстрого контекста' }]
),
mem_phase1: nd(
'Стратегия фазы 1: native Windows стек без Docker, pg_partman заменён Artisan cron.',
'При работе с infrastructure фазы 1 (PG/Redis/PHP-CLI на Windows native).',
'Стратегия фиксированная до закрытия Б-1 (Managed PG в YC) или 6 месяцев — переоценка указана в файле.',
[],
[],
[]
),
mem_archive: nd(
'Карта источников истины: версии всех 13+ ключевых документов проекта.',
'При вопросах «какая версия документа X» или «где источник истины для Y».',
'Синхронизируется с CLAUDE.md §0. Изменения в memory только если изменился CLAUDE.md §0.',
[],
[],
[{ name: 'CLAUDE.md', cond: 'memory синхронизирует версии с CLAUDE.md §0' }]
),
mem_github: nd(
'GitHub репозиторий CoralMinister/lidpotok: HEAD, hooks, правила push.',
'При работе с GitHub — push, PR, branch operations.',
'Не push --force на main (warning в Pravila). Pre-push hooks обязательны.',
[],
[],
[{ name: 'MCP: github', cond: 'MCP и memory дополняют друг друга для работы с GitHub' }]
),
mem_handoff: nd(
'Дизайн-handoff Платона: что из liderra_v8_handoff/ используем, что нет.',
'При UI/дизайн задачах — для разделения «брендбук используем» vs «состав фич — по ТЗ v8.5».',
'Handoff — только дизайн/токены/компоненты. Функционал, состав экранов — НЕ из handoff (берём из ТЗ).',
[],
[],
[]
),
mem_audit: nd(
'Полный аудит портала 13.05.2026: 38 находок, вердикт 🟡, 10 Q-items закрыты.',
'При вопросах про аудит или его последствия (Q.DEFER, P0/P1/P2 распределение).',
'Снимок состояния — не редактировать при последующих аудитах, создавать новые memory-файлы.',
[],
[],
[]
),
mem_supplier: nd(
'Прогресс интеграции поставщика Plans 1-5: Tasks, коммиты, блокеры.',
'При работе с supplier integration (Plans 1-5) — для текущего state и blockers.',
'Блокеры (Б-1, credentials) — внешние, не разрешаются Claude\'ом. Только трекинг.',
[],
[],
[]
),
mem_brain: nd(
'Claude Brain v1.0 — extracted brain repo, тег brain-v1.0, install.sh.',
'При работе с brain repository или install.sh для consumer sync.',
'GitHub push на brain repo blocked (8.2). Не пытаться push без разрешения.',
[],
[],
[]
),
mem_redesign: nd(
'Редизайн Quiet Luxury: 20 коммитов, foundation CSS + composables + AppSidebar rewrite.',
'При работе с portal redesign (frontend, AppSidebar, foundation CSS).',
'I2 backlog в spec §15 — 10 пунктов отложены до I2. Не реализовывать I2 пункты без явного запроса.',
[],
[],
[]
),
mem_devindices: nd(
'Dev Element Indices — временная feedback-фича для разработки; к удалению в продакшене.',
'При работе с dev feedback (e.g. «1030 измени цвет») — для понимания number → element mapping.',
'**TEMPORARY** — заказчик прямо сказал «уберём в конечном релизе». Не вкладываться в долгосрочную инфраструктуру.',
[],
[],
[]
),
};
// ════════════════════════════════════════════════════
// 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 } },
};
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 showLegend(nodeId) {
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) {
ldConflicts.innerHTML = details.conflicts.map(c =>
`<div class="conflict-item"><div class="cname">⚡ ${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');
}
network.on('click', params => {
if (params.nodes.length === 1) {
showLegend(params.nodes[0]);
} else if (params.nodes.length === 0 && params.edges.length === 0) {
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 () {
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' } });
showLegend(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 = '';
nodesDS.update(NODES.map(n => ({ id: n.id, borderWidth: 2, opacity: 1.0 })));
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 */ }
});
})();
window.addEventListener('DOMContentLoaded', restoreLegendWidth);
</script>
</body>
</html>