Files
portal/docs/superpowers/plans/2026-05-16-automation-graph-iter6-node-meta.md
T
Дмитрий 14d7235ba9 docs(plan): automation-graph iter6 — node meta + duplicates implementation plan
4-task plan for iter6 of docs/automation-graph.html: «Паспорт узла»
legend section (since/changed/uses) for all 83 nodes + 2 footer toggle
buttons (usage heatmap, duplicate highlight). NODE_META (83 records) and
DUPLICATE_GROUPS (6 pairs D1-D5/D7) carry factual values derived from
76 session transcripts (window 09-16.05.2026) + git history; method and
raw outputs in Appendix A. cspell-words.txt += pcreator, pvalid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:11:57 +03:00

60 KiB
Raw Blame History

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

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 (iter6) секцию «Паспорт узла» (дата внедрения, дата последнего изменения, число вызовов за 7 дней) в легенду каждого из 83 узлов и две toggle-кнопки в футере — теплокарта использования и подсветка явных дублей.

Architecture: чисто аддитивная итерация одного single-file HTML. Новый блок данных SECTION 3.6 (две константы-снимка + NODE_META на 83 записи + DUPLICATE_GROUPS + производные индексы) — между SECTION 3.5 и SECTION 4. Рендер легенды дополняется секцией «📇 Паспорт узла»; в IIFE HIGHLIGHT добавляется взаимоисключающий режим viewMode (heat / dup); в футер #cat-legend — две кнопки .cat-ctl. NODES (83), EDGES (90), CONFLICT_TYPES, builder nd(), все 83 записи NODE_DETAILS, EDGE_DETAILS (86 ключей) — не трогаются. Метрики карты остаются 83 узла / 90 рёбер / 11 конфликтов.

Tech Stack: vis.js Network 9.1.9 (CDN), ванильный JS, Solarized Dark, без шага сборки. Карта — артефакт без отдельного фреймворка unit-тестов; верификация — браузерный smoke через Playwright MCP по паттерну iter3/iter4/iter5 (browser_evaluate-ассерты + проверка консоли). Источник истины дизайна — docs/superpowers/specs/2026-05-16-automation-graph-iter6-node-meta-design.md (коммит 00937b7).


Структура файла

Единственный изменяемый файл — docs/automation-graph.html (1775 строк до iter6). Пять точек вставки/правки:

Задача Где (якорь, строки до-iter6) Что
1 между }; конца EDGE_DETAILS (стр. 1328) и // SECTION 4: VIS INIT (стр. 1330) новый блок SECTION 3.6 — данные
2 CSS перед </style> (стр. 75); DOM после #legend-category (стр. 96); рендер в showNodeLegend после set категории (стр. 1400) секция «Паспорт узла»
3 CSS перед </style> (стр. 75); DOM перед </div> #cat-legend (стр. 137); правки IIFE HIGHLIGHT (стр. 16351770); guard в network.on('click') (стр. 1488) две кнопки + режим viewMode
4 memory/project_automation_map.md (вне репозитория) фиксация iter6 + финальный smoke

Номера строк — на состояние файла до iter6. Задачи 1→4 выполняются по порядку; задачи 2–4 в правках опираются на стабильные якоря кода, а не на абсолютные номера (после задачи 1 строки сместятся на ~100 вниз).

Окружение для верификации (квирки memory): квирк #90 — Playwright browser_navigate отвергает file:// → отдавать карту через python -m http.server. Квирк #91 — browser_click нестабилен на узких элементах → клики делать через browser_evaluate.


Task 1: Блок данных SECTION 3.6

Files:

  • Modify: docs/automation-graph.html — вставка между стр. 1328 (}; конца EDGE_DETAILS) и стр. 1330 (// SECTION 4)

Назначение: META_SNAPSHOT / META_WINDOW (снимок), NODE_META (83 записи, поля since/changed/uses/usesSrc, опционально dupNote), DUPLICATE_GROUPS (6 пар), производные DUP_BY_NODE / DUP_NODE_SET. Значения — фактические, выведены на этапе writing-plans (методика и сырые выводы — Приложение А).

  • Шаг 1: вставить блок SECTION 3.6

Edit. old_string (якорь — заголовок SECTION 4, уникален):

// ════════════════════════════════════════════════════
// SECTION 4: VIS INIT
// ════════════════════════════════════════════════════
const GROUPS = {

new_string — тот же якорь, но перед ним вставлен весь блок SECTION 3.6:

// ════════════════════════════════════════════════════
// SECTION 3.6: NODE META (iter6 — даты, использование, дубли)
// ════════════════════════════════════════════════════
// Данные — фактический снимок: даты из git/changelog/mtime, счётчик uses —
// из разбора транскриптов сессий Claude Code за окно META_WINDOW.
// Методика и воспроизводимость — план iter6, Приложение А.
const META_SNAPSHOT = '16.05.2026';     // дата генерации значений
const META_WINDOW   = '0916.05.2026';  // окно подсчёта использования (7 дней)

// uses: number — измеримый узел (0 = реально простаивал); null — измерить нельзя
//   (узел-правило / плагин-обёртка / автономный демон / пассивное хранилище) → «нет данных».
// usesSrc: 'скил' | 'агент' | 'MCP' | 'хук' | 'memory-чтение' | 'коммиты' | 'инспекция' | '—'
const NODE_META = {
  // ── ПРАВИЛА (4) — узлы-правила, напрямую не вызываются ──
  pravila:   { since: '06.05.2026', changed: '15.05.2026', uses: null, usesSrc: '—' },
  claude_md: { since: '06.05.2026', changed: '15.05.2026', uses: null, usesSrc: '—' },
  psr_v1:    { since: '09.05.2026', changed: '15.05.2026', uses: null, usesSrc: '—' },
  tooling:   { since: '06.05.2026', changed: '15.05.2026', uses: null, usesSrc: '—' },

  // ── ПЛАГИНЫ (5) ──
  superpowers:    { since: '09.05.2026', changed: '—', uses: null, usesSrc: '—' },
  fd_plugin:      { since: '10.05.2026', changed: '—', uses: 1,    usesSrc: 'скил' },
  upm:            { since: '10.05.2026', changed: '—', uses: 0,    usesSrc: 'скил' },
  claude_md_mgmt: { since: '10.05.2026', changed: '—', uses: 15,   usesSrc: 'скил' },
  hookify_plugin: { since: '—',          changed: '—', uses: null, usesSrc: '—' },

  // ── СКИЛЫ SUPERPOWERS (14) — связка подключена 09.05.2026 ──
  sk_brainstorm: { since: '09.05.2026', changed: '—', uses: 44, usesSrc: 'скил' },
  sk_wplans:     { since: '09.05.2026', changed: '—', uses: 54, usesSrc: 'скил' },
  sk_eplans:     { since: '09.05.2026', changed: '—', uses: 11, usesSrc: 'скил' },
  sk_subagent:   { since: '09.05.2026', changed: '—', uses: 33, usesSrc: 'скил' },
  sk_tdd:        { since: '09.05.2026', changed: '—', uses: 2,  usesSrc: 'скил' },
  sk_verify:     { since: '09.05.2026', changed: '—', uses: 25, usesSrc: 'скил' },
  sk_debug:      { since: '09.05.2026', changed: '—', uses: 10, usesSrc: 'скил' },
  sk_parallel:   { since: '09.05.2026', changed: '—', uses: 3,  usesSrc: 'скил' },
  sk_worktree:   { since: '09.05.2026', changed: '—', uses: 3,  usesSrc: 'скил' },
  sk_pr:         { since: '09.05.2026', changed: '—', uses: 10, usesSrc: 'скил' },
  sk_coderev:    { since: '09.05.2026', changed: '—', uses: 1,  usesSrc: 'скил' },
  sk_spreview:   { since: '09.05.2026', changed: '—', uses: 0,  usesSrc: 'скил' },
  sk_wskills:    { since: '09.05.2026', changed: '—', uses: 0,  usesSrc: 'скил' },
  sk_elements:   { since: '09.05.2026', changed: '—', uses: 0,  usesSrc: 'скил' },

  // ── СКИЛЫ ПРОЕКТА (2) — файлы добавлены в репозиторий 13.05.2026 ──
  sk_rls:   { since: '13.05.2026', changed: '—', uses: 0, usesSrc: 'скил' },
  sk_qitem: { since: '13.05.2026', changed: '—', uses: 1, usesSrc: 'скил' },

  // ── ХУКИ (5) ──
  hk_session:     { since: '—',          changed: '—', uses: 67,   usesSrc: 'хук' },
  hk_economy:     { since: '10.05.2026', changed: '—', uses: 276,  usesSrc: 'хук' },
  hk_pre_claude:  { since: '13.05.2026', changed: '—', uses: 249,  usesSrc: 'хук' },
  hk_post_md:     { since: '06.05.2026', changed: '—', uses: 1334, usesSrc: 'хук' },
  hk_post_schema: { since: '13.05.2026', changed: '—', uses: 22,   usesSrc: 'хук' },

  // ── АГЕНТЫ (11) ──
  ag_explore:    { since: 'встроено в Claude Code', changed: '—', uses: 26,  usesSrc: 'агент' },
  ag_general:    { since: 'встроено в Claude Code', changed: '—', uses: 512, usesSrc: 'агент' },
  ag_plan:       { since: 'встроено в Claude Code', changed: '—', uses: 0,   usesSrc: 'агент' },
  ag_pest:       { since: '13.05.2026',             changed: '—', uses: 2,   usesSrc: 'агент' },
  ag_guide:      { since: 'встроено в Claude Code', changed: '—', uses: 4,   usesSrc: 'агент' },
  ag_statusline: { since: 'встроено в Claude Code', changed: '—', uses: 0,   usesSrc: 'агент' },
  ag_hookify:    { since: '—',                      changed: '—', uses: 0,   usesSrc: 'агент' },
  ag_pcreator:   { since: '—',                      changed: '—', uses: 0,   usesSrc: 'агент' },
  ag_pvalid:     { since: '—',                      changed: '—', uses: 0,   usesSrc: 'агент' },
  ag_skreview:   { since: '—',                      changed: '—', uses: 0,   usesSrc: 'агент' },
  ag_rls:        { since: '13.05.2026',             changed: '—', uses: 2,   usesSrc: 'агент' },

  // ── MCP-СЕРВЕРЫ (7) ──
  mcp_21st:    { since: '10.05.2026', changed: '—', uses: 0,   usesSrc: 'MCP' },
  mcp_pw:      { since: '06.05.2026', changed: '—', uses: 706, usesSrc: 'MCP' },
  mcp_gh:      { since: '06.05.2026', changed: '—', uses: 0,   usesSrc: 'MCP' },
  mcp_boost:   { since: '08.05.2026', changed: '—', uses: 73,  usesSrc: 'MCP' },
  mcp_redis:   { since: '13.05.2026', changed: '—', uses: 0,   usesSrc: 'MCP' },
  mcp_sentry:  { since: '13.05.2026', changed: '—', uses: 0,   usesSrc: 'MCP' },
  mcp_semgrep: { since: '10.05.2026', changed: '—', uses: 0,   usesSrc: 'MCP' },

  // ── LEFTHOOK JOBS (10) — uses ≈ число коммитов в окне, фильтр по типу файла ──
  lh_mdlint:    { since: '06.05.2026', changed: '—', uses: 232, usesSrc: 'коммиты' },
  lh_cspell:    { since: '06.05.2026', changed: '—', uses: 232, usesSrc: 'коммиты' },
  lh_stylelint: { since: '06.05.2026', changed: '—', uses: 43,  usesSrc: 'коммиты' },
  lh_eslint:    { since: '08.05.2026', changed: '—', uses: 120, usesSrc: 'коммиты' },
  lh_lychee:    { since: '06.05.2026', changed: '—', uses: 56,  usesSrc: 'коммиты' },
  lh_gitleaks:  { since: '06.05.2026', changed: '—', uses: 514, usesSrc: 'коммиты' },
  lh_gitleaks2: { since: '06.05.2026', changed: '—', uses: 56,  usesSrc: 'коммиты' },
  lh_pint:      { since: '08.05.2026', changed: '—', uses: 134, usesSrc: 'коммиты' },
  lh_larastan:  { since: '08.05.2026', changed: '—', uses: 134, usesSrc: 'коммиты' },
  lh_squawk:    { since: '08.05.2026', changed: '—', uses: 19,  usesSrc: 'коммиты' },

  // ── MEMORY FILES (15) — uses = вызовы Read; changed = mtime файла ──
  mem_user:       { since: '07.05.2026', changed: '08.05.2026', uses: 0,  usesSrc: 'memory-чтение' },
  mem_comm:       { since: '07.05.2026', changed: '08.05.2026', uses: 0,  usesSrc: 'memory-чтение' },
  mem_env:        { since: '07.05.2026', changed: '15.05.2026', uses: 41, usesSrc: 'memory-чтение' },
  mem_sp:         { since: '09.05.2026', changed: '10.05.2026', uses: 1,  usesSrc: 'memory-чтение' },
  mem_plugins:    { since: '09.05.2026', changed: '13.05.2026', uses: 9,  usesSrc: 'memory-чтение' },
  mem_handoff:    { since: '08.05.2026', changed: '—',          uses: 0,  usesSrc: 'memory-чтение' },
  mem_redesign:   { since: '12.05.2026', changed: '—',          uses: 0,  usesSrc: 'memory-чтение' },
  mem_devindices: { since: '12.05.2026', changed: '—',          uses: 1,  usesSrc: 'memory-чтение' },
  mem_phase1:     { since: '08.05.2026', changed: '—',          uses: 1,  usesSrc: 'memory-чтение' },
  mem_state:      { since: '07.05.2026', changed: '15.05.2026', uses: 73, usesSrc: 'memory-чтение' },
  mem_brain:      { since: '11.05.2026', changed: '—',          uses: 1,  usesSrc: 'memory-чтение' },
  mem_supplier:   { since: '10.05.2026', changed: '12.05.2026', uses: 13, usesSrc: 'memory-чтение' },
  mem_audit:      { since: '13.05.2026', changed: '—',          uses: 3,  usesSrc: 'memory-чтение' },
  mem_archive:    { since: '07.05.2026', changed: '15.05.2026', uses: 21, usesSrc: 'memory-чтение' },
  mem_github:     { since: '07.05.2026', changed: '15.05.2026', uses: 33, usesSrc: 'memory-чтение' },

  // ── RUFLO ОРКЕСТРАТОР (9) — все внедрены big-bang'ом 15.05.2026 ──
  ruflo_queen:          { since: '15.05.2026', changed: '—', uses: 0,    usesSrc: 'инспекция' },
  ruflo_plugins:        { since: '15.05.2026', changed: '—', uses: 0,    usesSrc: 'инспекция' },
  ruflo_workers:        { since: '15.05.2026', changed: '—', uses: 0,    usesSrc: 'инспекция' },
  ruflo_agents_catalog: { since: '15.05.2026', changed: '—', uses: 0,    usesSrc: 'инспекция',
    dupNote: '100 определений агентов дублируют реестр агентов; каталог буквально содержит 2 проектных агента' },
  ruflo_commands:       { since: '15.05.2026', changed: '—', uses: 0,    usesSrc: 'инспекция',
    dupNote: '88 slash-команд дублируют роль скилов — именованные вызываемые процедуры; команды инертны' },
  ruflo_daemon:         { since: '15.05.2026', changed: '—', uses: null, usesSrc: '—' },
  ruflo_memory:         { since: '15.05.2026', changed: '—', uses: null, usesSrc: '—',
    dupNote: 'дублирует роль 16 memory-файлов проекта — постоянная память между сессиями; уже ⚫-конфликт с project_state' },
  ruflo_mcp:            { since: '15.05.2026', changed: '—', uses: 36,   usesSrc: 'MCP' },
  ruflo_recall_hook:    { since: '15.05.2026', changed: '—', uses: 220,  usesSrc: 'хук' },

  // ── MEMORY +1 (артефакт ruflo big-bang) ──
  mem_ruflo: { since: '15.05.2026', changed: '16.05.2026', uses: 18, usesSrc: 'memory-чтение' },
};

// Явные парные дубли (Фича 3) — попадают в кнопку «⧉ Дубли».
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-файлов' },
];

// Производный индекс: nodeId -> { partner, pct, reason } — для рендера легенды и подсветки.
const DUP_BY_NODE = new Map();
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()); // 12 узлов-членов парных дублей

// ════════════════════════════════════════════════════
// SECTION 4: VIS INIT
// ════════════════════════════════════════════════════
const GROUPS = {
  • Шаг 2: smoke — данные загрузились, покрытие полное

Запустить локальный сервер и открыть карту:

# терминал: отдать docs/ по HTTP (квирк #90 — file:// не работает в Playwright)
cd "c:\моя\проекты\портал crm\Документация\docs"
python -m http.server 8011

Playwright MCP: browser_navigatehttp://localhost:8011/automation-graph.html; затем browser_evaluate:

() => ({
  nodeMetaKeys: Object.keys(NODE_META).length,
  allCovered: NODES.every(n => !!NODE_META[n.id]),
  extraKeys: Object.keys(NODE_META).filter(k => !NODES.some(n => n.id === k)),
  dupGroups: DUPLICATE_GROUPS.length,
  dupNodeSet: DUP_NODE_SET.size,
  nodes: NODES.length, edges: EDGES.length,
})

Expected: { nodeMetaKeys: 83, allCovered: true, extraKeys: [], dupGroups: 6, dupNodeSet: 12, nodes: 83, edges: 90 }. Затем browser_console_messages (level: error) — ожидается пусто, кроме внешнего favicon.ico 404.

  • Шаг 3: коммит
git add docs/automation-graph.html
git commit -m "feat(graph): iter6 SECTION 3.6 — NODE_META + DUPLICATE_GROUPS data"

Перед коммитом сработает pre-commit lefthook (gitleaks; markdownlint/cspell — только если в коммите есть .md; здесь только .html → gitleaks + stylelint). Вывод pre-commit показать целиком. --no-verify запрещён.


Task 2: Секция «📇 Паспорт узла» в легенде узла

Files:

  • Modify: docs/automation-graph.html — CSS (перед </style>), DOM (после #legend-category), рендер (в showNodeLegend)

Объединяет Фичи 1 и 2: одна новая секция легенды с датами + счётчиком + строкой «Дубль».

  • Шаг 1: CSS секции «Паспорт»

Edit. old_string (якорь — конец блока .cat-item.active, последнее правило перед </style>):

    .cat-item.active {
      background: rgba(253,246,227,0.12);
      box-shadow: inset 0 0 0 1px rgba(253,246,227,0.4);
      color: #fdf6e3;
    }
  </style>

new_string:

    .cat-item.active {
      background: rgba(253,246,227,0.12);
      box-shadow: inset 0 0 0 1px rgba(253,246,227,0.4);
      color: #fdf6e3;
    }

    /* ── Паспорт узла (iter6) ── */
    #passport-section p { font-size: 12px; color: #eee8d5; line-height: 1.6; }
    #passport-section p .pp-k { color: #839496; }
  </style>
  • Шаг 2: DOM секции «Паспорт»

Edit. old_string (якорь — бейдж категории + начало секции «Что делает»):

      <div id="legend-category"></div>
      <div class="legend-section"><h4>Что делает</h4><p id="ld-desc">—</p></div>

new_string:

      <div id="legend-category"></div>
      <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>
      <div class="legend-section"><h4>Что делает</h4><p id="ld-desc">—</p></div>
  • Шаг 3: рендер «Паспорта» в showNodeLegend

Edit. old_string (якорь — конец заполнения категории + начало ld-desc):

  document.getElementById('legend-category').innerHTML =
    `<span style="color:${catColor}; font-weight:600;">● ${catLabel}</span>`;

  document.getElementById('ld-desc').textContent = details.desc;

new_string:

  document.getElementById('legend-category').innerHTML =
    `<span style="color:${catColor}; font-weight:600;">● ${catLabel}</span>`;

  // ── Паспорт узла (iter6) ──
  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';
  }

  document.getElementById('ld-desc').textContent = details.desc;
  • Шаг 4: smoke — паспорт рендерится на всех классах узлов

Перезагрузить http://localhost:8011/automation-graph.html. browser_evaluate — программно прокликать репрезентативные узлы и снять состояние паспорта:

() => {
  const probe = id => {
    network.selectNodes([id]);
    showNodeLegend(id);
    return {
      since: document.getElementById('ld-since').textContent,
      changed: document.getElementById('ld-changed').textContent,
      uses: document.getElementById('ld-uses').textContent,
      dupShown: document.getElementById('ld-dup-row').style.display !== 'none',
      dup: document.getElementById('ld-dup').textContent,
    };
  };
  return {
    sk_wplans: probe('sk_wplans'),     // измеримый: 54
    pravila:   probe('pravila'),       // null → «нет данных»
    sk_rls:    probe('sk_rls'),        // D3 → строка «Дубль» с rls-reviewer
    ruflo_memory: probe('ruflo_memory'),// dupNote → строка «Дубль»
    mem_state: probe('mem_state'),     // 73, changed 15.05.2026
  };
}

Expected:

  • sk_wplans.since='09.05.2026', sk_wplans.uses содержит 54 и (скил) и срез 0916.05.2026, sk_wplans.dupShown=false;
  • pravila.uses='нет данных (узел не вызывается напрямую)', pravila.dupShown=false;
  • sk_rls.dupShown=true, sk_rls.dup содержит rls-reviewer и ~85%;
  • ruflo_memory.dupShown=true, ruflo_memory.dup содержит 16 memory-файлов;
  • mem_state.since='07.05.2026', mem_state.changed='15.05.2026', mem_state.uses содержит 73.

browser_console_messages (error) — пусто, кроме favicon 404.

  • Шаг 5: коммит
git add docs/automation-graph.html
git commit -m "feat(graph): iter6 — «Паспорт узла» (даты + использование) в легенде"

Показать полный вывод pre-commit.


Task 3: Кнопки футера + режим viewMode (теплокарта + дубли)

Files:

  • Modify: docs/automation-graph.html — CSS (перед </style>), DOM (#cat-legend), IIFE HIGHLIGHT, network.on('click')

Фича 2 (кнопка подсветки) + Фича 3 (кнопка дублей). Режимы heat/dup взаимоисключающие, сбрасывают пофильтровую подсветку.

  • Шаг 1: CSS кнопок .cat-ctl

Edit. old_string (якорь — CSS «Паспорта» из Task 2, конец перед </style>):

    /* ── Паспорт узла (iter6) ── */
    #passport-section p { font-size: 12px; color: #eee8d5; line-height: 1.6; }
    #passport-section p .pp-k { color: #839496; }
  </style>

new_string:

    /* ── Паспорт узла (iter6) ── */
    #passport-section p { font-size: 12px; color: #eee8d5; line-height: 1.6; }
    #passport-section p .pp-k { color: #839496; }

    /* ── Кнопки режимов в футере (iter6) ── */
    .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;
    }
  </style>
  • Шаг 2: DOM кнопок в #cat-legend

Edit. old_string (якорь — последний бейдж категории GREEN + закрытие #cat-legend):

  <div class="cat-item" data-filter-key="conflict:GREEN"><div class="cat-dot" style="background:#859900; border:1px dashed #859900"></div>🟢 Закрыт правилом</div>
</div>

new_string:

  <div class="cat-item" data-filter-key="conflict:GREEN"><div class="cat-dot" style="background:#859900; border:1px dashed #859900"></div>🟢 Закрыт правилом</div>
  <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="Подсветить явные пары дублей (D1D5, D7)">⧉ Дубли</button>
</div>
  • Шаг 3: viewMode в IIFE HIGHLIGHT — состояние + теплокарта

Edit 3a — добавить viewMode в state. old_string:

  const state = {
    selectedNode: null,
    legendFilter: new Set(),
  };

new_string:

  const state = {
    selectedNode: null,
    legendFilter: new Set(),
    viewMode: null, // null | 'heat' | 'dup' — взаимоисключающие режимы (iter6)
  };

  // ── Теплокарта использования (iter6) — 4 яруса по NODE_META[id].uses ──
  function heatOpacity(nodeId) {
    const m = NODE_META[nodeId];
    const u = m ? m.uses : null;
    if (u === null || u === undefined) return 0.5;  // нет данных — нейтрально
    if (u >= 21) return 1.0;                        // часто
    if (u >= 6)  return 0.65;                       // иногда
    if (u >= 1)  return 0.35;                       // редко
    return 0.12;                                    // простаивает (uses === 0)
  }
  // Узлы верхнего яруса теплокарты получают акцентную рамку.
  function heatBorderWidth(nodeId) {
    if (state.viewMode !== 'heat') return 2;
    const m = NODE_META[nodeId];
    const u = m ? m.uses : null;
    return (typeof u === 'number' && u >= 21) ? 4 : 2;
  }
  // Переключатель режима — toggle; включение режима гасит пофильтровую подсветку.
  function setViewMode(mode) {
    state.viewMode = (state.viewMode === mode) ? null : mode;
    if (state.viewMode !== null) {
      state.legendFilter.clear();
      state.selectedNode = null;
    }
  }

Edit 3b — приоритетная ветка viewMode в начале computeNodeOpacity. old_string:

  function computeNodeOpacity(nodeId) {
    // Row 1: focus
    if (state.selectedNode !== null) {

new_string:

  function computeNodeOpacity(nodeId) {
    // Row 0: view-mode (iter6) — глобальная картина, поверх focus/filter
    if (state.viewMode === 'heat') return heatOpacity(nodeId);
    if (state.viewMode === 'dup')  return DUP_NODE_SET.has(nodeId) ? OPACITY_FOCUS : OPACITY_DIM;
    // Row 1: focus
    if (state.selectedNode !== null) {

Edit 3c — borderWidth в applyHighlight. old_string:

  function applyHighlight() {
    const nodeUpdates = NODES.map(n => ({
      id: n.id,
      opacity: computeNodeOpacity(n.id),
    }));

new_string:

  function applyHighlight() {
    const nodeUpdates = NODES.map(n => ({
      id: n.id,
      opacity: computeNodeOpacity(n.id),
      borderWidth: heatBorderWidth(n.id), // 4 для верхнего яруса теплокарты, иначе 2 (iter6)
    }));

Намеренно: applyHighlight теперь нормализует borderWidth при каждом вызове (2, либо 4 в heat-режиме). Это обязательно для спецификации §7.3 «при выходе из heat borderWidth возвращается к 2». Побочный эффект: толстая рамка поиска (5) сбрасывается до 2 при клике по фильтру категории после поиска — не регрессия (сам поиск не затронут; проверяется в Task 4 §9.8 c8_search), а согласованное поведение (поиск и фильтр — раздельные режимы, поиск зовёт clearAll).

Edit 3d — сброс viewMode в clearAll. old_string:

  function clearAll() {
    state.selectedNode = null;
    state.legendFilter.clear();
  }

new_string:

  function clearAll() {
    state.selectedNode = null;
    state.legendFilter.clear();
    state.viewMode = null;
  }

Edit 3e — класс .active на кнопках в updateLegendVisuals. old_string:

  function updateLegendVisuals() {
    document.querySelectorAll('#cat-legend .cat-item').forEach(item => {
      const key = item.dataset.filterKey;
      if (!key) return;
      if (state.legendFilter.has(key)) item.classList.add('active');
      else item.classList.remove('active');
    });
  }

new_string:

  function updateLegendVisuals() {
    document.querySelectorAll('#cat-legend .cat-item').forEach(item => {
      const key = item.dataset.filterKey;
      if (!key) return;
      if (state.legendFilter.has(key)) item.classList.add('active');
      else item.classList.remove('active');
    });
    const heatBtn = document.getElementById('cat-ctl-heat');
    const dupBtn  = document.getElementById('cat-ctl-dup');
    if (heatBtn) heatBtn.classList.toggle('active', state.viewMode === 'heat');
    if (dupBtn)  dupBtn.classList.toggle('active', state.viewMode === 'dup');
  }

Edit 3f — обработка клика по .cat-ctl в делегате #cat-legend. old_string:

  document.getElementById('cat-legend').addEventListener('click', e => {
    const item = e.target.closest('.cat-item');
    if (!item || !item.dataset.filterKey) return;
    toggleFilter(item.dataset.filterKey);
    applyHighlight();
    updateLegendVisuals();
  });

new_string:

  document.getElementById('cat-legend').addEventListener('click', e => {
    // iter6 — клик по кнопке режима heat/dup
    const ctl = e.target.closest('.cat-ctl');
    if (ctl) {
      setViewMode(ctl.id === 'cat-ctl-heat' ? 'heat' : 'dup');
      applyHighlight();
      updateLegendVisuals();
      return;
    }
    const item = e.target.closest('.cat-item');
    if (!item || !item.dataset.filterKey) return;
    toggleFilter(item.dataset.filterKey);
    applyHighlight();
    updateLegendVisuals();
  });
  • Шаг 4: guard клика по узлу при активном viewMode

В режиме heat/dup клик по узлу открывает паспорт, но не должен сбивать глобальную картину режима выставлением selectedNode.

Edit. old_string:

network.on('click', params => {
  if (params.nodes.length === 1) {
    const id = params.nodes[0];
    HIGHLIGHT.setSelectedNode(id);
    HIGHLIGHT.applyHighlight();
    // Right panel still shows details of the clicked node (last-clicked, even after toggle-off)
    showNodeLegend(id);
  } else if (params.edges.length === 1) {
    showEdgeLegend(params.edges[0]);
  } else if (params.nodes.length === 0 && params.edges.length === 0) {
    HIGHLIGHT.setSelectedNode(null);
    HIGHLIGHT.applyHighlight();
    document.getElementById('legend-panel').classList.remove('visible');
  }
});

new_string:

network.on('click', params => {
  if (params.nodes.length === 1) {
    const id = params.nodes[0];
    // iter6 — в режиме heat/dup клик открывает паспорт, но не трогает подсветку режима
    if (HIGHLIGHT.state.viewMode === null) {
      HIGHLIGHT.setSelectedNode(id);
      HIGHLIGHT.applyHighlight();
    }
    // Right panel still shows details of the clicked node (last-clicked, even after toggle-off)
    showNodeLegend(id);
  } else if (params.edges.length === 1) {
    showEdgeLegend(params.edges[0]);
  } else if (params.nodes.length === 0 && params.edges.length === 0) {
    if (HIGHLIGHT.state.viewMode === null) {
      HIGHLIGHT.setSelectedNode(null);
      HIGHLIGHT.applyHighlight();
    }
    document.getElementById('legend-panel').classList.remove('visible');
  }
});
  • Шаг 5: smoke — режимы работают и взаимоисключающи

Перезагрузить карту. browser_evaluate:

() => {
  const heatBtn = document.getElementById('cat-ctl-heat');
  const dupBtn  = document.getElementById('cat-ctl-dup');
  const op = id => nodesDS.get(id).opacity;
  const res = {};

  heatBtn.click();
  res.heatOn        = HIGHLIGHT.state.viewMode;                 // 'heat'
  res.heatBtnActive = heatBtn.classList.contains('active');     // true
  res.opFreq        = op('hk_post_md');                        // 1.0  (uses 1334)
  res.opIdle        = op('mem_user');                          // 0.12 (uses 0)
  res.opRare        = op('sk_tdd');                            // 0.35 (uses 2)
  res.opNull        = op('pravila');                           // 0.5  (uses null)
  res.borderTop     = nodesDS.get('mcp_pw').borderWidth;       // 4

  dupBtn.click();
  res.dupOn          = HIGHLIGHT.state.viewMode;                // 'dup'
  res.heatOffAfterDup= heatBtn.classList.contains('active');    // false — взаимоисключение
  res.dupHighlighted = NODES.filter(n => op(n.id) === 1.0).length; // 12
  res.dupMember      = op('sk_rls');                           // 1.0
  res.dupNonMember   = op('mcp_pw');                           // 0.15

  document.getElementById('btn-clear').click();
  res.cleared        = HIGHLIGHT.state.viewMode;                // null
  res.borderReset    = nodesDS.get('mcp_pw').borderWidth;      // 2
  res.opAfterClear   = op('mem_user');                         // 1.0
  return res;
}

Expected: { heatOn:'heat', heatBtnActive:true, opFreq:1, opIdle:0.12, opRare:0.35, opNull:0.5, borderTop:4, dupOn:'dup', heatOffAfterDup:false, dupHighlighted:12, dupMember:1, dupNonMember:0.15, cleared:null, borderReset:2, opAfterClear:1 }.

browser_console_messages (error) — пусто, кроме favicon 404.

  • Шаг 6: коммит
git add docs/automation-graph.html
git commit -m "feat(graph): iter6 — кнопки «По использованию» / «Дубли» + режим viewMode"

Показать полный вывод pre-commit.


Task 4: Память + финальный приёмочный smoke + завершение ветки

Files:

  • Modify: memory/project_automation_map.md (вне репозитория — C:\Users\Administrator\.claude\projects\c---------------------crm-------------\memory\)

  • Modify: MEMORY.md (тот же каталог) — однострочный указатель уже есть, обновить хук-фразу

  • Шаг 1: финальный приёмочный smoke — 8 критериев спецификации

Перезагрузить http://localhost:8011/automation-graph.html. browser_evaluate — сводная проверка всех 8 критериев приёмки спецификации §9:

() => {
  const op = id => nodesDS.get(id).opacity;
  const r = {};
  // §9.1 — граф рендерится
  r.c1_nodes = NODES.length === 83 && EDGES.length === 90;
  // §9.7 — NODE_META покрывает все 83 id
  r.c7_coverage = NODES.every(n => !!NODE_META[n.id]) && Object.keys(NODE_META).length === 83;
  // §9.2/§9.3 — паспорт: измеримый / null / парный дубль / dupNote
  showNodeLegend('lh_gitleaks');
  r.c2_measured = document.getElementById('ld-uses').textContent.includes('514');
  r.c2_dupPair  = document.getElementById('ld-dup-row').style.display !== 'none';
  showNodeLegend('tooling');
  r.c3_null = document.getElementById('ld-uses').textContent.startsWith('нет данных');
  showNodeLegend('ruflo_commands');
  r.c2_dupNote = document.getElementById('ld-dup').textContent.includes('88 slash-команд');
  // §9.4 — теплокарта
  document.getElementById('cat-ctl-heat').click();
  r.c4_heat = HIGHLIGHT.state.viewMode === 'heat' && op('ag_general') === 1.0;
  // §9.5 — дубли: ровно 12
  document.getElementById('cat-ctl-dup').click();
  r.c5_dup12 = NODES.filter(n => op(n.id) === 1.0).length === 12;
  // §9.6 — взаимоисключение + сброс
  r.c6_exclusive = !document.getElementById('cat-ctl-heat').classList.contains('active');
  document.getElementById('btn-clear').click();
  r.c6_clear = HIGHLIGHT.state.viewMode === null && op('mem_user') === 1.0;
  // §9.8 — регрессий нет: пофильтровая подсветка iter3 жива
  HIGHLIGHT.toggleFilter('group:rules'); HIGHLIGHT.applyHighlight();
  r.c8_filter = op('pravila') === 1.0 && op('mcp_pw') === 0.15;
  document.getElementById('btn-clear').click();
  // §9.8 — поиск жив: matches → borderWidth 5 / opacity 1, прочие гаснут до 0.25
  const si = document.getElementById('search');
  si.value = 'gitleaks'; si.dispatchEvent(new Event('input'));
  const g = nodesDS.get('lh_gitleaks');
  r.c8_search = g.opacity === 1.0 && g.borderWidth === 5 && nodesDS.get('pravila').opacity === 0.25;
  document.getElementById('btn-clear').click();
  // §9.8 — edge-легенда жива: показ ребра переключает панель на edge-контент
  showEdgeLegend(edgesDS.get()[0].id);
  r.c8_edge = document.getElementById('legend-edge-content').style.display !== 'none'
           && document.getElementById('legend-node-content').style.display === 'none';
  document.getElementById('legend-close').click();
  return r;
}

Expected — все поля true: { c1_nodes:true, c7_coverage:true, c2_measured:true, c2_dupPair:true, c3_null:true, c2_dupNote:true, c4_heat:true, c5_dup12:true, c6_exclusive:true, c6_clear:true, c8_filter:true, c8_search:true, c8_edge:true }.

Resize-панель (setupResizeHandle) iter6 структурно не трогает — отдельная функция вне зоны правок; ручная проверка: потянуть #legend-handle, ширина меняется. Дополнительно: browser_take_screenshot (теплокарта включена) — визуальная сверка ярусности; browser_console_messages (error) — пусто, кроме внешнего favicon 404. Если хоть один критерий false — это дефект: применить superpowers:systematic-debugging, не править наугад.

  • Шаг 2: обновить memory project_automation_map.md

В файле C:\Users\Administrator\.claude\projects\c---------------------crm-------------\memory\project_automation_map.md дописать в начало описания карты пометку про iter6 (Edit, добавление фразы в существующий абзац — не переписывать файл целиком):

Добавить после фразы про iter5 closure (абзац, начинающийся с «iter5 closed»):

**iter6 closed 2026-05-16:** +секция «📇 Паспорт узла» в легенде всех 83 узлов (дата внедрения / дата последнего изменения / число вызовов за 7 дней — фактический срез окна 09–16.05.2026 из разбора 76 транскриптов сессий) + 2 toggle-кнопки футера: «🔥 По использованию» (теплокарта, 4 яруса) и «⧉ Дубли» (12 узлов из 6 явных пар D1–D5/D7). Чисто аддитивно: NODES/EDGES/NODE_DETAILS не тронуты, метрики 83/90/11 без изменений. Счётчик использования — снимок, статичный HTML его не обновляет; при следующих итерациях значения NODE_META требуют пересборки. Spec/plan — `docs/superpowers/{specs,plans}/2026-05-16-automation-graph-iter6-node-meta-*.md`.
  • Шаг 3: коммит
git add docs/automation-graph.html docs/superpowers/plans/2026-05-16-automation-graph-iter6-node-meta.md
git commit -m "docs(graph): iter6 closure — план + memory-фиксация"

Memory-файл project_automation_map.md вне репозитория — в коммит не входит, в git add не включать. Показать полный вывод pre-commit (в коммите есть .md → сработают markdownlint + cspell; gitleaks всегда).

  • Шаг 4: завершение ветки

Вызвать superpowers:finishing-a-development-branch: прогнать проверки, определить окружение, представить заказчику меню (merge / PR / оставить / отбросить). Перед заявлением «готово» — superpowers:verification-before-completion с реальным прогоном smoke (не «должно работать»).


Приложение А — методика и воспроизводимость фактических данных

Спецификация §5 делегирует производство значений since/changed/uses этапу writing-plans. Ниже — метод (чтобы числа были воспроизводимы) и фактические сырые выводы, на которых построен NODE_META из Task 1.

А.1. uses — счётчик вызовов за окно 0916.05.2026 (UTC)

Источник — 76 транскриптов сессий Claude Code C:\Users\Administrator\.claude\projects\c---------------------crm-------------\*.jsonl. Каждая строка — JSON-запись; вызовы инструментов — блоки tool_use в сообщениях ассистента. Окно — по полю timestamp каждой записи: 2026-05-09T00:00:00Z2026-05-16T23:59:59Z.

Скрипт разбора (node, ES-модуль):

import fs from 'fs';
import path from 'path';
const DIR = 'C:/Users/Administrator/.claude/projects/c---------------------crm-------------';
const S = Date.parse('2026-05-09T00:00:00.000Z'), E = Date.parse('2026-05-16T23:59:59.999Z');
const skill = {}, agent = {}, mcp = {}, memRead = {};
const sessions = new Set();
let editMd = 0, editClaudeMd = 0, editSchema = 0, econ = 0;
const ECON = /экономия\s*\d+\s*%/i;
for (const f of fs.readdirSync(DIR).filter(x => x.endsWith('.jsonl'))) {
  for (const ln of fs.readFileSync(path.join(DIR, f), 'utf8').split('\n')) {
    if (!ln.trim()) continue;
    let o; try { o = JSON.parse(ln); } catch { continue; }
    const ts = o.timestamp ? Date.parse(o.timestamp) : NaN;
    if (isNaN(ts) || ts < S || ts > E) continue;
    sessions.add(f);
    const m = o.message; if (!m || !Array.isArray(m.content)) continue;
    if (o.type === 'user' && m.role === 'user' && !m.content.some(c => c.type === 'tool_result')) {
      if (ECON.test(m.content.filter(c => c.type === 'text').map(c => c.text || '').join('\n'))) econ++;
    }
    for (const c of m.content) {
      if (c.type !== 'tool_use') continue;
      const n = c.name || '', inp = c.input || {};
      if (n === 'Skill') skill[inp.skill || '(none)'] = (skill[inp.skill || '(none)'] || 0) + 1;
      else if (n === 'Agent' || n === 'Task') agent[inp.subagent_type || '(none)'] = (agent[inp.subagent_type || '(none)'] || 0) + 1;
      else if (n.startsWith('mcp__')) mcp[n.split('__')[1]] = (mcp[n.split('__')[1]] || 0) + 1;
      else if (n === 'Read') {
        const fp = (inp.file_path || '').replace(/\\/g, '/');
        if (/\/memory\//i.test(fp) && fp.endsWith('.md')) { const b = fp.split('/').pop(); memRead[b] = (memRead[b] || 0) + 1; }
      } else if (n === 'Edit' || n === 'Write') {
        const fp = (inp.file_path || '').replace(/\\/g, '/'), b = fp.split('/').pop() || '';
        if (b === 'CLAUDE.md') editClaudeMd++; else if (b.endsWith('.md')) editMd++;
        if (/db\/schema\.sql$/i.test(fp)) editSchema++;
      }
    }
  }
}
// (вывод объектов skill/agent/mcp/memRead + sessions.size/editMd/editClaudeMd/editSchema/econ)

Сырой вывод (07–16.05 прогон, окно 09–16.05): 67 сессий, 12015 tool_use, 0 ошибок разбора.

  • Skill → узлы-скилы: writing-plans 54, brainstorming 43 +1 (plugin:superpowers:brainstorming) = 44, subagent-driven-development 33, verification-before-completion 25, executing-plans 11, finishing-a-development-branch 10, systematic-debugging 10, dispatching-parallel-agents 3, using-git-worktrees 3, test-driven-development 2, receiving-code-review 1 → sk_coderev, q-item-add 1; claude-md-management:claude-md-improver 12 + revise-claude-md 3 = 15 → claude_md_mgmt; frontend-design:frontend-design 1 → fd_plugin. Неотображённые скилы (spec-review, writing-skills, elements-of-style, rls-check, ui-ux-pro-max) → uses: 0.
  • Agent (subagent_type): general-purpose 473 + 39 без subagent_type (по семантике инструмента default = general-purpose) = 512; Explore 26; claude-code-guide 4; rls-reviewer 2; pest-parallel-debugger 2. Прочие (claude 3, researcher/code-analyzer/security-auditor по 1) — не узлы карты.
  • MCP (mcp__<server>__*): playwright 706, laravel-boost 73, ruflo 36. github/redis/sentry/semgrep/magic — 0 (весь GitHub-флоу шёл через gh/git в Bash).
  • memory Read: project_state.md 73, feedback_environment.md 41, reference_github.md 33, reference_archive.md 21, project_ruflo_integration.md 18, project_supplier_integration.md 13, feedback_plugin_paired_stack.md 9, project_full_audit_2026-05-13.md 3, feedback_superpowers_hard_rule.md/project_claude_brain.md/project_dev_indices.md/project_phase1_strategy.md по 1. Не упомянутые memory-узлы (user_profile, feedback_communication, reference_handoff, project_portal_redesign) → 0.
  • хуки: 67 сессий → hk_session; 276 промптов с тегом «экономия N%» → hk_economy; правки *.md (кроме корневого CLAUDE.md) 1334 → hk_post_md; правки корневого CLAUDE.md 249 (отдельный прогон с разбором полного пути: 246 абсолютных + 3 относительных, все — корневой проектный) → hk_pre_claude; правки db/schema.sql 22 → hk_post_schema.
  • lefthook: коммитов в окне 514 (git log --since --until | wc -l). Per-job — коммиты, тронувшие файлы под glob job'а (git log --name-only): *.md 232 (lh_mdlint/lh_cspell), *.html 43 (lh_stylelint), app/**/*.php 134 (lh_pint/lh_larastan), app/resources/js/**/*.{ts,vue} 120 (lh_eslint), *.sql 19 (lh_squawk), все 514 (lh_gitleaks). Pre-push (lh_gitleaks2/lh_lychee) — 56 обновлений origin/main из git reflog show origin/main в окне (push'и + редкие fetch'и, приближение).
  • ruflo: ruflo_mcp 36 (измерено, mcp__ruflo__*). ruflo_recall_hook 220 (промпты с момента регистрации хука 15.05.2026 — отдельный прогон). ruflo_queen/ruflo_workers/ruflo_plugins/ruflo_agents_catalog/ruflo_commands0 по верифицированной инспекции рантайма iter5 (рой idle, 0 задач; каталог/команды/плагины не задействованы). ruflo_daemon/ruflo_memorynull (см. А.3).

А.2. since / changed — даты

  • Узлы-файлы в git (4 документа-правила, lefthook.yml, .mcp.json, проектный .claude/settings.json, .claude/skills/*/SKILL.md, .claude/agents/*.md, tools/ruflo-recall-hook.mjs): since = первый коммит файла (git log --reverse --diff-filter / -S<токен> для отдельных job'ов/серверов/хуков в общем файле); changed = последний коммит с изменением (для документов-правил — дата последнего бампа версии). Сырьё: документы-правила — pravila 06.05→15.05, claude_md 06.05→15.05, psr_v1 09.05→15.05, tooling 06.05→15.05. lefthook job'ы — since lh_gitleaks/lh_mdlint/lh_cspell/lh_stylelint/lh_gitleaks2/lh_lychee 06.05, lh_pint/lh_larastan/lh_squawk/lh_eslint 08.05; -S показал, что токен job'а не менялся после внедрения → changed: '—'. MCP — mcp_pw/mcp_gh 06.05, mcp_boost 08.05, mcp_semgrep 10.05, mcp_sentry/mcp_redis 13.05, ruflo_mcp 15.05 (changed: '—'). Проектные хуки — hk_post_md 06.05, hk_pre_claude/hk_post_schema 13.05 (changed: '—'). Проектные скилы/агенты — sk_rls/sk_qitem/ag_pest/ag_rls все 13.05 (файлы добавлены в репозиторий 13.05.2026; changed: '—'). ruflo_recall_hook — 15.05 (changed: '—').
  • Memory-файлы (вне репозитория): changed = mtime файла (надёжен); since = более раннее из mtime-birthtime и первого упоминания файла в транскриптах (birthtime ненадёжен — Write пересоздаёт файл). Правило: changed показывается, только если позже since, иначе '—'. Сырьё первого упоминания: 07.05 — user_profile/feedback_communication/feedback_environment/project_state/reference_archive/reference_github; 08.05 — reference_handoff/project_phase1_strategy; 09.05 — feedback_superpowers_hard_rule/feedback_plugin_paired_stack; 10.05 — project_supplier_integration; 11.05 — project_claude_brain; 12.05 — project_portal_redesign/project_dev_indices; 13.05 — project_full_audit_2026-05-13; 15.05 — project_ruflo_integration. mtime: feedback_environment/project_state/reference_archive/reference_github 15.05, mem_ruflo 16.05, остальные — см. NODE_META.
  • ruflo runtime-узлы (9): big-bang 15.05.2026 (коммиты установки 55c49c9 и активации рантайма 5ac2961) → since: '15.05.2026', changed: '—'.
  • Связка-инструменты без проектного git-события: 14 скилов Superpowers + плагин superpowerssince 09.05.2026 (дата §12 hard-rule Pravila v1.4, presupposes установленный Superpowers), changed: '—'. fd_plugin/upm/claude_md_mgmt — 10.05.2026 (формализация в CLAUDE.md v1.78/v1.83/v1.84), changed: '—'. hookify_plugin + агенты ag_hookify/ag_pcreator/ag_pvalid/ag_skreview — надёжного источника нет → since: '—'. Встроенные агенты Claude Code (ag_explore/ag_general/ag_plan/ag_guide/ag_statusline) → since: 'встроено в Claude Code'. mcp_21st — глобальный (~/.claude.json, не в репозитории), since 10.05.2026 (формализация v1.83). hk_session/hk_economy — в глобальном ~/.claude/settings.json (не в репозитории): hk_economy since 10.05.2026 (документированная сборка 6-компонентной economy-архитектуры в feedback_superpowers_hard_rule.md); hk_session since: '—' (надёжной проектной записи нет).

А.3. Уточнения к спецификации §5.1 (выработаны при производстве значений)

Спецификация §5 явно делегировала производство значений плану; таблица §5.1 — это метод (a-priori допущения), которые план уточнил по факту:

  1. fd_plugin = 1 (не null). §5.1 относил плагины-обёртки к null. Но fd_plugin не имеет на карте дочерних узлов-скилов (в отличие от superpowers с 14 дочерними), напрямую вызывается как скил frontend-design:frontend-design и был вызван 1 раз. null отрендерил бы ложную фразу «узел не вызывается напрямую». → uses: 1.
  2. upm = 0 (не null). ui-ux-pro-max — напрямую вызываемый скил, измеримый, 0 вызовов в окне → uses: 0 (измеримо простаивает), а не null.
  3. superpowers, hookify_plugin = null — оставлены: подлинные контейнеры (использование superpowers распределено по 14 дочерним узлам-скилам; hookify_plugin работает через /hookify + дочерний агент ag_hookify).
  4. ruflo_daemon, ruflo_memory = null. §5.1 относил все ruflo runtime-узлы к 'инспекция' с числом. Но демон — автономный таймер-процесс (не «вызывается»), а память ruflo — пассивное хранилище (доступ — через ruflo_mcp/recall-хук, иначе двойной счёт). По определению null из §3.2 («узел не вызывается напрямую») — null корректен. Остальные 5 ruflo runtime-узлов измеримо-простаивают → 0 (спецификация §5.1 прямо приводит «idle-воркеры ruflo» как пример 0).
  5. hk_session/hk_economy — живут в глобальном ~/.claude/settings.json (вне репозитория), since не выводится из git; см. А.2.

А.4. Пороги теплокарты

Спецификация §7.3 («пороги уточняются в плане по фактическим значениям»). Фактическое распределение uses (диапазон 0…1334) подтверждает 4-ярусную раскладку §7.3 без изменений: ≥21 (часто) / 620 (иногда) / 15 (редко) / 0 (простаивает) / null (нет данных, нейтрально 0.5). Реализовано в heatOpacity (Task 3, Шаг 3).


Самопроверка плана

  • Покрытие спецификации. §3 (модель данных) → Task 1. §4 (секция «Паспорт») → Task 2. §6 (дубли D1D5/D7) → DUPLICATE_GROUPS в Task 1 + строка «Дубль» в Task 2; R-группы D6/D8/D9 → dupNote у 3 ruflo-узлов в Task 1. §7 (две кнопки + viewMode) → Task 3. §5 (методика чисел) → Приложение А + Task 1. §8 (честность/устаревание) → «срез 09–16.05.2026» в рендере (Task 2) + memory-фиксация (Task 4). §9 (8 критериев приёмки) → финальный smoke Task 4 Шаг 1. Пробелов нет.
  • Заглушек нет. Весь код Task 1–3 приведён целиком; NODE_META — 83 фактические записи; команды и ожидаемые выводы конкретны.
  • Согласованность типов. viewMode — строго null|'heat'|'dup' во всех точках. heatOpacity/heatBorderWidth/setViewMode определены в Task 3 Шаг 3 и там же используются. DUP_BY_NODE/DUP_NODE_SET/NODE_META/META_WINDOW определены в Task 1, используются в Task 2 (рендер) и Task 3 (heatOpacity, dup-ветка) — порядок определения раньше использования (SECTION 3.6 до SECTION 5/SECTION 8). state.viewMode читается снаружи IIFE через уже экспонированный HIGHLIGHT.state.
  • Объём. Одна цельная аддитивная итерация одного файла; декомпозиция на под-проекты не нужна.
  • Атомарность. 4 задачи = 4 коммита, каждый оставляет карту в рабочем состоянии (Task 1 — данные не используются; Task 2 — паспорт рендерится; Task 3 — кнопки активны; Task 4 — закрытие).