Files
portal/docs/superpowers/specs/2026-05-16-automation-graph-iter6-node-meta-design.md
T
Дмитрий e3c3f523a7 docs(spec): automation-graph iter6 — dates + usage + duplicates design
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:32:35 +03:00

26 KiB
Raw Blame History

Карта автоматизации — iter6: даты, счётчик использования, дубли узлов

Дата: 2026-05-16 Артефакт: docs/automation-graph.html (vis.js Network 9.1.9, single-file HTML) Тип: дизайн-спецификация (brainstorming → этот документ → writing-plans) Предыдущая итерация: iter5 — фактический реколлаж блока ruflo (docs/superpowers/specs/2026-05-15-automation-graph-iter5-ruflo-factual-design.md)


1. Контекст и цель

Заказчик (Дмитрий) запросил три добавления к карте:

  1. В легенду каждого узла — раздел с датой внедрения и датой последнего изменения.
  2. В легенду узла — количество вызовов узла за последние 7 дней + кнопка подсветки.
  3. Анализ узлов, дублирующих друг друга на ≥80% + кнопка подсветки дублей внизу легенды.

Сквозное требование (как в iter5): данные фактические, из рантайма и истории, а не из нормативной документации. Счётчик использования при отсутствии настоящей телеметрии нельзя выдумывать — он берётся из реальных логов либо честно помечается «нет данных».

Терминология карты. «Легенда узла» = правая панель #legend-node-content (строки 94–107, рендер showNodeLegend строки 1386–1440), 8 секций. «Низ легенды» = нижняя панель #cat-legend (строки 123–137) — 13 кликабельных бейджей-фильтров категорий.


2. Подход

Выбран Вариант A — статичные данные, вшитые в HTML. Новые блоки-константы в <script>; значения генерируются на этапе реализации из git, changelog/memory и разбора транскриптов сессий, затем вшиваются в файл. Рендер легенды и кнопки читают эти константы.

Отклонённые варианты (для протокола):

  • B — sidecar *.json + fetch(): карта открывается как локальный файл (file://), браузеры блокируют fetch() локальных файлов (CORS) — карта перестанет работать при открытии двойным кликом. Это зона квирка #90.
  • C — живой подсчёт в браузере: невозможно — у браузера нет доступа к транскриптам сессий и git.

Снимок-природа данных раскрывается явно: легенда пишет «срез 09–16.05.2026». Карта уже ручной артефакт — поля when/limits правятся вручную каждую итерацию; новые поля встают в тот же режим.


3. Модель данных

Две новые константы + два константа-параметра. Размещение — новая SECTION 3.6 между SECTION 3.5 (EDGE_DETAILS, заканчивается строкой 1328) и SECTION 4 (VIS INIT, строка 1330).

3.1. Параметры снимка

const META_SNAPSHOT = '16.05.2026';        // дата генерации значений
const META_WINDOW   = '0916.05.2026';     // окно подсчёта использования (7 дней)

3.2. NODE_META — метаданные по каждому узлу

Ключ = id узла. Покрытие — все 83 узла (включая mem_ruflo).

const NODE_META = {
  pravila: { since: '…', changed: '…', uses: null, usesSrc: '—' },
  // … 83 записи
};
поле тип смысл
since string дата внедрения 'DD.MM.YYYY', либо '—', либо короткая фраза ('встроено в Claude Code')
changed string дата последнего изменения 'DD.MM.YYYY' либо '—'
uses number | null вызовов за 7 дней. 0 = измеримый узел, реально простаивал. null = измерить нельзя (узел-правило) → в легенде «нет данных», не «0»
usesSrc string короткая метка источника числа: 'скил' / 'агент' / 'MCP' / 'хук' / 'memory-чтение' / 'коммиты' / 'инспекция' / '—'
dupNote string (опц.) только для 3 ruflo-узлов из ролевой избыточности (R-группы §6.2); короткая заметка о дублировании роли

Поле dupNote присутствует только там, где нужно (необязательное). Парные дубли (D-группы) в NODE_META не дублируются — они выводятся через DUPLICATE_GROUPS.

3.3. DUPLICATE_GROUPS — явные парные дубли

const DUPLICATE_GROUPS = [
  { id: 'D1', pct: 90, members: ['hk_post_md', 'lh_mdlint'],
    reason: 'оба запускают markdownlint --fix на .md — разница только в триггере (после правки в сессии против при коммите)' },
  { id: 'D2', pct: 85, members: ['lh_gitleaks', 'lh_gitleaks2'],
    reason: 'оба — скан секретов gitleaks; pre-commit проверяет staged-файлы, pre-push — всю историю (надмножество)' },
  { id: 'D3', pct: 85, members: ['sk_rls', 'ag_rls'],
    reason: 'оба — 7-пунктовая проверка RLS-соответствия; уже отмечено 🔴-конфликтом «нет регламента кто когда»' },
  { id: 'D4', pct: 80, members: ['sk_eplans', 'sk_subagent'],
    reason: 'оба исполняют план writing-plans пошагово; разница — параллельная сессия против суб-агентов в текущей' },
  { id: 'D5', pct: 80, members: ['sk_wplans', 'ag_plan'],
    reason: 'оба производят план задачи; скил writing-plans — inline с полным кодом, агент Plan — архитектурный набросок READ-ONLY' },
  { id: 'D7', pct: 80, members: ['ruflo_recall_hook', 'hk_session'],
    reason: 'оба инжектят память в контекст Клода; recall-хук — на каждый промпт из базы ruflo, SessionStart — при старте из .md-файлов' },
];

Нумерация (D1D5, D7) сохранена из обсуждения с заказчиком: D6 в обсуждении отнесён к ролевой избыточности ruflo (§6.2), поэтому в массиве парных дублей его нет — это намеренный пропуск, а не ошибка.

Производный индекс для рендера и подсветки строится один раз при инициализации:

const DUP_BY_NODE = new Map(); // nodeId -> { partner, pct, reason }
DUPLICATE_GROUPS.forEach(g => g.members.forEach(m => {
  const partner = g.members.find(x => x !== m);
  DUP_BY_NODE.set(m, { partner, pct: g.pct, reason: g.reason });
}));
const DUP_NODE_SET = new Set(DUP_BY_NODE.keys()); // все узлы-члены парных дублей

3.4. Что НЕ меняется (scope-guard)

Чисто аддитивная итерация. Не трогаются: NODES (83), EDGES (90), CONFLICT_TYPES, builder nd(), все 83 записи NODE_DETAILS, EDGE_DETAILS (86 ключей), радиально-секторный layout, координаты ruflo-кластера, GROUPS. Метрики карты остаются 83 узла / 90 рёбер / 11 конфликтов. Добавляется только: 2 константы данных, 1 секция DOM в легенде узла, 2 кнопки в футере, режим viewMode в IIFE HIGHLIGHT, минимальный CSS.


4. Фича 1+2 — секция «Паспорт узла» в легенде

Запросы 1 и 2 объединяются в одну новую секцию легенды узла (легенда уже из 8 секций; две раздельные сделали бы панель чрезмерно длинной). Секция ставится сразу под бейджем категории (#legend-category, строка 96), до «Что делает».

4.1. DOM (вставка в #legend-node-content)

<div class="legend-section" id="passport-section">
  <h4>📇 Паспорт узла</h4>
  <p><span class="pp-k">Внедрён:</span> <span id="ld-since"></span></p>
  <p><span class="pp-k">Последнее изменение:</span> <span id="ld-changed"></span></p>
  <p><span class="pp-k">Использований за 7 дней:</span> <span id="ld-uses"></span></p>
  <p id="ld-dup-row" style="display:none;"><span class="pp-k">Дубль:</span> <span id="ld-dup"></span></p>
</div>

4.2. Рендер (дополнение showNodeLegend)

После заполнения категории:

const meta = NODE_META[nodeId] || { since: '—', changed: '—', uses: null, usesSrc: '—' };
document.getElementById('ld-since').textContent   = meta.since || '—';
document.getElementById('ld-changed').textContent = meta.changed || '—';

const usesEl = document.getElementById('ld-uses');
if (meta.uses === null || meta.uses === undefined) {
  usesEl.textContent = 'нет данных (узел не вызывается напрямую)';
  usesEl.style.color = '#586e75';
} else {
  usesEl.innerHTML = `<b>${meta.uses}</b> <span style="color:#839496;">(${meta.usesSrc}) · срез ${META_WINDOW}</span>`;
  usesEl.style.color = '';
}

// строка «Дубль» — парный дубль (DUPLICATE_GROUPS) либо ролевая заметка ruflo (dupNote)
const dupRow = document.getElementById('ld-dup-row');
const dupEl  = document.getElementById('ld-dup');
const pair   = DUP_BY_NODE.get(nodeId);
if (pair) {
  const partnerNode = NODES.find(n => n.id === pair.partner);
  const partnerLabel = partnerNode ? partnerNode.label.replace(/\n/g, ' ') : pair.partner;
  dupEl.innerHTML = `⧉ <b>${partnerLabel}</b> (~${pair.pct}%) — ${pair.reason}`;
  dupRow.style.display = '';
} else if (meta.dupNote) {
  dupEl.innerHTML = `⧉ ${meta.dupNote}`;
  dupRow.style.display = '';
} else {
  dupRow.style.display = 'none';
}

4.3. CSS

.legend-section p .pp-k { color: #839496; }
#passport-section p { font-size: 12px; color: #eee8d5; line-height: 1.6; }

5. Методика фактических чисел

Значения since / changed / uses производятся на этапе реализации (writing-plans). Спецификация фиксирует метод, чтобы числа были воспроизводимы и честны.

5.1. uses — вызовов за 7 дней (окно 0916.05.2026)

Источник — транскрипты сессий Claude Code C:\Users\Administrator\.claude\projects\c---------------------crm-------------\*.jsonl (≈37 файлов в окне) и git-история.

категория узлов как считается usesSrc
скилы Superpowers (14) + проекта (2) число вызовов Skill с соответствующим значением skill скил
агенты (11) число вызовов Agent с соответствующим subagent_type агент
MCP-серверы (7) число вызовов инструментов mcp__<сервер>__* MCP
memory-файлы (16) число вызовов Read по пути этого файла memory-чтение
ruflo MCP вызовы mcp__ruflo__* MCP
ruflo Queen / workers / daemon / memory / commands / agents_catalog / plugins логи демона ruflo + прямая инспекция рантайма (как в iter5) инспекция
хук hk_session число сессий в окне (≈37) хук
хук hk_economy число промптов с тегом экономия N% в транскриптах хук
хуки hk_pre_claude / hk_post_md / hk_post_schema производно: правки CLAUDE.md / *.md / db/schema.sql в окне; если надёжно не вывести — uses: null хук /
lefthook jobs (10) ≈ число коммитов за 7 дней (каждый коммит гонит набор pre-commit; pre-push реже) коммиты
узлы-правила (4: pravila, claude_md, psr_v1, tooling) напрямую не вызываются uses: null
плагины-обёртки (superpowers, fd_plugin, upm, hookify_plugin) сам плагин не вызывается — работают через скилы; uses: null, кроме claude_md_mgmt (считается как скил) null / скил

Различие 0 против null обязательно: 0 ставится только узлу, чьё использование измеримо и было нулевым (например, idle-воркеры ruflo); null — узлу, чьё использование измерить нельзя.

5.2. since / changed — даты

класс узла since (внедрён) changed (последнее изменение)
внедрённые в проект (хуки, lefthook jobs, memory-файлы, проектные скилы/агенты, MCP-серверы, ruflo-узлы, документы-правила, claude-md-mgmt) первый git-коммит / запись в changelog / Tooling, вводящие узел последний git-коммит, тронувший файл/конфиг узла; для документов-правил — дата последнего бампа версии
пришедшие со связкой инструментов (12 скилов Superpowers, встроенные агенты Explore/Plan/general-purpose/guide/statusline + plugin-dev/hookify-агенты, плагины Superpowers/FD/UPM/hookify) дата подключения связки к проекту (установка Superpowers / FD plugin) последний проектно-значимый бамп версии связки либо '—'
без проектного события '—' (можно с фразой 'встроено в Claude Code') '—'

Все даты — из реальной истории (git / changelog / memory), не выдуманные. Узлы без надёжного источника получают честное '—'.


6. Фича 3 — анализ дублей

Критерий заказчика: узлы, явно дублирующие друг друга на ≥80% по функции.

6.1. Явные парные дубли — D-группы (попадают в кнопку подсветки)

# пара % обоснование
D1 hk_post_mdlh_mdlint ~90 оба — markdownlint --fix на .md; разница только в триггере (PostToolUse в сессии / lefthook при коммите). Карта уже подписывает ребро «дублируют задачу»
D2 lh_gitleakslh_gitleaks2 ~85 оба — скан секретов gitleaks; pre-commit (staged) против pre-push (вся история). pre-push — функциональное надмножество
D3 sk_rlsag_rls ~85 оба — 7-пунктовая проверка RLS-соответствия. Уже нарисован 🔴-конфликт «нет регламента кто когда»
D4 sk_eplanssk_subagent ~80 оба исполняют план writing-plans пошагово; разница — параллельная сессия против суб-агентов в текущей. Уже есть ребро «альтернатива»
D5 sk_wplansag_plan ~80 оба производят план задачи; скил — inline с полным кодом, агент Plan — архитектурный набросок READ-ONLY. NODE_DETAILS уже отмечает «решают похожую задачу в разных контекстах»
D7 ruflo_recall_hookhk_session ~80 оба инжектят память в контекст Клода; recall-хук — на каждый промпт из базы ruflo, SessionStart — при старте из .md-файлов

Итого 6 пар, 12 узлов.

6.2. Ролевая избыточность ruflo — R-группы (только заметка, НЕ в кнопке)

Это настоящее дублирование, но один узел дублирует роль целой подсистемы — подсветка раздула бы граф до ~40 узлов и потеряла читаемость. Решение заказчика: показать заметкой dupNote в легенде трёх ruflo-узлов, в кнопку не включать.

# узел дублирует роль заметка dupNote
D6 ruflo_memory 16 проектных memory-файлов «дублирует роль 16 memory-файлов проекта — постоянная память между сессиями; уже -конфликт с project_state»
D8 ruflo_commands 14 скилов Superpowers «88 slash-команд дублируют роль скилов — именованные вызываемые процедуры; команды инертны»
D9 ruflo_agents_catalog группа из 11 агентов «100 определений агентов дублируют реестр агентов; каталог буквально содержит 2 проектных агента»

(D6 здесь — отсюда пропуск в нумерации D-групп §6.1.)

6.3. Рассмотрено и отклонено

  • ag_explore против ag_general — специализация, не дубль (~70%): Explore — дешёвый поиск только на чтение, general-purpose — дорогой многошаговый. Карта намеренно их разводит.
  • fd_plugin / upm / mcp_21st — пересекаются по домену (UI), но роли различны (решатель / материал-библиотека / генератор шаблонов). Уже 🟢-конфликты по R14.5. Не функциональный дубль.
  • claude_md против tooling — оба «оперативная карта уровня 2», но содержание различно (карта проекта против реестра инструментов). ~50%.

7. Две кнопки

Обе — в нижнюю панель #cat-legend, отдельным мини-рядом после 13 бэйджей категорий, со стилем toggle-кнопок (.cat-ctl).

7.1. DOM (дополнение #cat-legend)

<span class="cat-ctl-sep"></span>
<button class="cat-ctl" id="cat-ctl-heat" title="Подсветить узлы по числу вызовов за 7 дней">🔥 По использованию</button>
<button class="cat-ctl" id="cat-ctl-dup"  title="Подсветить явные пары дублей (D1–D5, D7)">⧉ Дубли</button>

7.2. CSS

.cat-ctl-sep { width: 1px; align-self: stretch; background: #586e75; margin: 0 4px; }
.cat-ctl {
  background: #002b36; border: 1px solid #586e75; color: #93a1a1;
  border-radius: 4px; padding: 2px 8px; font-size: 11px; cursor: pointer;
  transition: background 0.12s, box-shadow 0.12s; user-select: none;
}
.cat-ctl:hover { background: #0d4a5a; color: #fdf6e3; }
.cat-ctl.active {
  background: rgba(253,246,227,0.12);
  box-shadow: inset 0 0 0 1px rgba(253,246,227,0.4);
  color: #fdf6e3;
}

7.3. Поведение — режим viewMode в IIFE HIGHLIGHT

В state (строки 1643–1646) добавляется viewMode: null со значениями null / 'heat' / 'dup'. Режимы взаимоисключающие: включение одного выключает другой и сбрасывает legendFilter + selectedNode (режим — отдельная глобальная картина, не комбинируется с пофильтровой подсветкой).

computeNodeOpacity получает приоритетную ветку в начале:

if (state.viewMode === 'heat') return heatOpacity(nodeId);
if (state.viewMode === 'dup')  return DUP_NODE_SET.has(nodeId) ? OPACITY_FOCUS : OPACITY_DIM;

computeEdgeOpacity в режимах остаётся на Math.min(fromO, toO) — без conflict-boost.

Теплокарта heatOpacity — 4 яруса по NODE_META[id].uses; пороги уточняются в плане по фактическим значениям, ориентир:

ярус условие opacity
часто uses ≥ 21 1.0
иногда 6 ≤ uses ≤ 20 0.65
редко 1 ≤ uses ≤ 5 0.35
простаивает uses === 0 0.12
нет данных uses === null 0.5 (нейтрально)

Узлы верхнего яруса дополнительно получают borderWidth: 4 для акцента (сбрасывается при выходе из режима).

Кнопки подключаются в существующий делегат #cat-legend click (строки 1753–1759): если клик по .cat-ctl — переключить viewMode, иначе — прежняя логика .cat-item.

clearAll (строки 1738–1741) дополняется сбросом viewMode = null; кнопка «✕ Снять выделение» уже зовёт clearAll — отдельная правка не нужна. updateLegendVisuals дополняется снятием/постановкой класса .active на двух .cat-ctl. При выходе из heat-режима borderWidth всех узлов возвращается к 2.


8. Честность и устаревание

  • Секция «Использований за 7 дней» в каждой легенде явно несёт «срез 09–16.05.2026».
  • uses: null рендерится как «нет данных», не как «0».
  • Числа — снимок на дату генерации; статичный HTML их не обновляет. Это согласовано с заказчиком (Вариант A) и соответствует природе карты (поля when/limits так же правятся вручную каждую итерацию).
  • Memory-файл project_automation_map.md фиксирует iter6 и дату снимка, чтобы при следующих итерациях было видно, что данные требуют обновления.

9. Критерии приёмки

  1. Открытие docs/automation-graph.html двойным кликом: 83 узла / 90 рёбер рендерятся, 0 JS-ошибок (кроме внешнего favicon-404).
  2. Клик по любому из 83 узлов: секция «📇 Паспорт узла» показывает Внедрён / Последнее изменение / Использований за 7 дней; узлы из D-групп дополнительно показывают строку «Дубль» с партнёром; 3 ruflo-узла из R-групп — строку «Дубль» с заметкой dupNote.
  3. Узлы-правила и плагины-обёртки показывают «нет данных», а не «0».
  4. Кнопка «🔥 По использованию»: включается/выключается, узлы перекрашиваются по 4 ярусам прозрачности; повторный клик возвращает.
  5. Кнопка «⧉ Дубли»: подсвечивает ровно 12 узлов из D1–D5, D7; остальное гаснет.
  6. Кнопки взаимоисключающие; «✕ Снять выделение» сбрасывает оба режима и borderWidth.
  7. NODE_META покрывает все 83 id; нет узла без записи.
  8. Существующая интерактивная подсветка (iter3), edge-легенда, resize-панель, поиск — без регрессий.

10. Самопроверка спецификации

  • Заглушек/TODO нет: методика дат и чисел задана явно (§5), значения производятся в плане.
  • Внутренняя согласованность: нумерация дублей D1D5, D7 (пары) + D6, D8, D9 (роли) — пропуски объяснены в §3.3 и §6.2.
  • Объём: одна когезивная итерация одного файла, аддитивная; декомпозиция не нужна.
  • Неоднозначности сняты: «легенда узла» = правая панель, «низ легенды» = футер #cat-legend (§1); 0 против null (§3.2, §5.1); набор кнопки «Дубли» = только D-группы (§6.1, решение заказчика).