# Ruflo Memory H7 Fix + Advisory Hook — Design

**Дата:** 2026-05-15
**Статус:** Design (согласован в `superpowers:brainstorming`)
**Связанные документы:** `docs/superpowers/specs/2026-05-15-ruflo-integration-design.md`
(ruflo big-bang integration), memory `project_ruflo_integration.md`.

---

## 1. Цель и контекст

ruflo runtime активирован (daemon под PM2, hive-mind Queen + 9 agents, memory +
локальные embeddings `Xenova/all-MiniLM-L6-v2` 384-dim), но архитектурно остаётся
**parallel subsystem** — не интегрирован в интерактивную Claude Code-сессию: Claude
работает напрямую, ruflo daemon/hive-mind крутятся отдельным процессом рядом.

Цель — три деливерабла, приближающие ruflo к роли advisory-слоя над сессией:

- **D1.** Установить standalone `claude` CLI, чтобы `ruflo hive-mind spawn --claude`
  мог запускать реальную агентскую работу on-demand.
- **D2.** Починить баг **H7** — `ruflo memory` не персистит данные между
  process-invocations.
- **D3.** Создать `UserPromptSubmit` advisory-хук, инжектящий релевантную
  ruflo-память в каждый промпт интерактивной сессии.

### 1.1. Решения пользователя (приняты в brainstorming)

- Provider-ключ Anthropic в ruflo **не настраиваем** — непрерывный daemon-расход не
  нужен. Реальная работа агентов — через `spawn --claude` on-demand, который
  использует auth самого `claude` CLI (существующий Claude-план), а не отдельный
  ruflo-ключ и не отдельный $-поток.
- H7 чиним подходом **«патч `getBridge()`»** (см. §4) — выбран из 4 вариантов как
  единственный, верифицированный по исходнику.
- Порядок: D1 → D2 → D3.

### 1.2. Не-цели

- Настройка provider-ключа Anthropic в ruflo.
- Исправление самого AgentDB-v3 bridge или пакета `@claude-flow/memory` — H7 чиним
  **обходом** bridge'а, не его починкой.
- Установка native `better-sqlite3` (рассмотрена как альтернатива §4.4, отклонена).
- Авто-store хук (PostToolUse / Stop), наполняющий память автоматически — память
  наполняется вручную через `ruflo memory store` по уместности (YAGNI).
- Routing-классификация в хуке (`ruflo route` / Q-Learning) — Q-таблица на свежей
  установке не обучена, сигнал пустой; хук инжектит только memory-recall.
- Косметический баг namespace `undefined` (`ctx.flags.namespace` приходит
  `undefined` несмотря на `default: 'default'` в `commands/memory.js:38`) — отдельный
  дефект, вне scope этого дизайна.
- Превращение ruflo в буквальный entry-point/control-flow над Claude — архитектурно
  невозможно (`UserPromptSubmit`-хук умеет только инжектить контекст или блокировать
  промпт, не передавать управление). Хук даёт «advisory», не «routing».

---

## 2. H7 — корневая причина (верифицировано по исходнику)

Симптом: `ruflo memory store -k K -v V` рапортует `[OK] Data stored successfully`
(384-dim вектор, ID), но `.swarm/memory.db` не меняется (mtime/size константны), а
`memory list`/`retrieve`/`search`/`stats` в последующих вызовах возвращают пусто
(`Total Entries: 0`).

Цепочка (`@claude-flow/cli` в глобальной установке ruflo, `dist/src/`):

1. `ruflo memory store` → `storeEntry()` — `memory/memory-initializer.js:1777`.
2. `storeEntry` зовёт `getBridge()` — `memory-initializer.js:1779`. `getBridge`
   (`memory-initializer.js:71-84`) делает `await import('./memory-bridge.js')` —
   это **локальный sibling-модуль, импорт всегда успешен** → `getBridge()`
   практически всегда возвращает truthy bridge.
3. → `bridge.bridgeStoreEntry(options)` — `memory-initializer.js:1781`.
4. `bridgeStoreEntry` (`memory/memory-bridge.js:534`) поднимает AgentDB-v3
   `ControllerRegistry` через `import('@claude-flow/memory')`
   (`memory-bridge.js:86`); на этой машине загрузка успешна → `bridgeAvailable = true`
   (`memory-bridge.js:350`).
5. Вставка строки: `ctx.db.prepare(insertSql).run(...)` —
   `memory-bridge.js:593-594`. Код написан под **`better-sqlite3`** (синхронный
   `.run()`, комментарий `memory-bridge.js:570`). Native-бинарь `better-sqlite3` на
   этой машине не собран (нет build tools) → AgentDB падает на **sql.js (WASM,
   in-RAM)** — отсюда строка «✅ Using sql.js» в каждом выводе `memory`-команд.
6. `bridgeStoreEntry` возвращает `{ success: true, id, ... }`
   (`memory-bridge.js:602-610`) — **truthy, но нигде не экспортирует in-RAM
   WASM-буфер на диск**: ни `db.export()`, ни `writeFileRestricted` в функции нет.
   Контраст — raw-fallback `storeEntry` явно делает `const data = db.export();
   writeFileRestricted(...)` на `memory-initializer.js:1858-1859`.
7. Раз `bridgeStoreEntry` вернул truthy — `storeEntry` делает ранний `return
   bridgeResult` на `memory-initializer.js:1793`. **Корректный персистящий
   raw-sql.js путь (`memory-initializer.js:1796-1875`) недостижим — он «затенён»
   bridge'ом.**
8. Процесс CLI завершается → WASM-память освобождается → строка пропадает.
   Следующий `ruflo memory <cmd>` — новый процесс, новый пустой in-RAM реестр.

`memory init` работает (создаёт `.swarm/memory.db` 144 KB) потому что
`initializeMemoryDatabase` пишет файл напрямую (`fs.writeFileSync` /
`writeFileRestricted` + `copyFileSync` в `.claude/memory.db`), не через bridge.

**Итог H7:** AgentDB-v3 bridge перехватывает каждую memory-операцию
(`store`/`get`/`list`/`search`/`delete` — все имеют паттерн `getBridge()`-first),
пишет в in-RAM sql.js и никогда не флашит на диск; рабочий персистящий raw-sql.js
путь затенён.

### 2.1. Гипотезы (systematic-debugging) — статус

| Гип. | Формулировка | Статус |
|---|---|---|
| A | `store` пишет в другой файл (stray `.db` / `ruvector.db`) | **Фальсифицирована** — `find` нашёл только `.swarm/memory.db` + `ruvector.db`; `stat` обоих через T0/T1/T2 вокруг `store` — не изменились ни разу |
| B | sql.js не экспортится на диск | **Уточнена и подтверждена** — no-export реален, но в `bridgeStoreEntry`, не в raw-пути (raw-путь экспортит корректно) |
| C | Нужен running memory-сервер | **Фальсифицирована** — `store` делает реальную in-process работу (embedding, ID) |
| D | namespace `undefined` ломает запись | **Фальсифицирована как корень** — отдельный косметический баг; namespace-слепой `list` тоже пуст, т.к. на диск ничего не пишется |
| E | Незакоммиченная транзакция | **Схлопывается в B** — у sql.js «commit» = `db.export()` + запись файла |

---

## 3. D1 — `claude` CLI + spawn-capability

### 3.1. Проблема

`ruflo doctor` сообщает «⚠ Claude Code CLI: Not installed». На машине Claude Code
работает через VSCode-расширение; отдельного бинаря `claude` на PATH нет (`claude
--version` → command not found). `ruflo hive-mind spawn --claude` запускает `claude`
Code CLI как подпроцесс (`--claude  Launch Claude Code with hive-mind coordination
prompt`) → без бинаря путь «реальной работы агентов» недоступен.

### 3.2. Решение

1. `npm i -g @anthropic-ai/claude-code` — установить standalone `claude` CLI.
2. Верификация: `claude --version` возвращает версию; `ruflo doctor` больше не
   выдаёт «Claude Code CLI: Not installed».
3. Smoke `spawn`: `ruflo hive-mind spawn --claude --no-auto-permissions` на
   тривиальной задаче. Флаг `--no-auto-permissions` обязателен — по умолчанию
   `spawn --claude` идёт с `--dangerously-skip-permissions` (default true),
   spawned-агент пропускает все permission-промпты (автономно правит репозиторий) —
   для CRM-проекта это недопустимо без явного решения.

### 3.3. Открытые вопросы D1

- **Auth `claude` CLI** — не верифицировано, подхватит ли свежеустановленный
  standalone CLI существующие credentials из `~/.claude/` (общие с VSCode-расширением)
  или потребует интерактивный `claude login`. Проверяется в ходе D1; если нужен
  `login` — это user-action (интерактив, не автоматизируется).
- Реальный `spawn --claude` потребляет ёмкость существующего Claude-плана
  пользователя — smoke делается на минимальной задаче.

---

## 4. D2 — Фикс H7: патч `getBridge()`

### 4.1. Суть

Сделать `getBridge()` в `memory-initializer.js` всегда возвращающим `null`. Тогда
`storeEntry`/`getEntry`/`searchEntries`/`listEntries`/`deleteEntry` пропускают
bridge-ветку и выполняют свои **raw-sql.js fallback-пути**, которые корректно
персистят (`db.export()` + `writeFileRestricted` — например
`memory-initializer.js:1858-1859` для `store`, `:2196-2197` для `get`,
`:2325-2326` для `delete`).

### 4.2. Патч

Файл: `<npm-root-g>/ruflo/node_modules/@claude-flow/cli/dist/src/memory/memory-initializer.js`.

Текущая функция (`:71-84`):

```js
let _bridge;
async function getBridge() {
    if (_bridge === null)
        return null;
    if (_bridge)
        return _bridge;
    try {
        _bridge = await import('./memory-bridge.js');
        return _bridge;
    }
    catch {
        _bridge = null;
        return null;
    }
}
```

Патч — вставка одной строки первой в тело функции:

```js
async function getBridge() {
    return null; /* LIDERRA-H7-PATCH: bridge writes to in-RAM sql.js, never flushes — force raw persisting path */
    if (_bridge === null)
```

Маркер `LIDERRA-H7-PATCH` служит признаком идемпотентности для re-apply-скрипта.
Остальное тело функции остаётся как мёртвый, но безвредный код (не переписываем —
минимизируем diff и риск рассинхрона с upstream при будущих сверках).

### 4.3. Re-apply-механизм

Патч лежит в `node_modules` **глобальной** установки ruflo — он не под git проекта
и **молча затирается** при `ruflo update` / `npm i -g ruflo`. Поэтому:

- Скрипт `tools/ruflo-h7-patch.mjs` (в репо проекта, под git):
  - Резолвит путь к `memory-initializer.js`: `npm root -g` → кандидаты
    `<root>/ruflo/node_modules/@claude-flow/cli/dist/src/memory/memory-initializer.js`
    и `<root>/@claude-flow/cli/.../memory-initializer.js` (на случай hoist).
  - Читает файл; если содержит маркер `LIDERRA-H7-PATCH` — no-op (идемпотентно).
  - Если маркера нет — находит якорь `async function getBridge() {` и вставляет
    строку патча сразу за ним; пишет файл обратно.
  - Если якорь **не найден и маркера нет** — выходит с ненулевым кодом и сообщением
    (upstream изменил функцию — патч пересмотреть вручную, не молчать).
  - Флаг `--revert` — снимает патч (удаляет строку с маркером).
  - Флаг `--check` — только проверка статуса (exit 0 patched / exit 1 unpatched),
    для CI/диагностики.
- Запуск — **вручную** после каждого `ruflo update`. Документируется в memory
  `project_ruflo_integration.md` (operational note) и, опционально, в
  `Tooling_v8_3.md` §4.10.
- `patch-package` не используется — он рассчитан на проектный `./node_modules`, а не
  на глобальную установку.

### 4.4. Отклонённые альтернативы

- **better-sqlite3 native** — bridge написан под better-sqlite3 (синхронная запись
  на диск); собранный native-бинарь убрал бы H7 без правки исходника. Отклонено:
  native-build на Windows + Node 24 невыполнимости не гарантирован (параллель с
  сагой sharp/libvips при активации embeddings).
- **MCP-сервер (session-scoped)** — `memory_*` через долгоживущий MCP-процесс.
  Отклонено: персист только на время жизни сервера, cross-session нет.
- **Обойти ruflo memory вовсе** — отклонено: пользователь выбрал чинить H7.

### 4.5. Граница scope D2

Патч `getBridge()` в `memory-initializer.js` покрывает **CLI-путь** memory-операций
(`ruflo memory store/get/list/search/delete`), который и использует хук D3.
**Не верифицировано**, разделяют ли MCP-инструменты `memory_*`
(`dist/src/mcp-tools/memory-tools.js`) тот же модуль `memory-initializer.js` или
имеют отдельный bridge-путь. Если в будущем понадобятся MCP memory-инструменты —
проверить и при необходимости расширить патч; в текущем дизайне MCP memory-путь вне
scope.

---

## 5. D3 — Advisory-хук

### 5.1. Назначение

`UserPromptSubmit`-хук, который на каждый промпт интерактивной сессии достаёт из
ruflo-памяти релевантные записи (semantic search) и инжектит их в контекст как
`additionalContext`. Это сдвигает ruflo из «parallel» в «integrated advisory» —
Claude видит припомненный контекст, но решение остаётся за Claude (не routing).

### 5.2. Компоненты

- **`tools/ruflo-recall-hook.mjs`** (в репо, под git) — Node-скрипт хука.
- Регистрация в проектном **`.claude/settings.json`** → `hooks.UserPromptSubmit`
  (не глобально — хук специфичен для этого проекта/ruflo-установки).

### 5.3. Data flow

1. Пользователь отправляет промпт → Claude Code запускает `UserPromptSubmit`-хуки.
2. `ruflo-recall-hook.mjs` получает на stdin JSON с полем `prompt`.
3. Хук спавнит `ruflo memory search -q "<prompt>" --format json -l 3` с таймаутом.
4. После фикса D2 `search` читает `.swarm/memory.db` (персистящий raw-путь) и
   возвращает топ-хиты JSON-ом.
5. Хук форматирует хиты в короткий текстовый блок.
6. Хук пишет в stdout:
   `{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"<блок>"}}`,
   exit 0.
7. Claude видит припомненный контекст в этом turn.

### 5.4. Обработка ошибок — fail-open (жёсткое требование)

Хук **никогда не ломает сессию**:

- Таймаут вызова `ruflo` (~3000 мс) — kill подпроцесса, инжект пустой.
- `ruflo` вернул ошибку / невалидный JSON / 0 результатов — инжект пустой.
- Любое исключение в скрипте — поймать, exit 0, инжект пустой.
- Хук **не использует** `decision: "block"` — промпт всегда проходит.

«Инжект пустой» = либо `additionalContext: ""`, либо вывод без `hookSpecificOutput`
(хук просто завершается exit 0 без JSON).

### 5.5. Стоимость и ограничения D3

- **Латентность:** per-prompt вызов `ruflo` CLI = Node + sql.js WASM init →
  ~1-3 с задержки на КАЖДЫЙ промпт. Таймаут ограничивает худший случай.
- **Coexistence:** в глобальном `~/.claude/settings.json` уже есть
  `UserPromptSubmit`-хук (economy-mode). Хуки независимы — оба срабатывают, оба
  инжектят свой `additionalContext`. Конфликта нет.
- **Ценность растёт с накоплением данных:** на свежей памяти recall пуст; полезным
  хук становится по мере наполнения `.swarm/memory.db`.

### 5.6. Write-сторона

Память наполняет Claude вручную — вызовом `ruflo memory store -k <key> --value
<val>` (после D2 он персистит), когда в сессии есть что запомнить. Авто-store хук —
не строим (YAGNI; при потребности — отдельная итерация).

---

## 6. Последовательность и зависимости

```
D1 (claude CLI)  ──independent──┐
D2 (H7 patch)    ───────────────┤
                                ▼
D3 (advisory hook) — зависит от D2 (хук бесполезен без персистящей памяти)
```

Порядок исполнения: **D1 → D2 → D3**. D1 первым — это «шаг 1» по постановке
пользователя; D2 строго до D3.

---

## 7. Верификация

### D1

- `claude --version` → версия выведена.
- `ruflo doctor` → нет строки «Claude Code CLI: Not installed».
- `ruflo hive-mind spawn --claude --no-auto-permissions` на тривиальной задаче →
  spawned-инстанс отработал (smoke).

### D2

- `tools/ruflo-h7-patch.mjs` — round-trip:
  - запуск на чистой установке → патч наложен, маркер присутствует;
  - повторный запуск → no-op (идемпотентность);
  - `--check` → exit 0;
  - `--revert` → маркер удалён, `--check` → exit 1.
- Эффект патча — **cross-process round-trip**: `ruflo memory store -k h7-verify -v
  "..."` → (новый процесс) `ruflo memory retrieve -k h7-verify` → запись найдена;
  `ruflo memory stats` → `Total Entries ≥ 1`; mtime `.swarm/memory.db` изменилась.
- `ruflo memory delete -k h7-verify` — cleanup verify-записи.

### D3

- Submit промпта → в контексте turn появляется блок recall (когда в памяти есть
  релевантные записи).
- **Fail-open тест:** временно сломать `ruflo` (переименовать бинарь / недостижимый
  PATH) → submit промпта → промпт проходит, сессия не падает, инжект пустой.
- Таймаут-тест: убедиться, что зависший `ruflo` убивается по таймауту и хук
  завершается exit 0.

### Регрессия

- ruflo daemon worker-jitter усиливает Pest quirk 72 (memory `feedback_environment.md`
  квирк #93) — при прогоне baseline-регрессии `pm2 stop ruflo-daemon`. D1/D2/D3 не
  меняют код приложения (`app/`), schema или тесты — отдельный Pest/Vitest-прогон не
  требуется; затрагиваются только ruflo-инфраструктура, `.claude/settings.json`,
  `tools/`.

---

## 8. Риски и ограничения

- **Фрагильность патча D2** — патч в глобальном `node_modules` теряется при `ruflo
  update`. Митигировано re-apply-скриптом + документированием; остаётся ручной шаг.
- **`claude` CLI auth (D1)** — не верифицировано, нужен ли `claude login`
  (см. §3.3).
- **Латентность хука (D3)** — ~1-3 с на каждый промпт (см. §5.5).
- **ruflo alpha** — raw-sql.js путь верифицирован чтением исходника (персистит), но
  его runtime-корректность подтверждается только тестом round-trip в D2; возможны
  иные alpha-баги в raw-пути, не выявленные чтением.
- **MCP memory-путь** — не верифицирован (см. §4.5).
- **Намеренно не чиним** косметический баг namespace `undefined` (§1.2).

---

## 9. Откат

- **D1:** `npm uninstall -g @anthropic-ai/claude-code`.
- **D2:** `node tools/ruflo-h7-patch.mjs --revert`, либо `npm i -g ruflo` (чистая
  переустановка перетирает патч).
- **D3:** удалить `UserPromptSubmit`-блок из `.claude/settings.json`; удалить
  `tools/ruflo-recall-hook.mjs`.

---

## 10. Артефакты

| Артефакт | Путь | Под git |
|---|---|---|
| Re-apply-скрипт патча H7 | `tools/ruflo-h7-patch.mjs` | да |
| Скрипт advisory-хука | `tools/ruflo-recall-hook.mjs` | да |
| Регистрация хука | `.claude/settings.json` (`hooks.UserPromptSubmit`) | да |
| Патч H7 | `<npm-root-g>/.../memory-initializer.js` | нет (node_modules) |
| Operational-нота | memory `project_ruflo_integration.md` | нет (личная память) |
