# Карта автоматизации 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` (стр. 1635–1770); 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`:

```javascript
// ════════════════════════════════════════════════════
// SECTION 3.6: NODE META (iter6 — даты, использование, дубли)
// ════════════════════════════════════════════════════
// Данные — фактический снимок: даты из git/changelog/mtime, счётчик uses —
// из разбора транскриптов сессий Claude Code за окно META_WINDOW.
// Методика и воспроизводимость — план iter6, Приложение А.
const META_SNAPSHOT = '16.05.2026';     // дата генерации значений
const META_WINDOW   = '09–16.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_navigate` → `http://localhost:8011/automation-graph.html`; затем `browser_evaluate`:

```js
() => ({
  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: коммит**

```bash
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` — программно прокликать репрезентативные узлы и снять состояние паспорта:

```js
() => {
  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` и `(скил)` и `срез 09–16.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: коммит**

```bash
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="Подсветить явные пары дублей (D1–D5, 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`:

```js
() => {
  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: коммит**

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

```js
() => {
  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: коммит**

```bash
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` — счётчик вызовов за окно 09–16.05.2026 (UTC)

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

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

```javascript
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_commands` — `0` по верифицированной инспекции рантайма iter5 (рой idle, 0 задач; каталог/команды/плагины не задействованы). `ruflo_daemon`/`ruflo_memory` — `null` (см. А.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 + плагин `superpowers` — `since` 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` (часто) / `6–20` (иногда) / `1–5` (редко) / `0` (простаивает) / `null` (нет данных, нейтрально 0.5). Реализовано в `heatOpacity` (Task 3, Шаг 3).

---

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

- **Покрытие спецификации.** §3 (модель данных) → Task 1. §4 (секция «Паспорт») → Task 2. §6 (дубли D1–D5/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 — закрытие).
