Артефакты параллельной Claude-сессии (mode «экономия 0%», 10.05.2026 ночь). Spec (385 строк): расширение hooks-skills-plugins-map.html новой §X «Связи — interactive map». Force-directed network через D3.js v7 (CDN), ~50 узлов (плагины + скилы + хук-скрипты + hook events + state-файл + permissions + Pravila §12 + CLAUDE.md), 52 ребра. Vintage-blueprint aesthetic. Drag/click/hover/category filters/reset. Plan (1246 строк): пошаговая реализация для executing-plans с TDD-структурой. cspell-words.txt: +3 термина (диспатчу/скилы/ребёр). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
48 KiB
Interactive Connections Graph — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Добавить в hooks-skills-plugins-map.html интерактивную force-directed диаграмму на D3.js v7 с 50 узлами, 52 рёбрами, drag/click/hover/filter взаимодействием.
Architecture: Новая HTML-секция §X «Связи — interactive map» с SVG-контейнером, D3.js v7 из CDN, vanilla JS для interactivity, vintage-blueprint aesthetic (наследуется от существующих стилей).
Tech Stack: D3.js v7 (CDN), SVG, vanilla JS, CSS variables (--paper, --ink, --blueprint, --amber, --rust, --sage из существующего файла).
Spec reference: 2026-05-10-connections-graph-design.md (385 строк, 10 секций).
File Structure
Модифицируется
| File | Что меняется |
|---|---|
docs/visualizations/hooks-skills-plugins-map.html |
Renumber §X→§XI, новая §X секция с graph (~260 строк добавки в существующие 2240) |
Не создаётся
Никаких новых файлов — всё inline в существующий HTML.
Tasks
Task 1: Renumber existing §X (Practical Actions) → §XI
Files:
-
Modify:
docs/visualizations/hooks-skills-plugins-map.html(1 строка изменена) -
Step 1: Edit section-num value X → XI
Use Edit tool. Найти:
<div class="section-num display-i">X</div>
<h2 class="section-title">Что вы можете сделать</h2>
Заменить на:
<div class="section-num display-i">XI</div>
<h2 class="section-title">Что вы можете сделать</h2>
- Step 2: Verify единственный X→XI выполнен
grep -nE '<div class="section-num display-i">(X|XI)<' \
"c:/моя/проекты/портал crm/Документация/docs/visualizations/hooks-skills-plugins-map.html"
Expected: одна строка с XI (другие — I-IX, плюс будем добавлять X в Task 3).
Task 2: Add D3 CDN script + graph CSS
Files:
-
Modify:
docs/visualizations/hooks-skills-plugins-map.html—<head>(D3 script) +<style>блок (CSS) -
Step 1: Add D3 CDN script tag после Google Fonts link
Use Edit tool. Найти:
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,200..900,0..100,0..1;1,9..144,200..900,0..100,0..1&family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap" rel="stylesheet">
<style>
Заменить на:
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,200..900,0..100,0..1;1,9..144,200..900,0..100,0..1&family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js" defer></script>
<style>
- Step 2: Add graph CSS перед PAGE LOAD ANIMATION block
Use Edit tool. Найти:
/* ============================================================
PAGE LOAD ANIMATION
============================================================ */
@keyframes rise {
Заменить на:
/* ============================================================
CONNECTIONS GRAPH (§X)
============================================================ */
.graph-controls {
margin-top: 32px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
flex-wrap: wrap;
padding: 16px 0;
border-top: 1px solid var(--rule);
border-bottom: 1px solid var(--rule);
}
.graph-filters {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.graph-filter {
font-family: 'JetBrains Mono', monospace;
font-size: 0.72rem;
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 6px 12px;
border: 1px solid var(--rule);
background: var(--paper);
color: var(--ink);
cursor: pointer;
transition: background 200ms, color 200ms;
}
.graph-filter.active {
background: var(--ink);
color: var(--paper);
border-color: var(--ink);
}
.graph-filter:hover { background: var(--paper-shade); }
.graph-filter.active:hover { background: var(--ink-soft); }
.graph-reset {
font-family: 'JetBrains Mono', monospace;
font-size: 0.72rem;
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 6px 14px;
border: 1px solid var(--rust);
background: var(--paper);
color: var(--rust);
cursor: pointer;
}
.graph-reset:hover { background: var(--rust); color: var(--paper); }
.graph-container {
margin-top: 24px;
display: grid;
grid-template-columns: 1fr 280px;
gap: 24px;
align-items: start;
}
.graph-svg-wrap {
border: 1px solid var(--rule);
background: var(--paper-shade);
background-image:
linear-gradient(rgba(14, 34, 53, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(14, 34, 53, 0.03) 1px, transparent 1px);
background-size: 24px 24px;
overflow: hidden;
min-height: 700px;
}
.graph-svg {
width: 100%;
height: 700px;
display: block;
cursor: default;
}
.graph-sidebar {
border: 1px solid var(--rule);
background: var(--paper);
padding: 20px 18px;
font-size: 0.88rem;
min-height: 200px;
position: relative;
}
.graph-sidebar[hidden] { display: none; }
.graph-sidebar-empty {
color: var(--ink-fade);
font-style: italic;
font-family: 'Fraunces', serif;
}
.graph-sidebar-title {
font-family: 'Fraunces', serif;
font-weight: 500;
font-size: 1.15rem;
margin-bottom: 4px;
line-height: 1.15;
}
.graph-sidebar-badge {
display: inline-block;
font-family: 'JetBrains Mono', monospace;
font-size: 0.65rem;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 2px 8px;
background: var(--ink);
color: var(--paper);
margin-bottom: 12px;
}
.graph-sidebar-section {
margin-top: 16px;
padding-top: 12px;
border-top: 1px dashed var(--rule);
}
.graph-sidebar-section-label {
font-family: 'JetBrains Mono', monospace;
font-size: 0.68rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--ink-fade);
margin-bottom: 8px;
}
.graph-conn-list {
list-style: none;
font-family: 'JetBrains Mono', monospace;
font-size: 0.78rem;
line-height: 1.7;
color: var(--ink-soft);
}
.graph-conn-list li b { color: var(--rust); font-weight: 600; }
.graph-close {
position: absolute;
top: 12px;
right: 14px;
border: none;
background: transparent;
font-family: 'JetBrains Mono', monospace;
font-size: 1.2rem;
cursor: pointer;
color: var(--ink-fade);
}
.graph-close:hover { color: var(--rust); }
.graph-legend {
margin-top: 24px;
padding: 20px 24px;
background: var(--paper);
border: 1px solid var(--rule);
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.graph-legend-section h4 {
font-family: 'JetBrains Mono', monospace;
font-size: 0.68rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--ink-fade);
margin-bottom: 12px;
font-weight: 500;
}
.graph-legend-item {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
font-size: 0.82rem;
color: var(--ink-soft);
}
.graph-legend-swatch {
width: 16px;
height: 16px;
border: 1.5px solid var(--ink);
flex-shrink: 0;
}
.graph-legend-line {
width: 28px;
height: 0;
border-top: 2px solid var(--ink);
flex-shrink: 0;
}
.graph-fallback {
padding: 48px;
text-align: center;
color: var(--rust);
font-family: 'Fraunces', serif;
font-style: italic;
font-size: 1rem;
}
/* SVG node styles */
.gn-plugin { fill: var(--rust); stroke: var(--ink); stroke-width: 2; }
.gn-skill { fill: var(--blueprint); stroke: var(--ink); stroke-width: 1.5; }
.gn-script { fill: var(--amber); stroke: var(--ink); stroke-width: 1.5; }
.gn-event { fill: var(--sage); stroke: var(--ink); stroke-width: 2; }
.gn-state { fill: var(--rust); stroke: var(--ink); stroke-width: 2; }
.gn-perm { fill: var(--ink-fade); stroke: var(--ink); stroke-width: 1.5; }
.gn-rule { fill: var(--ink); stroke: var(--rust); stroke-width: 2; }
.gn-label {
font-family: 'Fraunces', serif;
font-size: 9px;
font-weight: 500;
fill: var(--ink);
pointer-events: none;
text-anchor: middle;
}
.gn-label-bg {
fill: var(--paper);
opacity: 0.92;
pointer-events: none;
}
.gn-node { cursor: pointer; }
.gn-node:hover { filter: brightness(1.1); }
.dimmed { opacity: 0.15; }
.highlighted { opacity: 1; }
/* SVG link styles */
.gl { fill: none; pointer-events: stroke; }
.gl-contains { stroke: var(--blueprint); stroke-width: 1.5; }
.gl-triggers { stroke: var(--sage); stroke-width: 2; }
.gl-writes { stroke: var(--amber); stroke-width: 2; }
.gl-reads { stroke: var(--amber); stroke-width: 1.5; stroke-dasharray: 4 3; }
.gl-mandates { stroke: var(--rust); stroke-width: 2.5; }
.gl-references { stroke: var(--ink-fade); stroke-width: 1; stroke-dasharray: 2 3; }
.gl-blocks { stroke: var(--rust); stroke-width: 1.5; stroke-dasharray: 5 3; }
.gl-denies { stroke: var(--rust); stroke-width: 2; stroke-dasharray: 2 2; }
.gl-tooltip {
position: absolute;
background: var(--ink);
color: var(--paper);
font-family: 'JetBrains Mono', monospace;
font-size: 0.72rem;
padding: 4px 8px;
pointer-events: none;
z-index: 100;
border: 1px solid var(--paper-shade);
}
@media (max-width: 900px) {
.graph-container { grid-template-columns: 1fr; }
.graph-svg-wrap { overflow-x: auto; }
.graph-svg { min-width: 900px; }
.graph-sidebar {
position: fixed;
bottom: 0; left: 0; right: 0;
border-top: 2px solid var(--ink);
border-left: none; border-right: none; border-bottom: none;
z-index: 50;
max-height: 60vh;
overflow-y: auto;
}
.graph-legend { grid-template-columns: 1fr; }
}
/* ============================================================
PAGE LOAD ANIMATION
============================================================ */
@keyframes rise {
- Step 3: Verify CSS добавился
grep -c "graph-controls\|graph-svg\|gn-plugin\|gl-contains" \
"c:/моя/проекты/портал crm/Документация/docs/visualizations/hooks-skills-plugins-map.html"
Expected: число > 0 (минимум 4 уникальных селектора найдены).
- Step 4: Verify D3 CDN tag добавлен
grep -c "d3@7.9.0" "c:/моя/проекты/портал crm/Документация/docs/visualizations/hooks-skills-plugins-map.html"
Expected: 1.
Task 3: Insert new §X section HTML structure
Files:
-
Modify:
docs/visualizations/hooks-skills-plugins-map.html— insert section before §XI -
Step 1: Insert new section перед §XI
Use Edit tool. Найти:
<!-- ============================================================
IX. ACTIONS
============================================================ -->
<section class="section">
<div class="frame">
<div class="section-num display-i">XI</div>
<h2 class="section-title">Что вы можете сделать</h2>
Заменить на:
<!-- ============================================================
X. CONNECTIONS GRAPH
============================================================ -->
<section class="section" id="graph-section">
<div class="frame">
<div class="section-num display-i">X</div>
<h2 class="section-title">Связи — interactive map</h2>
<p class="section-lede">
50 узлов, 52 ребра, 8 типов связей. Кликни на любой узел —
подсветятся все его связи + откроется панель с деталями справа.
Тяни узлы мышкой для перестановки. Фильтры в верхней панели прячут
категории по одной.
</p>
<div class="graph-controls">
<div class="graph-filters">
<button class="graph-filter active" data-type="plugin">Plugins</button>
<button class="graph-filter active" data-type="skill">Skills</button>
<button class="graph-filter active" data-type="script">Scripts</button>
<button class="graph-filter active" data-type="event">Events</button>
<button class="graph-filter active" data-type="state">State</button>
<button class="graph-filter active" data-type="perm">Perms</button>
<button class="graph-filter active" data-type="rule">Rules</button>
</div>
<button class="graph-reset">Reset layout</button>
</div>
<div class="graph-container">
<div class="graph-svg-wrap">
<svg class="graph-svg" viewBox="0 0 1100 700" preserveAspectRatio="xMidYMid meet"></svg>
</div>
<aside class="graph-sidebar">
<div class="graph-sidebar-empty">
Кликни на узел чтобы увидеть его связи…
</div>
</aside>
</div>
<div class="graph-legend">
<div class="graph-legend-section">
<h4>Узлы (категории)</h4>
<div class="graph-legend-item"><span class="graph-legend-swatch" style="background:var(--rust); border-radius:50%;"></span>Plugin (4)</div>
<div class="graph-legend-item"><span class="graph-legend-swatch" style="background:var(--blueprint); border-radius:50%;"></span>Skill (28)</div>
<div class="graph-legend-item"><span class="graph-legend-swatch" style="background:var(--amber); border-radius:50%;"></span>Hook script (7)</div>
<div class="graph-legend-item"><span class="graph-legend-swatch" style="background:var(--sage);"></span>Hook event (5)</div>
<div class="graph-legend-item"><span class="graph-legend-swatch" style="background:var(--rust); transform:rotate(45deg);"></span>State file (1)</div>
<div class="graph-legend-item"><span class="graph-legend-swatch" style="background:var(--ink-fade);"></span>Permission (3)</div>
<div class="graph-legend-item"><span class="graph-legend-swatch" style="background:var(--ink); border-color:var(--rust);"></span>Rule (2)</div>
</div>
<div class="graph-legend-section">
<h4>Связи (типы)</h4>
<div class="graph-legend-item"><span class="graph-legend-line" style="border-color:var(--blueprint);"></span>contains</div>
<div class="graph-legend-item"><span class="graph-legend-line" style="border-color:var(--sage); border-width:2px;"></span>triggers</div>
<div class="graph-legend-item"><span class="graph-legend-line" style="border-color:var(--amber); border-width:2px;"></span>writes</div>
<div class="graph-legend-item"><span class="graph-legend-line" style="border-color:var(--amber); border-style:dashed;"></span>reads</div>
<div class="graph-legend-item"><span class="graph-legend-line" style="border-color:var(--rust); border-width:3px;"></span>mandates</div>
<div class="graph-legend-item"><span class="graph-legend-line" style="border-color:var(--ink-fade); border-style:dotted;"></span>references</div>
<div class="graph-legend-item"><span class="graph-legend-line" style="border-color:var(--rust); border-style:dashed;"></span>blocks</div>
<div class="graph-legend-item"><span class="graph-legend-line" style="border-color:var(--rust); border-style:dashed; border-width:2px;"></span>denies</div>
</div>
</div>
</div>
</section>
<!-- ============================================================
XI. ACTIONS
============================================================ -->
<section class="section">
<div class="frame">
<div class="section-num display-i">XI</div>
<h2 class="section-title">Что вы можете сделать</h2>
- Step 2: Verify HTML structure
grep -c '<section' "c:/моя/проекты/портал crm/Документация/docs/visualizations/hooks-skills-plugins-map.html"
Expected: 11 (было 10, добавили 1).
- Step 3: Verify graph elements
grep -cE 'class="graph-(svg|sidebar|filter|legend|reset)' "c:/моя/проекты/портал crm/Документация/docs/visualizations/hooks-skills-plugins-map.html"
Expected: > 10.
Task 4: Inline data — nodes (50)
Files:
-
Modify:
docs/visualizations/hooks-skills-plugins-map.html— append<script>block перед</body> -
Step 1: Add nodes data script перед
Use Edit tool. Найти (последние строки файла):
</main>
</body>
</html>
Заменить на:
</main>
<script>
// ============================================================
// CONNECTIONS GRAPH DATA
// ============================================================
const GRAPH_NODES = [
// === Plugins (4) ===
{ id: 'plg:superpowers', type: 'plugin', label: 'superpowers', desc: 'Главный плагин дисциплины процесса работы. 14 скилов.' },
{ id: 'plg:claude-md', type: 'plugin', label: 'claude-md-management', desc: 'Единственный канал правок CLAUDE.md. 2 скила.' },
{ id: 'plg:fd', type: 'plugin', label: 'frontend-design', desc: 'Создание distinctive frontend. Anthropic plugin.' },
{ id: 'plg:upm', type: 'plugin', label: 'ui-ux-pro-max', desc: 'Резерв-библиотека UI/UX (50+ стилей, 161 палитра).' },
// === Skills (28) ===
// Superpowers (14)
{ id: 'skl:brainstorming', type: 'skill', label: 'brainstorming', desc: 'Превращает идею в спек через диалог.' },
{ id: 'skl:writing-plans', type: 'skill', label: 'writing-plans', desc: 'Из спека → пошаговый план.' },
{ id: 'skl:executing-plans', type: 'skill', label: 'executing-plans', desc: 'Исполняет план по шагам.' },
{ id: 'skl:tdd', type: 'skill', label: 'test-driven-development', desc: 'Тест ДО кода. Red-green-refactor.' },
{ id: 'skl:debug', type: 'skill', label: 'systematic-debugging', desc: '4 фазы root cause. ≥3 гипотезы.' },
{ id: 'skl:req-review', type: 'skill', label: 'requesting-code-review', desc: 'Перед merge — двухстадийный review.' },
{ id: 'skl:recv-review', type: 'skill', label: 'receiving-code-review', desc: 'Обработка feedback ревью.' },
{ id: 'skl:verify', type: 'skill', label: 'verification-before-completion', desc: 'Перед claim готово — verify.' },
{ id: 'skl:finishing', type: 'skill', label: 'finishing-a-development-branch', desc: 'merge / PR / cleanup.' },
{ id: 'skl:worktrees', type: 'skill', label: 'using-git-worktrees', desc: 'Изоляция feature-работы.' },
{ id: 'skl:subagent', type: 'skill', label: 'subagent-driven-development', desc: 'Свежий subagent на каждую задачу.' },
{ id: 'skl:parallel', type: 'skill', label: 'dispatching-parallel-agents', desc: 'Параллельные subagent\'ы.' },
{ id: 'skl:using-sp', type: 'skill', label: 'using-superpowers', desc: 'Базовый: как находить skills.' },
{ id: 'skl:writing-skills', type: 'skill', label: 'writing-skills', desc: 'Создание новых skills.' },
// claude-md-management (2)
{ id: 'skl:md-improver', type: 'skill', label: 'claude-md-improver', desc: 'Audit + targeted updates CLAUDE.md.' },
{ id: 'skl:md-revise', type: 'skill', label: 'revise-claude-md', desc: 'Захват session-learnings.' },
// frontend-design (1)
{ id: 'skl:fd-skill', type: 'skill', label: 'frontend-design', desc: 'Distinctive UI без AI-aesthetics.' },
// ui-ux-pro-max (1)
{ id: 'skl:upm-skill', type: 'skill', label: 'ui-ux-pro-max', desc: 'Резерв-библиотека стилей.' },
// Standalone (10)
{ id: 'skl:update-config', type: 'skill', label: 'update-config', desc: 'Правки settings.json.' },
{ id: 'skl:keybindings', type: 'skill', label: 'keybindings-help', desc: 'Клавиатурные сокращения.' },
{ id: 'skl:simplify', type: 'skill', label: 'simplify', desc: 'Review кода на reuse/quality.' },
{ id: 'skl:fewer-prompts', type: 'skill', label: 'fewer-permission-prompts', desc: 'Снижение шума prompts.' },
{ id: 'skl:init', type: 'skill', label: 'init', desc: 'Новый CLAUDE.md для нового проекта.' },
{ id: 'skl:claude-api', type: 'skill', label: 'claude-api', desc: 'Claude API / SDK apps.' },
{ id: 'skl:loop', type: 'skill', label: 'loop', desc: 'Recurring prompt на интервале.' },
{ id: 'skl:schedule', type: 'skill', label: 'schedule', desc: 'Cron-расписания для агентов.' },
{ id: 'skl:review', type: 'skill', label: 'review', desc: 'Review текущего PR.' },
{ id: 'skl:sec-review', type: 'skill', label: 'security-review', desc: 'Security audit pending changes.' },
// === Hook scripts (7) ===
{ id: 'scr:skill-marker', type: 'script', label: 'skill-marker.py', desc: 'Отметка о вызове Skill.' },
{ id: 'scr:skill-check', type: 'script', label: 'skill-check.py', desc: 'Reminder §12 если skill не вызван.' },
{ id: 'scr:economy-mode', type: 'script', label: 'economy-mode.py', desc: 'Парсит экономию N%, пишет state.' },
{ id: 'scr:economy-self-check', type: 'script', label: 'economy-self-check.py', desc: 'SessionStart runtime guard.' },
{ id: 'scr:economy-state-guard', type: 'script', label: 'economy-state-guard.py', desc: 'PreToolUse reminder + Bash bypass.' },
{ id: 'scr:economy-verifier', type: 'script', label: 'economy-verifier.py (Sonnet 4.6)', desc: 'Stop verifier — блокирует cherry-pick.' },
{ id: 'scr:economy-postcompact', type: 'script', label: 'economy-postcompact.py', desc: 'Re-inject правил после компакции.' },
// === Hook events (5) ===
{ id: 'evt:session-start', type: 'event', label: 'SessionStart', desc: 'Один раз на старте сессии.' },
{ id: 'evt:user-prompt-submit', type: 'event', label: 'UserPromptSubmit', desc: 'Каждый submit от пользователя.' },
{ id: 'evt:pre-tool-use', type: 'event', label: 'PreToolUse', desc: 'Перед каждым tool call.' },
{ id: 'evt:post-compact', type: 'event', label: 'PostCompact', desc: 'После авто-компакции.' },
{ id: 'evt:stop', type: 'event', label: 'Stop', desc: 'Конец моего ответа.' },
// === State file (1) ===
{ id: 'st:economy-state', type: 'state', label: 'claude-economy-state.json', desc: '$TEMP/claude-economy-<session_id>.json — shared state.' },
// === Permissions (3) ===
{ id: 'prm:allow', type: 'perm', label: 'permissions.allow (1)', desc: 'Разрешено без вопросов: Bash(git push origin main:*).' },
{ id: 'prm:deny', type: 'perm', label: 'permissions.deny (7)', desc: 'Жёстко заблокировано: rm/mv hook/settings/state.' },
{ id: 'prm:ask', type: 'perm', label: 'permissions.ask (16)', desc: 'С approve пользователя: Edit/Write hook files и settings.json.' },
// === Rules (2) ===
{ id: 'rul:pravila-12', type: 'rule', label: 'Pravila §12', desc: 'Hard rule: Superpowers skill ПЕРВЫМ.' },
{ id: 'rul:claude-md', type: 'rule', label: 'CLAUDE.md', desc: 'Главная карта проекта.' }
];
console.log('GRAPH_NODES:', GRAPH_NODES.length, '(expected 50)');
</script>
</body>
</html>
- Step 2: Verify nodes count
Open the file в браузере, посмотри Console (F12 → Console tab). Expected log: GRAPH_NODES: 50 (expected 50).
Или offline:
grep -c "^ { id:" "c:/моя/проекты/портал crm/Документация/docs/visualizations/hooks-skills-plugins-map.html"
Expected: 50.
Task 5: Inline data — links (52)
Files:
-
Modify:
docs/visualizations/hooks-skills-plugins-map.html— добавить GRAPH_LINKS после GRAPH_NODES -
Step 1: Add links data
Use Edit tool. Найти:
console.log('GRAPH_NODES:', GRAPH_NODES.length, '(expected 50)');
</script>
Заменить на:
console.log('GRAPH_NODES:', GRAPH_NODES.length, '(expected 50)');
const GRAPH_LINKS = [
// === contains (18) — plugin → skill ===
// superpowers (14)
{ source: 'plg:superpowers', target: 'skl:brainstorming', type: 'contains' },
{ source: 'plg:superpowers', target: 'skl:writing-plans', type: 'contains' },
{ source: 'plg:superpowers', target: 'skl:executing-plans', type: 'contains' },
{ source: 'plg:superpowers', target: 'skl:tdd', type: 'contains' },
{ source: 'plg:superpowers', target: 'skl:debug', type: 'contains' },
{ source: 'plg:superpowers', target: 'skl:req-review', type: 'contains' },
{ source: 'plg:superpowers', target: 'skl:recv-review', type: 'contains' },
{ source: 'plg:superpowers', target: 'skl:verify', type: 'contains' },
{ source: 'plg:superpowers', target: 'skl:finishing', type: 'contains' },
{ source: 'plg:superpowers', target: 'skl:worktrees', type: 'contains' },
{ source: 'plg:superpowers', target: 'skl:subagent', type: 'contains' },
{ source: 'plg:superpowers', target: 'skl:parallel', type: 'contains' },
{ source: 'plg:superpowers', target: 'skl:using-sp', type: 'contains' },
{ source: 'plg:superpowers', target: 'skl:writing-skills', type: 'contains' },
// claude-md (2)
{ source: 'plg:claude-md', target: 'skl:md-improver', type: 'contains' },
{ source: 'plg:claude-md', target: 'skl:md-revise', type: 'contains' },
// fd (1)
{ source: 'plg:fd', target: 'skl:fd-skill', type: 'contains' },
// upm (1)
{ source: 'plg:upm', target: 'skl:upm-skill', type: 'contains' },
// === triggers (7) — event → script ===
{ source: 'evt:session-start', target: 'scr:economy-self-check', type: 'triggers' },
{ source: 'evt:user-prompt-submit', target: 'scr:economy-mode', type: 'triggers' },
{ source: 'evt:pre-tool-use', target: 'scr:skill-marker', type: 'triggers' },
{ source: 'evt:pre-tool-use', target: 'scr:skill-check', type: 'triggers' },
{ source: 'evt:pre-tool-use', target: 'scr:economy-state-guard', type: 'triggers' },
{ source: 'evt:post-compact', target: 'scr:economy-postcompact', type: 'triggers' },
{ source: 'evt:stop', target: 'scr:economy-verifier', type: 'triggers' },
// === writes (1) — script → state ===
{ source: 'scr:economy-mode', target: 'st:economy-state', type: 'writes' },
// === reads (3) — script ← state (модель: source reads target) ===
{ source: 'scr:economy-state-guard', target: 'st:economy-state', type: 'reads' },
{ source: 'scr:economy-verifier', target: 'st:economy-state', type: 'reads' },
{ source: 'scr:economy-postcompact', target: 'st:economy-state', type: 'reads' },
// === mandates (14) — Pravila §12 → 14 superpowers skills ===
{ source: 'rul:pravila-12', target: 'skl:brainstorming', type: 'mandates' },
{ source: 'rul:pravila-12', target: 'skl:writing-plans', type: 'mandates' },
{ source: 'rul:pravila-12', target: 'skl:executing-plans', type: 'mandates' },
{ source: 'rul:pravila-12', target: 'skl:tdd', type: 'mandates' },
{ source: 'rul:pravila-12', target: 'skl:debug', type: 'mandates' },
{ source: 'rul:pravila-12', target: 'skl:req-review', type: 'mandates' },
{ source: 'rul:pravila-12', target: 'skl:recv-review', type: 'mandates' },
{ source: 'rul:pravila-12', target: 'skl:verify', type: 'mandates' },
{ source: 'rul:pravila-12', target: 'skl:finishing', type: 'mandates' },
{ source: 'rul:pravila-12', target: 'skl:worktrees', type: 'mandates' },
{ source: 'rul:pravila-12', target: 'skl:subagent', type: 'mandates' },
{ source: 'rul:pravila-12', target: 'skl:parallel', type: 'mandates' },
{ source: 'rul:pravila-12', target: 'skl:using-sp', type: 'mandates' },
{ source: 'rul:pravila-12', target: 'skl:writing-skills', type: 'mandates' },
// === references (1) — CLAUDE.md → Pravila §12 ===
{ source: 'rul:claude-md', target: 'rul:pravila-12', type: 'references' },
// === blocks (7) — permissions.ask → hook scripts ===
{ source: 'prm:ask', target: 'scr:skill-marker', type: 'blocks' },
{ source: 'prm:ask', target: 'scr:skill-check', type: 'blocks' },
{ source: 'prm:ask', target: 'scr:economy-mode', type: 'blocks' },
{ source: 'prm:ask', target: 'scr:economy-self-check', type: 'blocks' },
{ source: 'prm:ask', target: 'scr:economy-state-guard', type: 'blocks' },
{ source: 'prm:ask', target: 'scr:economy-verifier', type: 'blocks' },
{ source: 'prm:ask', target: 'scr:economy-postcompact', type: 'blocks' },
// === denies (1) — permissions.deny → state file ===
{ source: 'prm:deny', target: 'st:economy-state', type: 'denies' }
];
console.log('GRAPH_LINKS:', GRAPH_LINKS.length, '(expected 52)');
</script>
- Step 2: Verify в браузере
Open в Chrome (или другом), F12 → Console. Expected log:
GRAPH_NODES: 50 (expected 50)
GRAPH_LINKS: 52 (expected 52)
Task 6: D3 force simulation + initial rendering
Files:
-
Modify:
docs/visualizations/hooks-skills-plugins-map.html— добавить simulation код в существующий<script>блок -
Step 1: Add D3 simulation и render code
Use Edit tool. Найти:
console.log('GRAPH_LINKS:', GRAPH_LINKS.length, '(expected 52)');
</script>
Заменить на:
console.log('GRAPH_LINKS:', GRAPH_LINKS.length, '(expected 52)');
// ============================================================
// D3 SIMULATION + RENDER
// ============================================================
window.addEventListener('DOMContentLoaded', () => {
if (typeof d3 === 'undefined') {
document.querySelector('.graph-svg-wrap').innerHTML =
'<div class="graph-fallback">⚠ D3.js не загружен (нужен интернет для CDN). Остальная страница работает offline.</div>';
return;
}
const W = 1100, H = 700;
const svg = d3.select('.graph-svg');
// Y-position по типу для clustering
const Y_BY_TYPE = {
'rule': 80,
'plugin': 200,
'skill': 340,
'event': 540,
'script': 460,
'state': 380,
'perm': 600
};
// Distance по типу связи
const LINK_DISTANCE = {
'contains': 60,
'triggers': 50,
'writes': 40,
'reads': 40,
'mandates': 100,
'references': 80,
'blocks': 70,
'denies': 50
};
// Size по типу узла
const NODE_SIZE = {
'plugin': 22, 'skill': 10, 'script': 14, 'event': 14,
'state': 16, 'perm': 16, 'rule': 18
};
// Build simulation
const sim = d3.forceSimulation(GRAPH_NODES)
.force('link', d3.forceLink(GRAPH_LINKS).id(d => d.id)
.distance(d => LINK_DISTANCE[d.type] || 50)
.strength(0.5))
.force('charge', d3.forceManyBody().strength(-280))
.force('center', d3.forceCenter(W / 2, H / 2))
.force('y', d3.forceY(d => Y_BY_TYPE[d.type] || H/2).strength(0.18))
.force('collide', d3.forceCollide().radius(d => (NODE_SIZE[d.type] || 10) + 6));
// Render links
const linkSel = svg.append('g').attr('class', 'links')
.selectAll('line')
.data(GRAPH_LINKS)
.join('line')
.attr('class', d => `gl gl-${d.type}`);
// Render nodes как <g>
const nodeSel = svg.append('g').attr('class', 'nodes')
.selectAll('g.gn-node')
.data(GRAPH_NODES)
.join('g')
.attr('class', d => `gn-node gn-node-${d.type}`);
// Shape по type: circle / rect / diamond / hexagon
nodeSel.each(function(d) {
const g = d3.select(this);
const size = NODE_SIZE[d.type];
if (d.type === 'plugin' || d.type === 'skill' || d.type === 'script') {
g.append('circle').attr('r', size).attr('class', `gn-${d.type}`);
} else if (d.type === 'event') {
g.append('rect').attr('x', -size).attr('y', -size)
.attr('width', size*2).attr('height', size*2).attr('class', 'gn-event');
} else if (d.type === 'state') {
g.append('polygon')
.attr('points', `0,-${size} ${size},0 0,${size} -${size},0`)
.attr('class', 'gn-state');
} else if (d.type === 'perm') {
const s = size;
g.append('polygon')
.attr('points', `-${s},-${s/2} 0,-${s} ${s},-${s/2} ${s},${s/2} 0,${s} -${s},${s/2}`)
.attr('class', 'gn-perm');
} else if (d.type === 'rule') {
g.append('rect').attr('x', -size).attr('y', -size)
.attr('width', size*2).attr('height', size*2).attr('class', 'gn-rule');
}
});
// Labels — text с background rect
nodeSel.append('text')
.attr('class', 'gn-label')
.attr('dy', d => (NODE_SIZE[d.type] || 10) + 14)
.text(d => d.label);
// Tick — обновление позиций
sim.on('tick', () => {
linkSel
.attr('x1', d => d.source.x).attr('y1', d => d.source.y)
.attr('x2', d => d.target.x).attr('y2', d => d.target.y);
nodeSel.attr('transform', d => `translate(${d.x},${d.y})`);
});
});
</script>
- Step 2: Open в браузере и verify
Open file. Should see граф с узлами разных типов, цветов, форм. Labels под узлами. Линии соединяют связанные узлы. Через 2-3 секунды simulation стабилизируется.
В Console F12: видны логи NODES/LINKS. Никаких ошибок.
Expected (визуально):
- 50 узлов рендерятся (можно посчитать через
document.querySelectorAll('.gn-node').lengthв Console) - 52 ребра рендерятся (
document.querySelectorAll('.gl').length) - Узлы группируются по Y-категории (rules сверху, plugins выше, perms внизу)
Task 7: Drag behavior
Files:
-
Modify:
docs/visualizations/hooks-skills-plugins-map.html— добавить drag behavior после tick -
Step 1: Add drag
Use Edit tool. Найти:
// Tick — обновление позиций
sim.on('tick', () => {
Заменить на:
// Drag behavior
function dragstart(event, d) {
if (!event.active) sim.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragmove(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragend(event, d) {
if (!event.active) sim.alphaTarget(0);
d.fx = null;
d.fy = null;
}
nodeSel.call(d3.drag()
.on('start', dragstart)
.on('drag', dragmove)
.on('end', dragend));
// Tick — обновление позиций
sim.on('tick', () => {
- Step 2: Verify drag в браузере
Открой файл, попробуй перетащить любой узел мышкой. Узел должен следовать за курсором, остальные узлы реагируют forces.
После отпускания — узел возвращается под действие forces, не фиксируется.
Task 8: Click → highlight + sidebar populate
Files:
-
Modify:
docs/visualizations/hooks-skills-plugins-map.html— добавить click handler перед drag setup -
Step 1: Add click highlight + sidebar populate
Use Edit tool. Найти:
// Drag behavior
function dragstart(event, d) {
Заменить на:
// Click → highlight + sidebar
const sidebar = document.querySelector('.graph-sidebar');
let activeId = null;
function showSidebar(node) {
const outgoing = GRAPH_LINKS.filter(l =>
(typeof l.source === 'object' ? l.source.id : l.source) === node.id);
const incoming = GRAPH_LINKS.filter(l =>
(typeof l.target === 'object' ? l.target.id : l.target) === node.id);
const out = outgoing.map(l => {
const tgtId = typeof l.target === 'object' ? l.target.id : l.target;
const tgt = GRAPH_NODES.find(n => n.id === tgtId);
return `<li><b>${l.type}</b> → ${tgt ? tgt.label : tgtId}</li>`;
}).join('');
const inc = incoming.map(l => {
const srcId = typeof l.source === 'object' ? l.source.id : l.source;
const src = GRAPH_NODES.find(n => n.id === srcId);
return `<li>${src ? src.label : srcId} <b>← ${l.type}</b></li>`;
}).join('');
sidebar.innerHTML = `
<button class="graph-close" aria-label="Close">×</button>
<div class="graph-sidebar-title">${node.label}</div>
<div class="graph-sidebar-badge">${node.type}</div>
<div style="font-size:0.85rem; color: var(--ink-soft); line-height: 1.5;">${node.desc}</div>
${outgoing.length ? `
<div class="graph-sidebar-section">
<div class="graph-sidebar-section-label">Исходящие (${outgoing.length})</div>
<ul class="graph-conn-list">${out}</ul>
</div>` : ''}
${incoming.length ? `
<div class="graph-sidebar-section">
<div class="graph-sidebar-section-label">Входящие (${incoming.length})</div>
<ul class="graph-conn-list">${inc}</ul>
</div>` : ''}
`;
sidebar.querySelector('.graph-close').addEventListener('click', clearHighlight);
}
function highlightNode(node) {
activeId = node.id;
const relatedIds = new Set([node.id]);
GRAPH_LINKS.forEach(l => {
const sId = typeof l.source === 'object' ? l.source.id : l.source;
const tId = typeof l.target === 'object' ? l.target.id : l.target;
if (sId === node.id) relatedIds.add(tId);
if (tId === node.id) relatedIds.add(sId);
});
nodeSel.classed('dimmed', d => !relatedIds.has(d.id));
linkSel.classed('dimmed', l => {
const sId = typeof l.source === 'object' ? l.source.id : l.source;
const tId = typeof l.target === 'object' ? l.target.id : l.target;
return sId !== node.id && tId !== node.id;
});
showSidebar(node);
}
function clearHighlight() {
activeId = null;
nodeSel.classed('dimmed', false);
linkSel.classed('dimmed', false);
sidebar.innerHTML = '<div class="graph-sidebar-empty">Кликни на узел чтобы увидеть его связи…</div>';
}
nodeSel.on('click', (event, d) => {
event.stopPropagation();
if (activeId === d.id) clearHighlight();
else highlightNode(d);
});
svg.on('click', clearHighlight);
// Drag behavior
function dragstart(event, d) {
- Step 2: Verify click highlight в браузере
Refresh страницу. Кликни на любой plugin узел (e.g., superpowers). Expected:
- Plugin узел остаётся ярким
- Все его связанные skills (14 для superpowers) — ярки
- Остальные узлы и линии — затемнены до opacity 0.15
- Sidebar справа показывает: имя плагина, описание, секцию «Исходящие» с 14 пунктами вида
contains → brainstormingetc.
Click на пустое место SVG → подсветка сбрасывается, sidebar возвращается в пустое состояние.
Task 9: Hover edge tooltip
Files:
-
Modify:
docs/visualizations/hooks-skills-plugins-map.html— добавить tooltip элемент + handlers -
Step 1: Add tooltip
Use Edit tool. Найти:
svg.on('click', clearHighlight);
Заменить на:
svg.on('click', clearHighlight);
// Hover edge → tooltip
const tooltip = d3.select(document.body)
.append('div').attr('class', 'gl-tooltip').style('display', 'none');
linkSel.on('mouseover', function(event, l) {
const sId = typeof l.source === 'object' ? l.source.id : l.source;
const tId = typeof l.target === 'object' ? l.target.id : l.target;
const src = GRAPH_NODES.find(n => n.id === sId);
const tgt = GRAPH_NODES.find(n => n.id === tId);
tooltip.html(`<b>${l.type}</b>: ${src.label} → ${tgt.label}`)
.style('display', 'block')
.style('left', (event.pageX + 12) + 'px')
.style('top', (event.pageY - 8) + 'px');
d3.select(this).style('stroke-width', '4');
});
linkSel.on('mousemove', function(event) {
tooltip.style('left', (event.pageX + 12) + 'px').style('top', (event.pageY - 8) + 'px');
});
linkSel.on('mouseout', function() {
tooltip.style('display', 'none');
d3.select(this).style('stroke-width', null);
});
- Step 2: Verify hover tooltip
Refresh. Наведи курсор на любую линию между узлами. Expected:
- Появляется чёрный tooltip с типом связи и source → target
- Линия становится толще (4px)
- При отведении курсора — tooltip исчезает, линия возвращается к нормальной толщине
Task 10: Filter chips + reset button
Files:
-
Modify:
docs/visualizations/hooks-skills-plugins-map.html— добавить filter и reset handlers -
Step 1: Add filter и reset
Use Edit tool. Найти:
linkSel.on('mouseout', function() {
tooltip.style('display', 'none');
d3.select(this).style('stroke-width', null);
});
Заменить на:
linkSel.on('mouseout', function() {
tooltip.style('display', 'none');
d3.select(this).style('stroke-width', null);
});
// Filter chips
const filters = document.querySelectorAll('.graph-filter');
const hiddenTypes = new Set();
function applyFilters() {
nodeSel.style('display', d => hiddenTypes.has(d.type) ? 'none' : null);
linkSel.style('display', l => {
const sT = (typeof l.source === 'object' ? l.source.type : GRAPH_NODES.find(n=>n.id===l.source).type);
const tT = (typeof l.target === 'object' ? l.target.type : GRAPH_NODES.find(n=>n.id===l.target).type);
return (hiddenTypes.has(sT) || hiddenTypes.has(tT)) ? 'none' : null;
});
}
filters.forEach(btn => {
btn.addEventListener('click', () => {
const type = btn.dataset.type;
if (hiddenTypes.has(type)) {
hiddenTypes.delete(type);
btn.classList.add('active');
} else {
hiddenTypes.add(type);
btn.classList.remove('active');
}
applyFilters();
});
});
// Reset button
document.querySelector('.graph-reset').addEventListener('click', () => {
hiddenTypes.clear();
filters.forEach(b => b.classList.add('active'));
applyFilters();
clearHighlight();
GRAPH_NODES.forEach(n => { n.fx = null; n.fy = null; });
sim.alpha(0.7).restart();
});
- Step 2: Verify filters и reset
Refresh. Кликни на «Plugins» chip (он станет неактивным — без чёрного фона). Expected:
- 4 plugin узла исчезают
- 18 contains-edges (их incident) тоже исчезают
- Skills остаются как «standalone» узлы без parent
Кликни «Plugins» снова — вернутся.
Кликни «Reset layout» — все filters снова active, simulation перезапускается, узлы reorganize.
Task 11: Final verification (success criteria)
Files:
-
Open:
docs/visualizations/hooks-skills-plugins-map.htmlв браузере -
Step 1: Visual checklist в браузере
Открой файл (двойной клик в Проводнике или cmd //c start "" "..."). Пройди checklist:
-
Page загружается без ошибок (F12 → Console, 0 errors)
-
Console log:
GRAPH_NODES: 50 (expected 50)иGRAPH_LINKS: 52 (expected 52) -
§X «Связи — interactive map» видна между §IX (Mental Model) и §XI (Actions)
-
50 узлов рендерятся (граф наполнен)
-
52 линии между узлами видны
-
Узлы кластеризованы по типу (rules сверху, plugins выше, events внизу)
-
Drag узла работает: можно перетащить, остальные реагируют
-
Click на plugin узел → подсветка skills + sidebar с 14 пунктами «contains →»
-
Click на state-file → подсветка 1 writer + 3 readers + 1 denier (5 connections)
-
Click на Pravila §12 → подсветка 14 superpowers skills + reference на CLAUDE.md
-
Hover на линии → tooltip с типом связи
-
Filter «Skills» off → 28 skill узлов исчезают
-
Filter «Skills» on → возвращаются
-
Reset → все filters active, sim restarts
-
Mobile width <900px (DevTools → Toggle device toolbar): граф horizontally scrolls, sidebar становится bottom-sheet
-
Step 2: Verify HTML structure
PYTHONIOENCODING=utf-8 python -c "
from html.parser import HTMLParser
class P(HTMLParser):
def __init__(self):
super().__init__()
self.opened = []
self.unmatched = 0
def handle_starttag(self, tag, attrs):
if tag not in ('br','img','input','meta','link','hr','source'):
self.opened.append(tag)
def handle_endtag(self, tag):
if self.opened and self.opened[-1] == tag:
self.opened.pop()
else:
self.unmatched += 1
with open(r'c:\моя\проекты\портал crm\Документация\docs\visualizations\hooks-skills-plugins-map.html', encoding='utf-8') as f:
p = P(); p.feed(f.read())
print('unclosed:', len(p.opened), 'orphans:', p.unmatched)
"
Expected: unclosed: 0 orphans: 0.
- Step 3: Verify file size
wc -lc "c:/моя/проекты/портал crm/Документация/docs/visualizations/hooks-skills-plugins-map.html"
Expected: ~2500 строк (было 2240, +260).
Self-Review
Spec coverage
| Spec section | Plan task |
|---|---|
| §1 Goal (interactive force-directed) | Task 6 (D3 simulation), Task 8 (click), Task 7 (drag) |
| §2.1 Scope: D3, ~50 nodes | Tasks 4-6 |
| §2.2 Out of scope (search, export, zoom) | НЕ имплементируется (verified в Task 11) |
| §3.1 Tech: D3 v7 CDN | Task 2 step 1 |
| §3.2 Data: 50 nodes / 52 edges | Tasks 4-5 |
| §3.3 Visual encoding (shapes/colors) | Task 2 step 2 (CSS), Task 6 step 1 (shape rendering) |
| §3.4 Initial positioning | Task 6 step 1 (Y_BY_TYPE clustering) |
| §3.5 Interactivity (drag/click/hover/filter/reset) | Tasks 7-10 |
| §3.6 Sidebar | Task 8 step 1 (showSidebar) |
| §3.7 Category filters | Task 10 step 1 |
| §4.1 HTML structure | Task 3 step 1 |
| §4.2 JS module | Tasks 4-10 (inline scripts) |
| §4.3 CSS additions | Task 2 step 2 |
| §5.1 Manual verification checklist | Task 11 step 1 |
| §5.2 Browser compat | Не имплементируется (Chrome 120+ assumed) |
| §6 Risks: CDN fallback | Task 6 step 1 (typeof d3 === 'undefined') |
| §7 Success criteria | Task 11 |
| §8 Cascading (renumber §X → §XI) | Task 1 |
✅ Все spec sections покрыты.
Placeholder scan
- ✅ Никаких TBD / TODO /
Similar to Task N - ✅ Полный код в каждом step
- ✅ Exact commands с expected output
- ✅ Exact file paths
Type consistency
- ✅
GRAPH_NODESмассив — везде с тем же именем - ✅
GRAPH_LINKSмассив — везде с тем же именем - ✅ Field names согласованы:
id,type,label,descдля nodes;source,target,typeдля links - ✅ CSS class names:
gn-{type}для node shapes,gl-{type}для link styles — consistent - ✅ Function names:
showSidebar,highlightNode,clearHighlight,applyFilters,dragstart/dragmove/dragend— без drift'ов
Execution Handoff
Plan complete and saved to docs/superpowers/plans/2026-05-10-connections-graph.md (~750 строк).
Two execution options:
1. Subagent-Driven (recommended) — диспатчу свежего subagent'a на каждую task, review между ними. Изоляция контекста. Однако: всё работает с одним HTML файлом — нет параллелизма, последовательные tasks. ~11 dispatches.
2. Inline Execution — выполняю tasks в этой сессии через executing-plans skill. Batch с checkpoints. Учитывая, что я уже глубоко в контексте файла (только что писал spec и иерархию), inline может быть эффективнее.
Какой подход выбираешь?