Files
portal/docs/superpowers/plans/2026-05-14-automation-graph-iter2.md
T
Дмитрий abaeebbde6 docs(plan): automation-graph iter2 — 10 atomic tasks (7 parallel-safe + 3 sequential)
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>
2026-05-14 12:12:33 +03:00

48 KiB
Raw Blame History

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-handle styles

Использовать 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 type argument

Заменить 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 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
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.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:

'<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-panel content в 2 containers #legend-node-content и #legend-edge-content)
    • SCRIPT section 5 (function showEdgeLegend + refactor showLegendshowNodeLegend + 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">...) и заменить на:

  <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 showEdgeLegend function in SECTION 5

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

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.


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

    .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

  • showLegendshowNodeLegend — переименование явное в 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.

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