Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
26 KiB
Карта автоматизации — 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. Контекст и цель
Заказчик (Дмитрий) запросил три добавления к карте:
- В легенду каждого узла — раздел с датой внедрения и датой последнего изменения.
- В легенду узла — количество вызовов узла за последние 7 дней + кнопка подсветки.
- Анализ узлов, дублирующих друг друга на ≥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 = '09–16.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-файлов' },
];
Нумерация (D1–D5, 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 дней (окно 09–16.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_md ⇄ lh_mdlint |
~90 | оба — markdownlint --fix на .md; разница только в триггере (PostToolUse в сессии / lefthook при коммите). Карта уже подписывает ребро «дублируют задачу» |
| D2 | lh_gitleaks ⇄ lh_gitleaks2 |
~85 | оба — скан секретов gitleaks; pre-commit (staged) против pre-push (вся история). pre-push — функциональное надмножество |
| D3 | sk_rls ⇄ ag_rls |
~85 | оба — 7-пунктовая проверка RLS-соответствия. Уже нарисован 🔴-конфликт «нет регламента кто когда» |
| D4 | sk_eplans ⇄ sk_subagent |
~80 | оба исполняют план writing-plans пошагово; разница — параллельная сессия против суб-агентов в текущей. Уже есть ребро «альтернатива» |
| D5 | sk_wplans ⇄ ag_plan |
~80 | оба производят план задачи; скил — inline с полным кодом, агент Plan — архитектурный набросок READ-ONLY. NODE_DETAILS уже отмечает «решают похожую задачу в разных контекстах» |
| D7 | ruflo_recall_hook ⇄ hk_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. Критерии приёмки
- Открытие
docs/automation-graph.htmlдвойным кликом: 83 узла / 90 рёбер рендерятся, 0 JS-ошибок (кроме внешнего favicon-404). - Клик по любому из 83 узлов: секция «📇 Паспорт узла» показывает
Внедрён/Последнее изменение/Использований за 7 дней; узлы из D-групп дополнительно показывают строку «Дубль» с партнёром; 3 ruflo-узла из R-групп — строку «Дубль» с заметкойdupNote. - Узлы-правила и плагины-обёртки показывают «нет данных», а не «0».
- Кнопка «🔥 По использованию»: включается/выключается, узлы перекрашиваются по 4 ярусам прозрачности; повторный клик возвращает.
- Кнопка «⧉ Дубли»: подсвечивает ровно 12 узлов из D1–D5, D7; остальное гаснет.
- Кнопки взаимоисключающие; «✕ Снять выделение» сбрасывает оба режима и
borderWidth. NODE_METAпокрывает все 83 id; нет узла без записи.- Существующая интерактивная подсветка (iter3), edge-легенда, resize-панель, поиск — без регрессий.
10. Самопроверка спецификации
- Заглушек/TODO нет: методика дат и чисел задана явно (§5), значения производятся в плане.
- Внутренняя согласованность: нумерация дублей
D1–D5, D7(пары) +D6, D8, D9(роли) — пропуски объяснены в §3.3 и §6.2. - Объём: одна когезивная итерация одного файла, аддитивная; декомпозиция не нужна.
- Неоднозначности сняты: «легенда узла» = правая панель, «низ легенды» = футер
#cat-legend(§1);0противnull(§3.2, §5.1); набор кнопки «Дубли» = только D-группы (§6.1, решение заказчика).