# 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` запускать:

```bash
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)

```bash
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

```bash
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**

```bash
cd "c:/моя/проекты/портал crm/Документация" && git status --short && git log --oneline -3
```

Expected: clean working tree, HEAD `f936944` или новее.

- [ ] **Step 2: Edit CSS — add `#legend-handle` styles**

Использовать Edit tool. Найти строку 30 (`#legend-panel { width: 300px; ...`) и добавить ПОСЛЕ неё:

```css
    #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">` вставить:

```html
    <div id="legend-handle" title="Перетащи, чтобы изменить ширину"></div>
```

- [ ] **Step 4: Edit SCRIPT — add resize section**

В конце SCRIPT (после строки 1185 `document.getElementById('btn-clear')...`) добавить новую секцию:

```js
// ════════════════════════════════════════════════════
// 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**

```bash
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**

```bash
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-commit
```

Expected: 1 file changed, gitleaks 0 leaks, markdownlint/cspell skip (no .md staged).

- [ ] **Step 7: Commit**

```bash
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):

Было:

```js
pravila: nd(
  'Главный свод правил работы Клода — приоритеты, запреты и обязательные скилы.',
  'Действует постоянно — fundamental слой автоматизации; читается при старте каждой сессии через SessionStart хук.',
  '§12 hard rule неотменяем; §9 «Отступления» не применяется к §12. Расходимость с CLAUDE.md/PSR_v1/Tooling — нарушение §7.',
  ...
)
```

Стало:

```js
pravila: nd(
  'Главный свод правил работы Клода — кто чем командует, что запрещено, какие обязательные действия.',
  'Действует всегда — Клод читает его при старте каждой сессии.',
  'Правило §12 (обязательные скилы) нельзя отменить — даже режимом экономии или «не используй сейчас». Расходимость с другими документами — нарушение §7.',
  ...
)
```

- [ ] **Step 4: JS syntax check**

```bash
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**

```bash
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**

```bash
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-commit
```

Expected: all jobs pass.

- [ ] **Step 7: Commit**

```bash
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) => ({`) и ВСТАВИТЬ ПЕРЕД ней:

```js
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 `type` argument**

Заменить existing CONFLICT helper (строки 213-223) на:

```js
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 на:

```js
  // ══════════════════════════════════════════════════
  // КОНФЛИКТЫ — 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 `type` field to conflicts items в NODE_DETAILS**

Найти все existing conflicts items в NODE_DETAILS (12 мест) и добавить `type` field:

- `claude_md` conflicts (line ~383): `{ name: 'PSR_v1', desc: '...', type: 'GREEN' }` (вместо отсутствующего type)
- `psr_v1` conflicts (line ~396): аналогично, GREEN
- `superpowers` conflicts (line ~421): `{ name: 'economy-mode хук', desc: '...', type: 'GREEN' }`
- `fd_plugin` conflicts (line ~431-433): 2 items, оба GREEN
- `upm` conflicts (line ~442): GREEN
- `hookify_plugin` conflicts (line ~459-462): 2 items — оба RED (hk_pre_claude RED, hk_economy не существует как конфликт — переписать)
- `sk_rls` conflicts (line ~591): RED
- `hk_pre_claude` conflicts (line ~610): RED (hookify затенение)
- `hk_economy` conflicts (line ~649): GREEN
- `ag_rls` conflicts (line ~668): RED
- `mcp_21st` conflicts (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**

```bash
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**

```bash
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-commit
```

Expected: pass.

- [ ] **Step 7: Commit**

```bash
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.html` SCRIPT 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:

```js
'<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:

```js
// ════════════════════════════════════════════════════
// 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**

```bash
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**

```bash
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**

```bash
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-commit
```

Expected: pass.

- [ ] **Step 6: Commit**

```bash
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-panel` content в 2 containers `#legend-node-content` и `#legend-edge-content`)
  - SCRIPT section 5 (function `showEdgeLegend` + refactor `showLegend` → `showNodeLegend` + update click handler)
  - CSS — добавить styles для edge layout (`.edge-section`, `.edge-meta-row`)

**Dependencies:** T7 (EDGE_DETAILS должен существовать)

**Spec ref:** §5.4, §5.5

- [ ] **Step 1: Refactor `#legend-panel` HTML — 2 containers**

Edit найти строки 66-80 (`<div id="legend-panel">...`) и заменить на:

```html
  <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) добавить:

```css
    #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 `showEdgeLegend` function in SECTION 5**

В SECTION 5 (SCRIPT, ~строка 1061) после `function showLegend(nodeId)` добавить новую функцию (и переименовать существующую `showLegend` → `showNodeLegend`):

```js
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) и заменить тело на:

```js
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**

```bash
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**

```bash
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-commit
```

- [ ] **Step 7: Commit**

```bash
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 — обновить `showNodeLegend` conflicts rendering (sort + colored badges)
  - HTML footer (`#cat-legend`, ~строка 83-94) — заменить 1 «— конфликт» badge на 3

**Dependencies:** T6 (CONFLICT_TYPES должны существовать), T8 (showNodeLegend должен существовать)

**Spec ref:** §4.3

- [ ] **Step 1: Update CSS for `.conflict-item`**

Edit найти CSS строку 42 (`.conflict-item { background: #2d0000; ... }`) и заменить на:

```css
    .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) и заменить:

```js
  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 строки:

```html
  <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**

```bash
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.

```bash
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**

```bash
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-commit
```

- [ ] **Step 6: Commit**

```bash
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**

```bash
cd "c:/моя/проекты/портал crm/Документация" && lefthook run pre-push
```

Expected: gitleaks 0 leaks (full-history), lychee 0 errors. Output показывать целиком.

- [ ] **Step 2: Open в Edge browser**

```bash
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**

```bash
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):

```bash
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.

**Какой подход?**
