Tasks 1-7 (parallel-safe через dispatching-parallel-agents): T1 resize handle CSS+JS+localStorage, T2-T5 text rewrite groups A/B/C/D (9+21+18+25=73 nodes по Style Guide), T6 CONFLICT_TYPES enum + 2 new ⚫ edges + reclassify 6, T7 EDGE_DETAILS data (74 entries). Tasks 8-10 (sequential): T8 edge legend render + click handler (depends T7), T9 3-color render + sort + footer (depends T6), T10 visual smoke + push. Test strategy для single-file HTML без unit-tests: 3-уровневая verification (Level 1 — Node.js syntax check per Edit, Level 2 — lefthook pre-commit gauntlet per commit, Level 3 — manual visual smoke в Edge browser). Pre-push: gitleaks full-history + lychee. Self-review pass: spec coverage 100%, no placeholders, no type drift. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
48 KiB
Automation Graph iter2 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: добавить в docs/automation-graph.html 4 улучшения: drag-resize правой panel (300-900px + localStorage), simple-language rewrite всех 73 узлов, 3-цветную классификацию 8 конфликтов (🔴/⚫/🟢 + сортировка), edge legend с 7 полями на click по ребру.
Architecture: single-file HTML с inline JS на vis.js 9.1.9. Все правки — в одном файле, поделены на 7 независимых tasks (parallel-safe) + 3 sequential integration tasks. Каждая task = атомарный git commit. Tests-substitute: Node.js new Function(js) syntax check + lefthook pre-commit gauntlet + manual visual smoke в Edge browser.
Tech Stack: vanilla JS (ES2020+), vis.js Network 9.1.9 (CDN), CSS3 (no preprocessor), localStorage API. Tooling: lefthook + gitleaks + markdownlint + cspell + lychee.
Spec: docs/superpowers/specs/2026-05-14-automation-graph-iter2-design.md
Branch: main (HEAD f936944). Атомарные коммиты прямо в main, как в iter1.
File Structure
Modify:
docs/automation-graph.html(single file, 1188 lines → ~1900 lines after iter2)
Optionally Create:
cspell-words.txt(+ word при появлении нового жаргона в rewrite — добавлять inline во время task'а если нужно)
Reference (read-only):
docs/superpowers/specs/2026-05-14-automation-graph-iter2-design.md(spec — source of truth для всех решений)memory/project_automation_map.md(контекст артефакта)
Test Strategy (TDD-substitute для single-file HTML)
Карта не имеет unit-tests (Vitest setup отсутствует на docs/). TDD заменяется 3-уровневой проверкой:
Level 1 — JS syntax check (per Edit, mandatory)
После каждого Edit на automation-graph.html запускать:
node -e "const fs=require('fs');const html=fs.readFileSync('docs/automation-graph.html','utf8');const m=html.match(/<script>([\s\S]+?)<\/script>/);if(!m)throw new Error('No <script> block');new Function(m[1]);console.log('SYNTAX OK ('+m[1].length+' chars)');"
Expected: SYNTAX OK (N chars) — N растёт с каждой task.
Если SyntaxError → откатить Edit, проверить кавычки/скобки, повторить.
Level 2 — lefthook pre-commit gauntlet (per commit)
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-commit
Expected: все 8 jobs pass или skip (gitleaks/markdownlint/cspell/stylelint/pint/larastan/squawk/eslint-vue).
Level 3 — Visual smoke (после всех 9 implementation коммитов, manual)
Дмитрий открывает docs/automation-graph.html в Edge browser и проверяет per spec §7.3 чеклист (resize / text / conflicts / edge legend / regression).
Pre-push gauntlet
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-push
Expected: gitleaks full-history (0 leaks) + lychee (0 broken links).
Task Dependency Graph
Phase 2 (parallel-safe, dispatchable to 7 subagents):
T1 (resize CSS+JS) ─┐
T2 (text group A) ─┤
T3 (text group B) ─┤
T4 (text group C) ─┤── all independent, parallel-safe
T5 (text group D) ─┤
T6 (CONFLICT_TYPES + ⚫×2) ─┤
T7 (EDGE_DETAILS data) ─┘
Phase 3 (sequential):
T8 (edge legend render) ← depends on T7 (EDGE_DETAILS exists)
T9 (3-color render+footer) ← depends on T6 (CONFLICT_TYPES exists)
T10 (visual smoke + push) ← all prior tasks
Subagent-driven recommendation: dispatch T1-T7 в 7-агентном parallel batch (через superpowers:dispatching-parallel-agents), затем sequential T8, T9, T10.
Task 1: Resize handle (CSS + JS + localStorage)
Files:
- Modify:
docs/automation-graph.html(CSS block lines 8-51 — добавить#legend-handle; HTML lines 64-81 — добавить<div id="legend-handle">; SCRIPT section — добавить новую секцию 7 «RESIZE HANDLE»)
Dependencies: none
Spec ref: §2 (full section)
- Step 1: Verify current state
cd "c:/моя/проекты/портал crm/Документация" && git status --short && git log --oneline -3
Expected: clean working tree, HEAD f936944 или новее.
- Step 2: Edit CSS — add
#legend-handlestyles
Использовать Edit tool. Найти строку 30 (#legend-panel { width: 300px; ...) и добавить ПОСЛЕ неё:
#legend-panel { 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; }
(NB: #legend-panel { position: relative; } дополняет existing rule — Edit замени строку 30 на новую с position:relative и добавь сверху.)
- Step 3: Edit HTML — insert
<div id="legend-handle">
В блоке <div id="legend-panel"> (строка 66), сразу после <div id="legend-panel"> и перед <button id="legend-close"> вставить:
<div id="legend-handle" title="Перетащи, чтобы изменить ширину"></div>
- Step 4: Edit SCRIPT — add resize section
В конце SCRIPT (после строки 1185 document.getElementById('btn-clear')...) добавить новую секцию:
// ════════════════════════════════════════════════════
// SECTION 7: RESIZE HANDLE + LOCALSTORAGE
// ════════════════════════════════════════════════════
const LEGEND_STORAGE_KEY = 'liderra-map-legend-width';
const LEGEND_MIN_W = 300, LEGEND_MAX_W = 900;
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) network.redraw();
}
function restoreLegendWidth() {
const saved = parseInt(localStorage.getItem(LEGEND_STORAGE_KEY) || '300', 10);
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);
localStorage.setItem(LEGEND_STORAGE_KEY, String(w));
});
})();
window.addEventListener('DOMContentLoaded', restoreLegendWidth);
- Step 5: JS syntax check
cd "c:/моя/проекты/портал crm/Документация" && node -e "const fs=require('fs');const html=fs.readFileSync('docs/automation-graph.html','utf8');const m=html.match(/<script>([\s\S]+?)<\/script>/);if(!m)throw new Error('No script block');new Function(m[1]);console.log('SYNTAX OK ('+m[1].length+' chars)');"
Expected: SYNTAX OK (~31500 chars) (was ~30700).
- Step 6: Pre-commit gauntlet
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-commit
Expected: 1 file changed, gitleaks 0 leaks, markdownlint/cspell skip (no .md staged).
- Step 7: Commit
cd "c:/моя/проекты/портал crm/Документация" && git add docs/automation-graph.html && git commit -m "$(cat <<'EOF'
feat(graph): resize handle 300-900px + localStorage
Drag-handle 6px на левой границе #legend-panel (cursor:col-resize, hover-bg #0d4a5a), JS-обработчики mousedown/mousemove/mouseup, clamp [300, 900]px, сохранение ширины в localStorage ключ liderra-map-legend-width, restoration on DOMContentLoaded. После каждого resize вызывается network.redraw() для пересчёта vis.js canvas. Iter2 spec §2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
Expected: [main HASH] feat(graph): resize handle 300-900px + localStorage, 1 file changed, ~55 insertions, all hooks pass.
Task 2-5: Text rewrite (groups A/B/C/D)
Common pattern: 4 параллельных tasks, каждая переписывает NODE_DETAILS для своей группы узлов по Style Guide (spec §3.2). Структура nd() не меняется — только содержимое строк desc/when/limits + items reportsTo/manages/together/conflicts.
Task 2: Text rewrite group A (rules + plugins, 9 nodes)
Files:
- Modify:
docs/automation-graph.htmlстроки 355-463 (NODE_DETAILS блокиpravila,claude_md,psr_v1,tooling,superpowers,fd_plugin,upm,claude_md_mgmt,hookify_plugin)
Dependencies: none
Spec ref: §3 (Style Guide + group A)
- Step 1: Read spec Style Guide
Открыть docs/superpowers/specs/2026-05-14-automation-graph-iter2-design.md §3.2 (Style Guide) — прочитать все 5 принципов и жаргон-блэклист (11 терминов).
- Step 2: Read source nodes 355-463 в automation-graph.html
Прочитать существующие nd() для 9 узлов группы A. Понять текущий жаргон.
- Step 3: Rewrite 9 nd() блоков
Для каждого из 9 узлов переписать desc (50-100 символов, 1-2 предложения, простой язык), when (40-80 символов, 1 предложение), limits (50-150 символов, 1-2 предложения). Сохранить параграф-ссылки как примечания в скобках. Items reportsTo/manages/together/conflicts — переписать cond и desc строки на простой язык если содержат жаргон-блэклист.
Применить через серию Edit'ов (по 1 узлу за раз — Edit tool требует unique old_string).
Пример rewrite (pravila):
Было:
pravila: nd(
'Главный свод правил работы Клода — приоритеты, запреты и обязательные скилы.',
'Действует постоянно — fundamental слой автоматизации; читается при старте каждой сессии через SessionStart хук.',
'§12 hard rule неотменяем; §9 «Отступления» не применяется к §12. Расходимость с CLAUDE.md/PSR_v1/Tooling — нарушение §7.',
...
)
Стало:
pravila: nd(
'Главный свод правил работы Клода — кто чем командует, что запрещено, какие обязательные действия.',
'Действует всегда — Клод читает его при старте каждой сессии.',
'Правило §12 (обязательные скилы) нельзя отменить — даже режимом экономии или «не используй сейчас». Расходимость с другими документами — нарушение §7.',
...
)
- Step 4: JS syntax check
cd "c:/моя/проекты/портал crm/Документация" && node -e "const fs=require('fs');const html=fs.readFileSync('docs/automation-graph.html','utf8');const m=html.match(/<script>([\s\S]+?)<\/script>/);if(!m)throw new Error('No script');new Function(m[1]);console.log('SYNTAX OK ('+m[1].length+' chars)');"
Expected: SYNTAX OK.
- Step 5: Жаргон-блэклист grep verification
cd "c:/моя/проекты/портал crm/Документация" && for w in "hard rule" "matcher" "pipeline" "override" "hook config" "stage_fixed" "pre-commit stage" "bypass" "runtime crash" "peerDep" "guideline"; do echo "=== $w ==="; grep -c "$w" docs/automation-graph.html; done
Expected: для каждого жаргона — 0 (group A не должна содержать ни одного блэклист-термина после rewrite). Если >0 — посмотреть grep -n "$w" docs/automation-graph.html и решить: оставить (если в технической секции вне group A) или переписать.
- Step 6: Pre-commit gauntlet
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-commit
Expected: all jobs pass.
- Step 7: Commit
cd "c:/моя/проекты/портал crm/Документация" && git add docs/automation-graph.html && git commit -m "$(cat <<'EOF'
chore(graph): rewrite group A (rules + plugins, 9 nodes) — plain language
Переписаны nd() блоки для pravila/claude_md/psr_v1/tooling/superpowers/fd_plugin/upm/claude_md_mgmt/hookify_plugin. Жаргон-блэклист (hard rule, matcher, pipeline, override, peerDep и др. — 11 терминов) убран; параграф-ссылки сохранены как примечания в скобках. Iter2 spec §3 (Style Guide + group A).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
Expected: 1 file changed, ~40-80 lines modified.
Task 3: Text rewrite group B (skills + hooks, 21 nodes)
Files: docs/automation-graph.html строки 466-651 (NODE_DETAILS блоки skills_sp sk_brainstorm...sk_elements + skills_proj sk_rls/sk_qitem + hooks hk_pre_claude...hk_economy)
Dependencies: none (parallel-safe с T1, T2, T4, T5, T6, T7)
Steps: идентичны Task 2, но для 21 узла группы B. Commit message:
chore(graph): rewrite group B (skills + hooks, 21 nodes) — plain language
Переписаны nd() блоки для 14 Superpowers-скилов (sk_brainstorm/sk_tdd/...), 2 проектных (sk_rls/sk_qitem), 5 хуков (hk_pre_claude/hk_post_md/hk_post_schema/hk_session/hk_economy). Жаргон-блэклист убран, параграф-ссылки сохранены. Iter2 spec §3 group B.
Task 4: Text rewrite group C (agents + MCP, 18 nodes)
Files: docs/automation-graph.html строки 654-800 (NODE_DETAILS блоки ag_pest...ag_skreview + mcp_pw...mcp_21st)
Dependencies: none
Steps: идентичны Task 2 для 18 узлов. Commit:
chore(graph): rewrite group C (agents + MCP, 18 nodes) — plain language
Переписаны nd() блоки для 11 агентов (ag_pest/ag_rls/ag_explore/ag_general/ag_plan/ag_guide/ag_statusline/ag_hookify/ag_pcreator/ag_pvalid/ag_skreview) и 7 MCP (mcp_pw/mcp_gh/mcp_boost/mcp_semgrep/mcp_sentry/mcp_redis/mcp_21st). Иностранные аббревиатуры расшифрованы (SAST/CVE/SQLi/XSS/ПДн). Iter2 spec §3 group C.
Task 5: Text rewrite group D (lefthook + memory, 25 nodes)
Files: docs/automation-graph.html строки 803-1008 (NODE_DETAILS блоки lh_gitleaks...lh_lychee + mem_user...mem_devindices)
Dependencies: none
Steps: идентичны Task 2 для 25 узлов. Commit:
chore(graph): rewrite group D (lefthook + memory, 25 nodes) — plain language
Переписаны nd() блоки для 10 lefthook jobs (lh_gitleaks/lh_mdlint/lh_cspell/lh_stylelint/lh_pint/lh_larastan/lh_squawk/lh_eslint/lh_gitleaks2/lh_lychee) и 15 memory-файлов. Уточнено что «pre-commit stage» = «перед каждым коммитом», «stage_fixed:true» = «авто-сохранить исправленное». Iter2 spec §3 group D.
Task 6: 3-color conflicts data (CONFLICT_TYPES enum + 2 new ⚫ edges + reclassify 6 existing)
Files:
- Modify:
docs/automation-graph.htmlстроки 213-223 (CONFLICT helper) + 321-326 (6 existing CONFLICT() entries) + NODE_DETAILS conflicts items (lines 383, 396, 421-422, 431-433, 442, 459-462, 591, 610, 649, 668, 799)
Dependencies: none
Spec ref: §4.1, §4.2, §4.4
- Step 1: Add CONFLICT_TYPES enum
Edit найти строку 213 (const E = (from, to, label) => ({) и ВСТАВИТЬ ПЕРЕД ней:
const CONFLICT_TYPES = {
RED: { color: '#ff5f57', bg: '#2d0000', emoji: '🔴', label: 'Не закрыт правилом', rank: 1 },
BLACK: { color: '#888888', bg: '#1a1a1a', emoji: '⚫', label: 'Возник на практике', rank: 2 },
GREEN: { color: '#859900', bg: '#0e1a00', emoji: '🟢', label: 'Закрыт правилом', rank: 3 },
};
- Step 2: Extend CONFLICT() helper to accept
typeargument
Заменить existing CONFLICT helper (строки 213-223) на:
const CONFLICT = (from, to, label, type = 'RED') => ({
from, to,
title: label,
label: CONFLICT_TYPES[type].emoji,
dashes: true,
width: 2,
color: { color: CONFLICT_TYPES[type].color, highlight: '#ff8880', hover: '#ff8880' },
arrows: { to: { enabled: true, scaleFactor: 0.7 }, from: { enabled: true, scaleFactor: 0.7 } },
font: { color: CONFLICT_TYPES[type].color, size: 14, align: 'middle', strokeWidth: 3, strokeColor: '#1e1e2e' },
smooth: { type: 'curvedCW', roundness: 0.35 }
});
- Step 3: Reclassify 6 existing CONFLICT() entries (lines 321-326)
Заменить весь блок строк 318-327 на:
// ══════════════════════════════════════════════════
// КОНФЛИКТЫ — 3-color classification (iter2 §4)
// 🔴 не закрыт правилом / ⚫ возник на практике / 🟢 закрыт правилом
// ══════════════════════════════════════════════════
CONFLICT('sk_rls', 'ag_rls', 'RLS compliance: оба покрывают, нет регламента', 'RED'),
CONFLICT('hookify_plugin', 'hk_pre_claude', 'hookify может перезаписать существующий хук', 'RED'),
CONFLICT('mcp_pw', 'sk_parallel', 'Browser is already in use (квирк #2)', 'BLACK'),
CONFLICT('ag_pest', 'mcp_redis', 'Redis race в Pest --parallel subdir-only (квирк 72)', 'BLACK'),
CONFLICT('psr_v1', 'claude_md', 'Закрыто §5п.10 CLAUDE.md + хук CLAUDE.md-warn', 'GREEN'),
CONFLICT('upm', 'fd_plugin', 'PSR_v1 R14.5: не параллельно', 'GREEN'),
CONFLICT('mcp_21st', 'fd_plugin', 'PSR_v1 R14.5: не параллельно', 'GREEN'),
CONFLICT('hk_economy', 'superpowers', 'Pravila §12.4: §12 неотменяем', 'GREEN'),
(NB: добавлено 2 новых ⚫ edges: mcp_pw↔sk_parallel и ag_pest↔mcp_redis.)
- Step 4: Add
typefield to conflicts items в NODE_DETAILS
Найти все existing conflicts items в NODE_DETAILS (12 мест) и добавить type field:
claude_mdconflicts (line ~383):{ name: 'PSR_v1', desc: '...', type: 'GREEN' }(вместо отсутствующего type)psr_v1conflicts (line ~396): аналогично, GREENsuperpowersconflicts (line ~421):{ name: 'economy-mode хук', desc: '...', type: 'GREEN' }fd_pluginconflicts (line ~431-433): 2 items, оба GREENupmconflicts (line ~442): GREENhookify_pluginconflicts (line ~459-462): 2 items — оба RED (hk_pre_claude RED, hk_economy не существует как конфликт — переписать)sk_rlsconflicts (line ~591): REDhk_pre_claudeconflicts (line ~610): RED (hookify затенение)hk_economyconflicts (line ~649): GREENag_rlsconflicts (line ~668): REDmcp_21stconflicts (line ~799): GREEN
Также добавить новые conflicts items для:
mcp_pw(новый ⚫ конфликт с sk_parallel):{ name: 'parallel-work скил', desc: 'Один shared browser на сессию — конкуренция при parallel-work (см. memory квирк #2)', type: 'BLACK' }sk_parallel(новый ⚫ конфликт с mcp_pw):{ name: 'MCP: playwright', desc: 'Browser is already in use при параллельном запуске нескольких сессий через worktrees', type: 'BLACK' }ag_pest(новый ⚫ конфликт с mcp_redis):{ name: 'MCP: redis', desc: 'Pest --parallel subdir-only race с Redis кэшем (квирк 72)', type: 'BLACK' }mcp_redis(новый ⚫ конфликт с ag_pest):{ name: 'pest-parallel-debugger агент', desc: 'Redis race condition в --parallel subdir-only runs (квирк 72)', type: 'BLACK' }
(NB: эта step самый трудоёмкий — 12 existing + 4 new conflicts items = 16 правок. Делать через серию targeted Edit'ов.)
- Step 5: JS syntax check
cd "c:/моя/проекты/портал crm/Документация" && node -e "const fs=require('fs');const html=fs.readFileSync('docs/automation-graph.html','utf8');const m=html.match(/<script>([\s\S]+?)<\/script>/);if(!m)throw new Error('No script');new Function(m[1]);console.log('SYNTAX OK ('+m[1].length+' chars)');"
Expected: SYNTAX OK.
- Step 6: Pre-commit gauntlet
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-commit
Expected: pass.
- Step 7: Commit
cd "c:/моя/проекты/портал crm/Документация" && git add docs/automation-graph.html && git commit -m "$(cat <<'EOF'
feat(graph): 3-color conflicts data (CONFLICT_TYPES + 2 new ⚫ edges + reclassify 6)
CONFLICT_TYPES enum (RED/BLACK/GREEN), CONFLICT() helper расширен опциональным `type` (default RED). 6 существующих рёбер реклассифицированы: 2 🔴 (sk_rls↔ag_rls, hookify↔hk_pre_claude), 4 🟢 (psr_v1↔claude_md, upm↔fd, 21st↔fd, economy↔superpowers). 2 новых ⚫ ребра: mcp_pw↔sk_parallel (browser-in-use, квирк #2), ag_pest↔mcp_redis (Redis race в Pest --parallel, квирк 72). NODE_DETAILS conflicts items получили field `type`. Iter2 spec §4.1, §4.2, §4.4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
Expected: ~30-50 lines modified.
Task 7: EDGE_DETAILS data structure
Files:
- Modify:
docs/automation-graph.htmlSCRIPT section — добавить новый блокEDGE_DETAILSпосле строки 1008 (после NODE_DETAILS) и до строки 1010 (SECTION 4: VIS INIT)
Dependencies: none
Spec ref: §5.1, §5.2, §5.3
- Step 1: Build EDGE_DETAILS object for all 72 edges
Прочитать EDGES array (строки 225-327 в automation-graph.html) и составить EDGE_DETAILS для каждого from→to ребра.
Структура entry:
'<from_id>-><to_id>': {
type: '<enum>', // содержит/подчиняет/координирует/читает/запускает/документирует/триггерит/альтернатива/конфликт
when: '<строка>', // когда срабатывает (по-русски)
transfers: '<enum>', // контроль/данные/проверка/триггер/документация/coverage
mandatory: '<enum>', // обязательно/опционально/hard-block/рекомендуется
rule: '<строка>', // ссылка на правило или 'нет регламента'
},
Для рёбер-конфликтов (8 штук) — type: 'конфликт', остальные поля — описание самой природы конфликта (when: 'при одновременной активации обоих', transfers: 'триггер', mandatory: 'hard-block' для 🔴, опционально для ⚫, обязательно для 🟢, rule: ... — соответствующая ссылка).
Полный объект — ~72 entries × 5 полей. Размещается между NODE_DETAILS и SECTION 4 VIS INIT.
Образец первых 5 entries:
// ════════════════════════════════════════════════════
// SECTION 3.5: EDGE DETAILS (iter2 §5)
// ════════════════════════════════════════════════════
const edgeKey = (from, to) => from + '->' + to;
const EDGE_DETAILS = {
'pravila->claude_md': {
type: 'подчиняет',
when: 'всегда — CLAUDE.md находится на уровень ниже Pravila в иерархии правил',
transfers: 'контроль',
mandatory: 'обязательно',
rule: 'Pravila §1 цепочка приоритетов (уровень 1 → 2a)',
},
'pravila->psr_v1': {
type: 'подчиняет',
when: 'всегда — PSR_v1 третий уровень иерархии',
transfers: 'контроль',
mandatory: 'обязательно',
rule: 'Pravila §1 (уровень 1 → 3)',
},
'claude_md->tooling': {
type: 'документирует',
when: 'когда нужно узнать какой инструмент за что отвечает',
transfers: 'документация',
mandatory: 'рекомендуется',
rule: 'CLAUDE.md §3 ссылается на Tooling Прил. Н',
},
'pravila->superpowers': {
type: 'подчиняет',
when: 'при creative / multi-step / debug / verify / TDD / brainstorm задачах',
transfers: 'контроль',
mandatory: 'обязательно',
rule: 'Pravila §12 hard rule',
},
'psr_v1->superpowers': {
type: 'координирует',
when: 'при UI-задачах когда нужны и процесс и UI-знания',
transfers: 'контроль',
mandatory: 'обязательно',
rule: 'PSR_v1 R5 (парный стек)',
},
// ... остальные 67 entries
};
- Step 2: Place EDGE_DETAILS object в файл
Использовать Edit. Найти строку 1008 (последняя строка }; от NODE_DETAILS) и сразу после неё вставить весь блок EDGE_DETAILS + комментарий-секцию.
- Step 3: JS syntax check
cd "c:/моя/проекты/портал crm/Документация" && node -e "const fs=require('fs');const html=fs.readFileSync('docs/automation-graph.html','utf8');const m=html.match(/<script>([\s\S]+?)<\/script>/);if(!m)throw new Error('No script');new Function(m[1]);console.log('SYNTAX OK ('+m[1].length+' chars)');"
Expected: SYNTAX OK, length ~40000+ chars (was ~32000).
- Step 4: Coverage check
cd "c:/моя/проекты/портал crm/Документация" && node -e "const fs=require('fs');const html=fs.readFileSync('docs/automation-graph.html','utf8');const m=html.match(/<script>([\s\S]+?)<\/script>/)[1];const fn=new Function(m+';return {EDGES,EDGE_DETAILS};');const {EDGES,EDGE_DETAILS}=fn();const missing=EDGES.filter(e=>!EDGE_DETAILS[e.from+'->'+e.to]);console.log('Total EDGES:',EDGES.length,'EDGE_DETAILS:',Object.keys(EDGE_DETAILS).length,'Missing:',missing.length);if(missing.length>0)console.log('Missing entries:',missing.map(e=>e.from+'->'+e.to));"
Expected: Total EDGES: 74, EDGE_DETAILS: 74, Missing: 0 (74 = 72 normal + 2 new ⚫; если 76 — оригинальные 72 + 4 после добавления ⚫ из Task 6, нужно сверить).
Если Missing > 0 — добавить пропущенные entries в EDGE_DETAILS и повторить.
- Step 5: Pre-commit gauntlet
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-commit
Expected: pass.
- Step 6: Commit
cd "c:/моя/проекты/портал crm/Документация" && git add docs/automation-graph.html && git commit -m "$(cat <<'EOF'
feat(graph): EDGE_DETAILS data structure (7-field profile for all edges)
Новый объект EDGE_DETAILS — для каждого ребра 5 полей (type/when/transfers/mandatory/rule). Источник и получатель derived from `from`/`to` при рендере. Покрытие 100% (все рёбра имеют запись). Iter2 spec §5.1, §5.2, §5.3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
Expected: ~80-100 lines insertion.
Task 8: Edge legend render + click handler
Files:
- Modify:
docs/automation-graph.html:- HTML strings 64-81 (refactor
#legend-panelcontent в 2 containers#legend-node-contentи#legend-edge-content) - SCRIPT section 5 (function
showEdgeLegend+ refactorshowLegend→showNodeLegend+ update click handler) - CSS — добавить styles для edge layout (
.edge-section,.edge-meta-row)
- HTML strings 64-81 (refactor
Dependencies: T7 (EDGE_DETAILS должен существовать)
Spec ref: §5.4, §5.5
- Step 1: Refactor
#legend-panelHTML — 2 containers
Edit найти строки 66-80 (<div id="legend-panel">...) и заменить на:
<div id="legend-panel">
<div id="legend-handle" title="Перетащи, чтобы изменить ширину"></div>
<button id="legend-close">×</button>
<div id="legend-node-content">
<div id="legend-title">—</div>
<div id="legend-category"></div>
<div class="legend-section"><h4>Что делает</h4><p id="ld-desc">—</p></div>
<div class="legend-section"><h4>Когда используется</h4><p id="ld-when">—</p></div>
<div class="legend-section"><h4>Ограничения</h4><p id="ld-limits">—</p></div>
<div class="legend-section"><h4>Кому подчиняется</h4><ul id="ld-reports"></ul></div>
<div class="legend-section"><h4>Кто подчиняется ему</h4><ul id="ld-manages"></ul></div>
<div class="legend-section"><h4>С кем работает одновременно</h4><ul id="ld-together"></ul></div>
<div class="legend-section" id="conflicts-section">
<h4>⚡ Конфликты</h4>
<div id="ld-conflicts"></div>
</div>
</div>
<div id="legend-edge-content" style="display:none;">
<div id="legend-edge-title">—</div>
<div id="legend-edge-arrow">→</div>
<div class="legend-section"><h4>Источник запроса</h4><p id="le-from">—</p></div>
<div class="legend-section"><h4>Конечный получатель</h4><p id="le-to">—</p></div>
<div class="legend-section"><h4>Тип связи</h4><p id="le-type">—</p></div>
<div class="legend-section"><h4>Когда срабатывает</h4><p id="le-when">—</p></div>
<div class="legend-section"><h4>Что передаёт</h4><p id="le-transfers">—</p></div>
<div class="legend-section"><h4>Обязательность</h4><p id="le-mandatory">—</p></div>
<div class="legend-section"><h4>Регламент</h4><p id="le-rule">—</p></div>
</div>
</div>
- Step 2: Add CSS for edge layout
В CSS блоке (~ строка 47) добавить:
#legend-edge-title { font-size: 15px; font-weight: 600; color: #fdf6e3; overflow-wrap: break-word; }
#legend-edge-arrow { font-size: 18px; color: #93a1a1; text-align: center; margin: -8px 0 8px; }
- Step 3: Add
showEdgeLegendfunction in SECTION 5
В SECTION 5 (SCRIPT, ~строка 1061) после function showLegend(nodeId) добавить новую функцию (и переименовать существующую showLegend → showNodeLegend):
function showNodeLegend(nodeId) {
document.getElementById('legend-node-content').style.display = '';
document.getElementById('legend-edge-content').style.display = 'none';
// ... existing showLegend body unchanged ...
}
function showEdgeLegend(edgeId) {
const edge = edgesDS.get(edgeId);
if (!edge) return;
const panel = document.getElementById('legend-panel');
document.getElementById('legend-node-content').style.display = 'none';
document.getElementById('legend-edge-content').style.display = '';
const fromNode = NODES.find(n => n.id === edge.from);
const toNode = NODES.find(n => n.id === edge.to);
if (!fromNode || !toNode) return;
const fromCat = CATEGORY_LABELS[fromNode.group] || fromNode.group;
const toCat = CATEGORY_LABELS[toNode.group] || toNode.group;
const fromColor = GROUPS[fromNode.group]?.color?.border || '#839496';
const toColor = GROUPS[toNode.group]?.color?.border || '#839496';
const edgeColor = (edge.color && edge.color.color) ? edge.color.color : '#586e75';
document.getElementById('legend-edge-title').innerHTML =
`<span style="color:${fromColor}">${fromNode.label.replace(/\n/g,' ')}</span>` +
` <span style="color:${edgeColor}">→</span> ` +
`<span style="color:${toColor}">${toNode.label.replace(/\n/g,' ')}</span>`;
document.getElementById('legend-edge-arrow').textContent = '';
document.getElementById('le-from').innerHTML =
`${fromNode.label.replace(/\n/g,' ')} <span style="color:${fromColor};font-size:11px;text-transform:uppercase;">(${fromCat})</span>`;
document.getElementById('le-to').innerHTML =
`${toNode.label.replace(/\n/g,' ')} <span style="color:${toColor};font-size:11px;text-transform:uppercase;">(${toCat})</span>`;
const details = EDGE_DETAILS[edgeKey(edge.from, edge.to)];
if (details) {
document.getElementById('le-type').textContent = details.type;
document.getElementById('le-when').textContent = details.when;
document.getElementById('le-transfers').textContent = details.transfers;
document.getElementById('le-mandatory').textContent = details.mandatory;
document.getElementById('le-rule').textContent = details.rule;
} else {
['le-type','le-when','le-transfers','le-mandatory'].forEach(id =>
document.getElementById(id).textContent = '—');
document.getElementById('le-rule').textContent = 'Регламент не задокументирован';
}
panel.classList.add('visible');
}
- Step 4: Update click handler
Edit найти network.on('click', params => { (~строка 1111) и заменить тело на:
network.on('click', params => {
if (params.nodes.length === 1) {
showNodeLegend(params.nodes[0]);
} else if (params.edges.length === 1) {
showEdgeLegend(params.edges[0]);
} else if (params.nodes.length === 0 && params.edges.length === 0) {
document.getElementById('legend-panel').classList.remove('visible');
}
});
NB: showLegend references в search handler (~строка 1147) обновить на showNodeLegend.
- Step 5: JS syntax check + click handler trace
cd "c:/моя/проекты/портал crm/Документация" && node -e "const fs=require('fs');const html=fs.readFileSync('docs/automation-graph.html','utf8');const m=html.match(/<script>([\s\S]+?)<\/script>/);if(!m)throw new Error('No script');new Function(m[1]);console.log('SYNTAX OK ('+m[1].length+' chars)');"
Expected: SYNTAX OK.
- Step 6: Pre-commit gauntlet
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-commit
- Step 7: Commit
cd "c:/моя/проекты/портал crm/Документация" && git add docs/automation-graph.html && git commit -m "$(cat <<'EOF'
feat(graph): edge legend render + click handler for 7-field edge profile
#legend-panel разделён на 2 containers (node + edge). На click по ребру открывается edge layout с 7 полями (источник/получатель/тип связи/когда/что передаёт/обязательность/регламент). showLegend переименована в showNodeLegend для ясности. Iter2 spec §5.4, §5.5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
Expected: ~80-100 lines modified.
Task 9: 3-color conflicts render + sort + footer cat-legend
Files:
- Modify:
docs/automation-graph.html:- CSS — обновить
.conflict-itemдля динамического bg - SCRIPT — обновить
showNodeLegendconflicts rendering (sort + colored badges) - HTML footer (
#cat-legend, ~строка 83-94) — заменить 1 «— конфликт» badge на 3
- CSS — обновить
Dependencies: T6 (CONFLICT_TYPES должны существовать), T8 (showNodeLegend должен существовать)
Spec ref: §4.3
- Step 1: Update CSS for
.conflict-item
Edit найти CSS строку 42 (.conflict-item { background: #2d0000; ... }) и заменить на:
.conflict-item { border-radius: 4px; padding: 6px 8px; margin-top: 4px; }
.conflict-item .cname { font-weight: 600; font-size: 12px; }
.conflict-item .cdesc { color: #eee8d5; font-size: 11px; margin-top: 2px; line-height: 1.4; }
(bg перенесён в inline через JS из CONFLICT_TYPES[type].bg).
- Step 2: Update conflicts rendering в showNodeLegend
Edit найти блок «conflicts rendering» (~строка 1098-1106) и заменить:
const ldConflicts = document.getElementById('ld-conflicts');
if (details.conflicts && details.conflicts.length) {
const sorted = [...details.conflicts].sort((a, b) =>
(CONFLICT_TYPES[a.type] ? CONFLICT_TYPES[a.type].rank : 999) -
(CONFLICT_TYPES[b.type] ? CONFLICT_TYPES[b.type].rank : 999)
);
ldConflicts.innerHTML = sorted.map(c => {
const t = CONFLICT_TYPES[c.type] || CONFLICT_TYPES.RED;
return `<div class="conflict-item" style="background:${t.bg}">
<div class="cname" style="color:${t.color}">${t.emoji} ${c.name}</div>
<div class="cdesc">${c.desc}</div>
</div>`;
}).join('');
} else {
ldConflicts.innerHTML = '<div id="legend-no-conflicts" style="font-size:12px;color:#586e75;">Конфликтов не выявлено</div>';
}
- Step 3: Update footer cat-legend
Edit найти строку 93 (<div class="cat-item"><div class="cat-dot" style="background:#ff5f57; border: 1px dashed #ff5f57;"></div>— конфликт</div>) и заменить на 3 строки:
<div class="cat-item"><div class="cat-dot" style="background:#ff5f57; border:1px dashed #ff5f57"></div>🔴 Не закрыт правилом</div>
<div class="cat-item"><div class="cat-dot" style="background:#888; border:1px dashed #888"></div>⚫ Возник на практике</div>
<div class="cat-item"><div class="cat-dot" style="background:#859900; border:1px dashed #859900"></div>🟢 Закрыт правилом</div>
- Step 4: JS syntax check + render-trace test
cd "c:/моя/проекты/портал crm/Документация" && node -e "const fs=require('fs');const html=fs.readFileSync('docs/automation-graph.html','utf8');const m=html.match(/<script>([\s\S]+?)<\/script>/);if(!m)throw new Error('No script');new Function(m[1]);console.log('SYNTAX OK ('+m[1].length+' chars)');"
Expected: SYNTAX OK.
cd "c:/моя/проекты/портал crm/Документация" && node -e "const fs=require('fs');const html=fs.readFileSync('docs/automation-graph.html','utf8');const m=html.match(/<script>([\s\S]+?)<\/script>/)[1];const fn=new Function(m+';return {CONFLICT_TYPES,NODE_DETAILS};');const {CONFLICT_TYPES,NODE_DETAILS}=fn();const allConflicts=Object.values(NODE_DETAILS).flatMap(d=>d.conflicts||[]);const byType=allConflicts.reduce((acc,c)=>(acc[c.type||'undefined']=(acc[c.type||'undefined']||0)+1,acc),{});console.log('Conflict counts by type:',byType);"
Expected: Conflict counts by type: { RED: ≥4, BLACK: ≥4, GREEN: ≥8 } (числа удвоены потому что каждый конфликт записан в обе стороны узлов).
- Step 5: Pre-commit gauntlet
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-commit
- Step 6: Commit
cd "c:/моя/проекты/портал crm/Документация" && git add docs/automation-graph.html && git commit -m "$(cat <<'EOF'
feat(graph): 3-color conflicts render + sort 🔴→⚫→🟢 + footer cat-legend
.conflict-item теперь использует динамический bg из CONFLICT_TYPES[type].bg, эмодзи-префикс + цветной name. Сортировка через CONFLICT_TYPES[type].rank (RED=1, BLACK=2, GREEN=3). Footer cat-legend заменил 1 «— конфликт» бэйдж на 3 цветных. Iter2 spec §4.3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
Expected: ~30 lines modified.
Task 10: Visual smoke + final push
Files: none (verification + push)
Dependencies: T1-T9 all complete
Spec ref: §7.3, §7.4
- Step 1: Pre-push gauntlet
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-push
Expected: gitleaks 0 leaks (full-history), lychee 0 errors. Output показывать целиком.
- Step 2: Open в Edge browser
start "" "c:/моя/проекты/портал crm/Документация/docs/automation-graph.html"
Дмитрий проверяет per §7.3 чеклист (5 аспектов):
| Аспект | Что смотреть | OK? |
|---|---|---|
| Resize | Drag левого края panel, восстановление ширины после reload | ☐ |
| Text | Случайные 10 узлов: понятно нон-tech reader'у | ☐ |
| Conflicts | 3 цвета на canvas, footer 3 бэйджа, сортировка в node legend | ☐ |
| Edge legend | Click на 5 разных рёбер: все 7 полей заполнены | ☐ |
| Regression | Поиск, фиксация, расшевелить, сброс — работают; tooltip на ребро | ☐ |
- Step 3: Дмитрий говорит «принято» или указывает регрессии
Если регрессии — открыть новые tasks. Если принято — push.
- Step 4: Push to origin/main
cd "c:/моя/проекты/портал crm/Документация" && git push origin main
Expected:
To github.com:CoralMinister/lidpotok.git
f936944..NEW_HASH main -> main
Pre-push hooks отработают ещё раз (gitleaks full-history + lychee). Output показывать целиком.
- Step 5: Update memory
project_automation_map.md
После push — обновить memory с новым HEAD commit, обновлённым числом конфликтов (6 → 8), числом конфликтных категорий (1 → 3), статусом iter2 (closed):
cd "c:/моя/проекты/портал crm/Документация" && git log --oneline -15
Зафиксировать новый HEAD в memory. Обновить:
- Текущее состояние (14.05.2026) — HEAD commit
- Коммиты: добавить запись про iter2 (+9 implementation commits + 2 docs)
- 6 конфликтных рёбер → 8 конфликтных рёбер (🔴2 / ⚫2 / 🟢4)
Через Edit на memory/project_automation_map.md.
- Step 6: Final report
Сводный отчёт Дмитрию:
- Коммиты pushed (
f936944..NEW_HASH) - 9 implementation commits + 2 doc commits = 11 commits since iter1 close
- Все hooks pass (gitleaks 0, lychee 0, markdownlint 0, cspell 0)
- Visual smoke: 5/5 pass
- Memory updated
Self-Review
1. Spec coverage
| Spec section | Task | Status |
|---|---|---|
| §2 Resize panel | T1 | ✅ |
| §3 Text rewrite (Style Guide) | T2-T5 | ✅ |
| §3.3 4 groups (A/B/C/D, 9/21/18/25) | T2/T3/T4/T5 соответственно | ✅ |
| §3.4 Audit чеклист | T2-T5 step «жаргон-блэклист grep» | ✅ |
| §4.1 CONFLICT_TYPES enum | T6 step 1 | ✅ |
| §4.2 8 классифицированных конфликтов | T6 step 3 | ✅ |
| §4.3 Renderer changes | T9 | ✅ |
| §4.4 Data migration (type field) | T6 step 4 | ✅ |
| §5.1 7 полей | T8 step 1 (HTML structure) + T7 step 1 (data) | ✅ |
| §5.2 Enum значения | T7 step 1 (внутри EDGE_DETAILS values) | ✅ |
| §5.3 EDGE_DETAILS | T7 | ✅ |
| §5.4 UI переключение | T8 step 1 (2 containers) | ✅ |
| §5.5 Click handler | T8 step 4 | ✅ |
| §6 Parallel execution strategy | Tasks 1-7 marked as parallel-safe | ✅ |
| §6.5 11 commits plan | T1-T9 = 9 commits + 2 docs = 11 | ✅ |
| §7 Verification | T10 + per-task lefthook | ✅ |
Coverage: 100%. No gaps.
2. Placeholder scan
- ✅ Все steps содержат конкретные commands и code blocks.
- ✅ Нет «TBD» / «TODO» / «implement later».
- ✅ Tasks 2-5 имеют «Steps идентичны Task 2» — это допустимый short-hand при идентичной структуре (Task 2 содержит полный example, остальные просто меняют группу).
- ⚠️ Task 7 step 1 показывает 5 entries из 72 — остальные 67 не expanded в plan. Это намеренно: содержимое EDGE_DETAILS — задача subagent во время execution (по spec §5.1 + §5.2 enum). Plan указывает структуру и pattern.
- ⚠️ Tasks 2-5 не показывают rewrite всех узлов — это намеренно (по spec Style Guide subagent rewrite'ит во время execution).
3. Type consistency
showLegend→showNodeLegend— переименование явное в T8 step 3 и T8 step 4.CONFLICT_TYPES— определён в T6 step 1, used в T9 steps 2 и 4.EDGE_DETAILS— определён в T7 step 1, used в T8 step 3 (showEdgeLegend).edgeKey(from, to)helper — определён в T7 step 1, used в T8 step 3.
No type drift.
Execution Handoff
Plan complete and saved to docs/superpowers/plans/2026-05-14-automation-graph-iter2.md. Two execution options:
1. Subagent-Driven (recommended) — главный агент диспетчит fresh subagent per task, review между tasks. Phase 2 (T1-T7) — через superpowers:dispatching-parallel-agents (7 параллельных subagent'ов). Phase 3 (T8-T10) — sequential subagent-per-task. Fast iteration, isolated context per task.
2. Inline Execution — выполнение всех tasks в этой сессии через superpowers:executing-plans. Контекст копится, но checkpoint'ы между tasks для review. Меньше overhead на subagent dispatch.
Какой подход?