---
title: "Automation Graph — iter2 (resize + simple-language + 3-color conflicts + edge legend)"
date: 2026-05-14
author: Claude (через superpowers:brainstorming)
status: design — awaiting user review
related:
  - docs/automation-graph.html (artifact)
  - docs/superpowers/specs/2026-05-14-automation-graph-refactor-design.md (iter1 spec — закрыт)
  - docs/superpowers/plans/2026-05-14-automation-graph-refactor.md (iter1 plan — закрыт)
  - memory/project_automation_map.md (термин «карта»)
---

# Automation Graph — iter2 (resize + simple-language + 3-color conflicts + edge legend)

## 0. Context

`docs/automation-graph.html` — single-file интерактивный граф автоматизации Лидерры на vis.js 9.1.9 (73 узла / 9 групп / 72 ребра / radial-sector layout). Был зарелизен 13.05.2026 (`7ee78a9`) и отрефакторен 14.05.2026 (10 commits → `54ee37c`). Заказчик Дмитрий — нетехнический владелец проекта.

После использования карты в течение дня Дмитрий зафиксировал 4 улучшения для iter2. Все 4 — независимы по контенту, имеют пересечения только по структуре файла. Brainstorming-цикл прошёл 14.05.2026 (4 clarifying-вопроса через AskUserQuestion → дизайн принят).

## 1. Цели и не-цели

**Цели:**

1. **Resizable legend panel** — растягиваемая правая панель (300-900px, drag-handle, сохранение в localStorage).
2. **Simple-language rewrite** — переписать все 73 узла на простой язык (нетехническая аудитория).
3. **3-color conflicts** — классифицировать 8 конфликтов по 3 категориям (🔴 не закрыт правилом / ⚫ возник на практике / 🟢 закрыт правилом) с сортировкой 🔴 → ⚫ → 🟢.
4. **Edge legend (7 полей)** — на click по ребру открывать панель с детализацией.

**Не-цели:**

- Не менять radial-sector layout или координаты узлов.
- Не добавлять новые узлы (73 остаются как есть; 2 новых конфликтных ребра добавляются для категории ⚫).
- Не интегрировать с CI или workflow'ами — карта остаётся single-file static HTML.
- Не реализовывать i18n — UI остаётся русским.
- Не делать mobile-responsive — карта для desktop Edge browser Дмитрия.

## 2. Section 1: Resizable legend panel

### 2.1. Параметры

| Параметр | Значение |
|---|---|
| Min-width | 300px (текущий default) |
| Max-width | 900px (≈45% экрана при 1920px) |
| Default-width | 300px (первый запуск) |
| Restored from | `localStorage.getItem('liderra-map-legend-width')` |
| Cursor on handle hover | `col-resize` |
| Handle visual | 6px вертикальная полоска на левой границе panel |
| Handle bg | transparent default, `#0d4a5a` (solarized base01) on hover |

### 2.2. CSS

```css
#legend-panel { position: relative; /* для абсолютного позиционирования handle */ }
#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; }
```

### 2.3. JS-логика

```js
const STORAGE_KEY = 'liderra-map-legend-width';
const MIN_W = 300, MAX_W = 900;

function applyLegendWidth(w) {
  const clamped = Math.max(MIN_W, Math.min(MAX_W, w));
  document.getElementById('legend-panel').style.width = clamped + 'px';
  document.getElementById('legend-panel').style.minWidth = clamped + 'px';
  if (network) network.redraw();
}

function restoreLegendWidth() {
  const saved = parseInt(localStorage.getItem(STORAGE_KEY) || '300', 10);
  applyLegendWidth(saved);
}

// drag-handle:
const handle = document.getElementById('legend-handle');
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(STORAGE_KEY, w);
});

window.addEventListener('DOMContentLoaded', restoreLegendWidth);
```

### 2.4. Smoke acceptance

- Открыть карту → panel 300px (или ранее сохранённый).
- Hover на левый край panel → cursor col-resize, полоска подсвечивается.
- Drag → panel плавно меняет ширину; canvas пересчитывается.
- Drag за пределы [300, 900] → clamped.
- Reload страницы → panel восстанавливает сохранённую ширину.

## 3. Section 2: Simple-language text rewrite

### 3.1. Scope

Все 73 узла × 3 поля (`desc` / `when` / `limits`) + items `reportsTo`/`manages`/`together`/`conflicts` где есть жаргон. Структура функции `nd()` не меняется — только содержимое строк.

### 3.2. Style Guide

1. **Технический термин → понятное объяснение в скобках**:
   - «ENABLE RLS» → «ENABLE RLS (включение защиты строк по тенанту)»
   - «BYPASSRLS» → «BYPASSRLS (право обходить RLS — для системных задач)»
   - «PSR-стандарт» → «PSR-стандарт (стиль кода PHP)»
   - «matcher» → «фильтр путей»

2. **Параграф-ссылки сохраняются как примечания**:
   - «hard rule» → «обязательное правило (см. §12)»
   - «§5п.10» → «правило §5п.10 в нормативке»

3. **Длина**:
   - `desc`: 1-2 предложения, 50-100 символов
   - `when`: 1 предложение, 40-80 символов
   - `limits`: 1-2 предложения, 50-150 символов

4. **Жаргон-блэклист (заменяем)**:

   | Жаргон | Простой эквивалент |
   |---|---|
   | hard rule | обязательное правило |
   | matcher | фильтр |
   | pipeline | процедура |
   | override | отмена |
   | hook config | настройка хука |
   | stage_fixed:true | авто-сохранить исправленное |
   | pre-commit stage | перед каждым коммитом |
   | bypass | обход |
   | runtime crash | ошибка во время работы |
   | peerDep | внешняя зависимость |
   | guideline | правило-подсказка |

5. **Аббревиатуры расшифровываются при первом упоминании в узле**:
   - SAST → «SAST (статический анализ кода на уязвимости)»
   - CVE → «CVE (известная уязвимость с номером)»
   - SQLi/XSS → «SQLi (внедрение SQL) / XSS (внедрение JS)»
   - PDN/ПДн → «ПДн (персональные данные)»

### 3.3. Группы узлов для параллельного rewrite

| Группа | Узлы | Count |
|---|---|---|
| A: правила + плагины | rules (4) + plugins (5) | 9 |
| B: скилы + хуки | skills_sp (14) + skills_proj (2) + hooks (5) | 21 |
| C: агенты + MCP | agents (11) + mcp (7) | 18 |
| D: lefthook + memory | lefthook (10) + memory (15) | 25 |

Group sizes неравны намеренно — Group A содержит самые сложные тексты (правила = много ссылок на параграфы), Group D — самые простые (lefthook jobs + memory одинокие по структуре).

### 3.4. Audit-чеклист после rewrite

- Случайная выборка 10 узлов прочитана как нон-tech reader.
- Длина не превышает указанные лимиты (можно превысить только при веской причине — указать в комментарии).
- Параграф-ссылки сохранены (grep `§\d+` показывает ожидаемое количество).
- Жаргон-блэклист словарь: grep по каждому из 11 терминов → 0 матчей в NODE_DETAILS.

## 4. Section 3: 3-color conflict classification

### 4.1. Data structure

```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 },
};
```

### 4.2. 8 конфликтов после Q2 (классификация Дмитрия)

| # | Type | From ↔ To | Описание (rewrite на простой язык) | Источник классификации |
|---|---|---|---|---|
| 1 | 🔴 | sk_rls ↔ ag_rls | Оба покрывают RLS compliance, нет регламента кто когда | iter1 |
| 2 | 🔴 | hookify_plugin ↔ hk_pre_claude | hookify создаёт хуки динамически — может перезаписать существующий | iter1 |
| 3 | ⚫ | mcp_pw ↔ sk_parallel | «Browser is already in use» при параллельных сессиях (memory квирк #2) | memory + audit #3 evidence |
| 4 | ⚫ | ag_pest ↔ mcp_redis | Redis race в Pest --parallel subdir-only (квирк 72) | memory + audit #3 evidence |
| 5 | 🟢 | psr_v1 ↔ claude_md | Закрыто §5п.10 CLAUDE.md + хук CLAUDE.md-warn | iter1 |
| 6 | 🟢 | upm ↔ fd_plugin | PSR_v1 R14.5: не параллельно | iter1 |
| 7 | 🟢 | mcp_21st ↔ fd_plugin | PSR_v1 R14.5: не параллельно | iter1 |
| 8 | 🟢 | hk_economy ↔ superpowers | Pravila §12.4: §12 неотменяем | iter1 |

NB: новых рёбер-конфликтов 2 (⚫ #3 и ⚫ #4). Это означает 2 новых `CONFLICT(...)` записи в массиве `EDGES`.

### 4.3. Renderer changes

**На canvas (рёбра):**

- Расширить `CONFLICT(from, to, label, type='RED')` — последний параметр type
- Цвет ребра — из `CONFLICT_TYPES[type].color`
- Dashes сохраняются для всех 3 типов
- Bidirectional arrows сохраняются (двунаправленные стрелки = знак конфликта)

**В node legend panel `.conflict-item`:**

- Bg: `CONFLICT_TYPES[type].bg`
- Emoji prefix + цветной name: `CONFLICT_TYPES[type].emoji ' ' c.name` цветом `CONFLICT_TYPES[type].color`
- Описание (`.cdesc`) тем же серым `#cb9b96` → переработать на `#eee8d5` (читаемый ivory на любом фоне) — но это touch-up, не критичен

**Сортировка в node legend:**

```js
details.conflicts.sort((a, b) => CONFLICT_TYPES[a.type].rank - CONFLICT_TYPES[b.type].rank);
```

**Footer cat-legend:** заменить одиночный «— конфликт» бэйдж тремя:

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

### 4.4. Data migration

Все существующие записи `conflicts: [{ name, desc }]` в NODE_DETAILS получают `type` field. По дефолту — `RED` (старая семантика), затем перекласифицируется per таблицу выше.

```js
// Было:
{ name: 'rls-reviewer агент', desc: '...' }
// Стало:
{ name: 'rls-reviewer агент', desc: '...', type: 'RED' }
```

## 5. Section 4: Edge legend (7 полей)

### 5.1. Поля

| # | Поле | Тип | Source of truth | Пример |
|---|---|---|---|---|
| 1 | Источник запроса | derived from `from` node | NODES + GROUPS | "Superpowers (плагин)" |
| 2 | Конечный получатель | derived from `to` node | NODES + GROUPS | "TDD (скил)" |
| 3 | Тип связи | enum | EDGE_DETAILS | содержит |
| 4 | Когда срабатывает | строка | EDGE_DETAILS | при новом production коде |
| 5 | Что передаёт | enum | EDGE_DETAILS | контроль |
| 6 | Обязательность | enum | EDGE_DETAILS | обязательно |
| 7 | Регламент | строка | EDGE_DETAILS | Pravila §12.2 |

### 5.2. Enum значения

**Тип связи:**

- `содержит` — родитель → child (Superpowers → sk_tdd)
- `подчиняет` — иерархия (Pravila → CLAUDE.md)
- `координирует` — PSR_v1 → парный стек
- `читает` — hook → memory file
- `запускает` — skill → agent / agent → MCP
- `документирует` — CLAUDE.md → MCP («описывает §3.2»)
- `триггерит` — реактивная связь (PostToolUse → markdownlint)
- `альтернатива` — wplans → eplans / wplans → subagent-driven
- `конфликт` — для всех 8 рёбер из Section 3

**Что передаёт:**

- `контроль` — кто-то управляет процессом другого
- `данные` — SQL запрос, файлы, артефакты
- `проверка` — verification / audit
- `триггер` — событие запускает
- `документация` — описание / ссылка
- `coverage` — функциональное покрытие

**Обязательность:**

- `обязательно` — без этой связи правило нарушается
- `опционально` — связь может не использоваться
- `hard-block` — нарушение блокирует выполнение (gitleaks fail → блок коммита)
- `рекомендуется` — best practice, но допустимо не использовать

### 5.3. Data structure

```js
const EDGE_DETAILS = {
  'superpowers->sk_tdd': {
    type: 'содержит',
    when: 'когда нужен скил TDD — он часть плагина Superpowers',
    transfers: 'контроль',
    mandatory: 'обязательно',
    rule: 'Pravila §12.2 (карта обязательных скилов)',
  },
  // ...для каждого из 72 рёбер
};
const edgeKey = (from, to) => `${from}->${to}`;
```

Все 72 ребра должны иметь запись. Если ребра нет в EDGE_DETAILS — fallback: показать только поля 1-2 (источник + получатель) + warning «Регламент не задокументирован».

### 5.4. UI: переключение panel между node/edge режимами

Общая panel `#legend-panel` отображает один из 2 layouts:

**Node режим (existing):** title + category + desc / when / limits / reportsTo / manages / together / conflicts.

**Edge режим (новый):** title (с цветом ребра) + 7 секций.

Реализация: 2 контейнера `#legend-node-content` и `#legend-edge-content` — один visible, другой hidden.

### 5.5. Click handler

```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 closeLegend();
});
```

vis.js передаёт `params.edges[0]` = edge id (auto-сгенерированный, типа `edgeId123`), не from/to. Нужно map'ить edge id → from/to через `edgesDS.get(edgeId)`:

```js
function showEdgeLegend(edgeId) {
  const edge = edgesDS.get(edgeId);
  const details = EDGE_DETAILS[edgeKey(edge.from, edge.to)] || null;
  // render...
}
```

## 6. Section 5: Parallel execution strategy

### 6.1. Принцип

Выполнение разделено на 3 phase. Phase 1 — sequential foundation. Phase 2 — 6 параллельных subagent'ов (text rewrite × 4 + 2 foundation tasks). Phase 3 — sequential integration + verification.

Поскольку все правки в одном файле `docs/automation-graph.html`, истинная параллель через git worktrees была бы рискована для merge. Безопасный подход: **subagent'ы возвращают raw JS-блоки**, главный агент применяет Edit'ы поочерёдно из единого контекста (один файл → один edit за раз, но все блоки готовы одновременно).

### 6.2. Зависимости и изоляция

| Задача | Меняет в файле | Зависит от |
|---|---|---|
| S1: Resize | CSS top + JS bottom (new section 7) | — |
| S2: Text rewrite | NODE_DETAILS desc/when/limits values | — |
| S3: Conflict classification | CONFLICT() helper + conflicts items type field + footer cat-legend | — |
| S4: Edge legend data | New EDGE_DETAILS object + new showEdgeLegend func | — |
| S5: Edge legend render | NEW HTML markup for `#legend-edge-content` + click handler refactor | S4 |
| S6: Conflict render | conflict-item template + sort + edge color logic | S3 |

S1, S2, S3, S4 — **полностью независимы**: разные секции файла, разные данные.
S5 зависит от S4. S6 зависит от S3.

### 6.3. Phase 2: 6 параллельных subagent'ов

Через `superpowers:dispatching-parallel-agents`. Каждый subagent получает:

- Полный текст спека (этот файл).
- Свой раздел задачи (S1/S2-A/S2-B/S2-C/S2-D/S3-data/S4-data).
- Требование вернуть **raw JS-блок** (не Edit, не Bash) для применения главным агентом.
- Запрет на Edit/Write на любые файлы.

| Subagent | Задача | Output |
|---|---|---|
| P1 | S1 Resize: CSS + JS блок | 2 текстовых блока (CSS добавка + JS section) |
| P2 | S2 text rewrite group A (rules + plugins, 9 узлов) | 9 nd(...) объектов |
| P3 | S2 text rewrite group B (skills + hooks, 21 узлов) | 21 nd(...) объектов |
| P4 | S2 text rewrite group C (agents + MCP, 18 узлов) | 18 nd(...) объектов |
| P5 | S2 text rewrite group D (lefthook + memory, 25 узлов) | 25 nd(...) объектов |
| P6 | S3 + S4 data: CONFLICT_TYPES enum + 8 classified conflict items + EDGE_DETAILS (72 entries) | 3 текстовых блока |

NB: S3 и S4 data объединены в один subagent (P6), потому что оба связаны с описанием рёбер и работа над ними логически связана.

### 6.4. Phase 3: Sequential integration

Главный агент применяет Edit'ы:

1. Edit 1: добавить CSS для drag-handle (S1 part 1)
2. Edit 2: добавить JS для drag-handle (S1 part 2)
3. Edit 3: NODE_DETAILS rewrite group A
4. Edit 4: NODE_DETAILS rewrite group B
5. Edit 5: NODE_DETAILS rewrite group C
6. Edit 6: NODE_DETAILS rewrite group D
7. Edit 7: CONFLICT_TYPES enum + расширить CONFLICT() helper + добавить 2 новых ⚫ edges (S3 data)
8. Edit 8: добавить EDGE_DETAILS объект (S4 data)
9. Edit 9: HTML markup для `#legend-edge-content` + переключение panel (S5)
10. Edit 10: showEdgeLegend + click handler refactor (S5)
11. Edit 11: conflict-item template + sort + edge color logic (S6)
12. Edit 12: footer cat-legend (3 бэйджа)

Каждый Edit → коммит. Атомарность: один логический change → один коммит.

### 6.5. План коммитов (11 atomic)

| # | Commit | Содержание | Phase |
|---|---|---|---|
| 1 | `docs(spec)` | iter2 design (этот файл) | 0 |
| 2 | `docs(plan)` | iter2 plan | 0 |
| 3 | `feat(graph)` | drag-handle + localStorage (S1, Edit 1-2) | 3 |
| 4 | `chore(graph)` | text rewrite group A (S2-A, Edit 3) | 3 |
| 5 | `chore(graph)` | text rewrite group B (S2-B, Edit 4) | 3 |
| 6 | `chore(graph)` | text rewrite group C (S2-C, Edit 5) | 3 |
| 7 | `chore(graph)` | text rewrite group D (S2-D, Edit 6) | 3 |
| 8 | `feat(graph)` | 3-color conflicts data + 2 new ⚫ edges (S3 data, Edit 7) | 3 |
| 9 | `feat(graph)` | EDGE_DETAILS data (S4 data, Edit 8) | 3 |
| 10 | `feat(graph)` | edge legend render + click handler (S5, Edit 9-10) | 3 |
| 11 | `feat(graph)` | 3-color conflicts render + sort + footer (S6, Edit 11-12) | 3 |

Pre-commit lefthook gauntlet (gitleaks/markdownlint/cspell/...) после каждого. Pre-push gauntlet (gitleaks full-history + lychee) перед push'ом всей серии.

## 7. Verification

### 7.1. Per-commit

- `lefthook run pre-commit` после каждого Edit.
- Full output показывается в ответе, не сокращается.

### 7.2. Pre-push

- `lefthook run pre-push` (gitleaks full-history + lychee).
- Full output.

### 7.3. Visual smoke (manual, Дмитрий)

После push — Дмитрий открывает `docs/automation-graph.html` в Edge и проверяет:

| Аспект | Что смотреть |
|---|---|
| Resize | Drag левого края panel, восстановление ширины после reload |
| Text | Случайные 10 узлов: понятно ли нон-tech reader'у |
| Conflicts | 3 цвета на canvas рёбра, footer-бэйджи, сортировка в node legend |
| Edge legend | Click на 5 разных рёбер: все 7 полей заполнены |
| Regression | Поиск работает, фиксация/расшевелить/сброс кнопки работают, hover на ребро показывает tooltip |

### 7.4. Acceptance criteria

- ✅ Resize handle присутствует, drag работает, localStorage сохраняет ширину.
- ✅ 73 узла переписаны на простой язык по Style Guide.
- ✅ 8 конфликтов классифицированы по 3 цветам, отсортированы 🔴→⚫→🟢 в node legend и footer.
- ✅ Click по 72 рёбрам открывает edge legend с 7 полями.
- ✅ Lefthook pre-commit + pre-push pass на каждом коммите.
- ✅ Дмитрий подтверждает visual smoke.

## 8. Open questions

Открытых вопросов нет — все 4 clarifying-вопроса разрешены в brainstorming-цикле.

## 9. Risks

| Risk | Likelihood | Mitigation |
|---|---|---|
| Subagent возвращает Markdown вместо JS-блока | Medium | В prompt явно: "raw JS code, no Markdown fences, no commentary outside code block" |
| Edge legend click ловит drag на canvas | Low | vis.js handles это сам — click vs drag distingue by movement threshold |
| localStorage заблокирован (private mode) | Low | Fallback на in-memory; не критично |
| Vis.js redraw слишком частый при drag | Low | rAF throttle в mousemove handler; если визуально не лагает — оставить как есть |
| Файл `docs/automation-graph.html` превысит 25k tokens после расширения EDGE_DETAILS | Medium | EDGE_DETAILS добавляет ~5-7k токенов; читать через offset/limit при следующих правках |
| Параллельные subagent'ы пишут несовместимые форматы (e.g., разные кавычки) | Low | Спека жёстко фиксирует Style Guide; главный агент валидирует output перед Edit |

## 10. Termination conditions

Iter2 закрыта когда:

1. Все 11 atomic commits применены и pushed на `origin/main`.
2. Lefthook pre-push pass (gitleaks 0 leaks + lychee 0 errors).
3. Дмитрий проверил visual smoke в Edge browser и подтвердил «принято».
4. Memory `project_automation_map.md` обновлён под iter2 (новый HEAD commit, обновлённые цифры узлов/конфликтов/функций).

---

**Author signature:** Claude (Opus 4.7) через `superpowers:brainstorming` skill, economy 0% mode.
**Next step:** spec self-review → user approves spec → invoke `superpowers:writing-plans` skill.
