diff --git a/.claude/commands/diary.md b/.claude/commands/diary.md index a06abb2..3e92047 100644 --- a/.claude/commands/diary.md +++ b/.claude/commands/diary.md @@ -1,14 +1,14 @@ Запиши дневниковую запись текущей сессии. 1. Определи номер сессии: - - Посмотри файлы в `~/.claude/projects/D--code-2026-2-cortex/memory/diary/` + - Посмотри файлы в `memory/diary/` (в корне репо) - Следующий номер = max существующий + 1 (или 001 если пусто) 2. Проанализируй что было сделано в этой сессии: - Просмотри историю разговора (сообщения, tool calls, изменённые файлы) - Если контекст уже сжат — используй `git log --oneline -20` и `git diff` -3. Создай файл: `~/.claude/projects/D--code-2026-2-cortex/memory/diary/NNN_YYYY-MM-DD.md` +3. Создай файл: `memory/diary/NNN_YYYY-MM-DD.md` Формат (строго): ``` diff --git a/.claude/commands/heartbeat.md b/.claude/commands/heartbeat.md deleted file mode 100644 index 67b84c0..0000000 --- a/.claude/commands/heartbeat.md +++ /dev/null @@ -1,52 +0,0 @@ -Запусти Heartbeat — сканирование AI/Tech трендов и генерация дайджеста. - -## Prerequisites - -Перед запуском проверь что .env содержит нужные ключи: -```bash -grep -q "GOOGLE_API_KEY" D:/code/2026/2/cortex/.env && echo "OK: GOOGLE_API_KEY" || echo "MISSING: GOOGLE_API_KEY — нужен для Gemini" -grep -q "GITHUB_TOKEN\|GH_TOKEN" D:/code/2026/2/cortex/.env && echo "OK: GITHUB_TOKEN" || echo "OPTIONAL: GITHUB_TOKEN — для GitHub trending (работает и без)" -``` -Если GOOGLE_API_KEY отсутствует — скажи пользователю и останови. - -## Инструкция - -### Шаг 1 — Сбор данных - -Выполни Python-скрипт для сбора трендов: - -```bash -export PATH="/c/Users/User/.local/bin:$PATH" && cd /d/code/2026/2/cortex/tools/heartbeat && uv run python main.py --mode fetch -``` - -Скрипт выведет сырые данные: топ посты Hacker News, трендовые репозитории GitHub. - -### Шаг 2 — Контекст - -Прочитай CURRENT_CONTEXT.md чтобы понять текущие проекты и интересы. - -### Шаг 3 — Анализ - -На основе сырых данных и контекста проекта: -1. Выдели 5-10 самых релевантных трендов -2. Объясни почему каждый тренд важен (1 предложение) -3. Предложи конкретные задачи для /dispatch - -### Шаг 4 — Вывод - -Формат: - -``` -## Heartbeat [дата] - -### Тренды -1. **[Trend]** — [почему важно] ([ссылка]) - - Actionable: [задача для агента] - -### Рекомендуемые действия -- [ ] [задача для /dispatch] (агент: Jules/Codex, размер: S/M/L) - -### Источники -- HN: X постов проанализировано -- GitHub: Y репозиториев -``` diff --git a/.claude/commands/new-project.md b/.claude/commands/new-project.md deleted file mode 100644 index baf2bcc..0000000 --- a/.claude/commands/new-project.md +++ /dev/null @@ -1,31 +0,0 @@ -Инициализация нового проекта на базе шаблона Cortex. - -Эта команда автоматизирует создание нового репозитория с сохранением всей инфраструктуры Cortex (хуки, воркфлоу, команды), но с очищенным контентом. - -### Процесс -1. Спроси у пользователя: - - **Название проекта** - - **Описание** (цель проекта) - - **Стек** (предложи варианты: Python/FastAPI, Next.js/TypeScript, Node.js/Express) - - **Путь** (по умолчанию: D:/code/2026/<название>) - -2. Выполни скрипт инициализации: - ```bash - uv run --project tools/scaffold python tools/scaffold/main.py \ - --name "<название>" \ - --description "<описание>" \ - --stack "<стек>" \ - --target "<путь>" - ``` - -3. После успешного завершения: - - Сообщи пользователю, что проект создан. - - Дай инструкции по переходу в новую папку и запуску `claude`. - - Напомни заполнить `CURRENT_CONTEXT.md` в новом проекте. - -### Что будет сделано -- Копирование структуры `.claude/`, `.github/`, `tools/`, `docs/`. -- Очистка `CURRENT_CONTEXT.md` (замена на шаблон). -- Создание чистого `README.md`. -- Удаление Cortex-специфичных данных и самого инструмента `tools/scaffold` из целевой папки. -- Инициализация Git и первый коммит. diff --git a/.claude/commands/reflect.md b/.claude/commands/reflect.md index c28e989..28fe48a 100644 --- a/.claude/commands/reflect.md +++ b/.claude/commands/reflect.md @@ -1,8 +1,8 @@ Синтезируй паттерны из дневниковых записей в правила. -1. Прочитай все файлы в `~/.claude/projects/D--code-2026-2-cortex/memory/diary/` +1. Прочитай все файлы в `memory/diary/` (в корне репо) 2. Прочитай текущий `CLAUDE.md` -3. Прочитай `~/.claude/projects/D--code-2026-2-cortex/memory/reflections/processed.log` (если есть) — пропусти уже обработанные +3. Прочитай `memory/reflections/processed.log` (в `~/.claude/projects/D--code-2026-2-cortex/memory/reflections/` — пока индекс и reflections живут там) — пропусти уже обработанные 4. Найди паттерны в необработанных записях: @@ -21,7 +21,7 @@ **E. Эффективные workflow** - Что экономит время -5. Создай рефлексию: `~/.claude/projects/D--code-2026-2-cortex/memory/reflections/YYYY-MM-reflection-N.md` +5. Создай рефлексию: `~/.claude/projects/D--code-2026-2-cortex/memory/reflections/YYYY-MM-reflection-N.md` (пока reflections живут там) 6. Обнови `CLAUDE.md`: - Новые правила — краткие, императивные, одна строка diff --git a/.claude/hooks/pre-compact.py b/.claude/hooks/pre-compact.py index 8a94613..54e80bd 100644 --- a/.claude/hooks/pre-compact.py +++ b/.claude/hooks/pre-compact.py @@ -12,14 +12,8 @@ from datetime import datetime, timezone, timedelta from pathlib import Path -MEMORY_DIR = Path(os.environ.get( - "CLAUDE_PROJECT_DIR", - r"D:\code\2026\2\cortex" -)).parent.parent / ".claude" / "projects" / "D--code-2026-2-cortex" / "memory" / "diary" - -# Fallback: try project-local memory PROJECT_DIR = Path(os.environ.get("CLAUDE_PROJECT_DIR", r"D:\code\2026\2\cortex")) -DIARY_DIR = Path(r"C:\Users\User\.claude\projects\D--code-2026-2-cortex\memory\diary") +DIARY_DIR = PROJECT_DIR / "memory" / "diary" def get_next_number() -> int: diff --git a/.gitignore b/.gitignore index b6bd951..09ff300 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,8 @@ video_output/ # Screenshots screenshots/ screen/ +scripts/*.png +scripts/.claude/ # Claude Code local settings (permissions, not for sharing) .claude/settings.local.json @@ -85,3 +87,9 @@ tools/kwork-monitor/covers/ # Lock files (runtime) *.lock !uv.lock + +# Huashu-Design BGM tracks (27MB, restore via scripts/fetch_huashu_bgm.sh) +.claude/skills/huashu-design/assets/bgm-*.mp3 + +# Huashu-Design skill (external repo, fetch via scripts/fetch_huashu_bgm.sh or git clone) +.claude/skills/huashu-design/ diff --git a/CLAUDE.md b/CLAUDE.md index d078b43..e34533c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,11 +25,15 @@ bash scripts/ops.sh secrets # секреты в tracked files .claude/ → commands, skills, hooks, agents (платформа) tools// → самодостаточные продукты (свой .venv, pyproject.toml) docs/ → устойчивые правила (git-flow, python-rules, verify) -memory/ → diary, MEMORY.md, topic files +memory/ → diary + subagents-playbook (в git) runtime/ → gitignored, сырые данные archive/ → старые контексты (читать по запросу) ``` +> `MEMORY.md`, feedback/project/user/reference/topic файлы и `reflections/` живут +> в `C:\Users\User\.claude\projects\D--code-2026-2-cortex\memory\` — per-user, +> автозагружаются в контекст, не версионируются в git. + ## Conventions - Плоская структура, минимум зависимостей. Код объясняет себя сам. @@ -39,7 +43,6 @@ archive/ → старые контексты (читать по зап - Ошибки: сначала причина, затем фикс. - Conventional Commits: `(): `. См. `docs/git-flow.md` - **SSOT**: каждый факт живёт в ОДНОМ файле. Остальные ссылаются, не дублируют. -- **L0 заголовки**: первая строка каждого .md — ``. Для навигации: `grep " +# Session 017 — 2026-04-17 + +## Что сделано + +### VoiceType — два фикса + +**1. Autostart не поднимался после включения компа.** +Лог: `Error querying device -1`. USB-микрофон DJI не успевал проиниться до запуска pythonw. +- `voice_type/main.py:51-67`: retry-loop 30 попыток × 1 сек вокруг `check_microphone_access()`, первый фейл уходит в WARNING а не EXIT. +- `scripts/voicetype.vbs`: `WScript.Sleep 10000` перед стартом pythonw — 10-секундный буфер на инициализацию USB. + +**2. Paste длинных сообщений в Claude Code 4.7 дробил одно аудио на несколько сообщений.** +Причина: Gemini cleanup-промпт инструктировал "add blank lines between logical blocks" → `\n\n` в тексте → paste.py делил по `\n` и между кусками жал Shift+Enter → Claude Code 4.7 интерпретировал Shift+Enter как submit. +- `voice_type/llm.py:28`: промпт теперь требует "single paragraph, no blank lines". +- `voice_type/paste.py`: убрал split+Shift+Enter, делаю один `paste` целиком. Нормализую `\n\s*\n+` → `\n` как защиту даже если Gemini проигнорит. Современные чаты submit'ят только на Enter KeyPress, pasted `\n` безопасны. + +### Рабочий стол — чистка +- `Chrome CDP.lnk` → `runtime/shortcuts/` (gitignored, но остаётся в Cortex-репо под рукой) +- `Google Chrome.lnk`, старые фотки/аудио/видео, `sync_client.py` (устарел — на мамином ноуте живёт `sync_standalone.py`), lock-pdf — удалено +- Оставил: `STEAM_SNIPER_CODEX_BRIEFING.md` (пока нужен), `Yoga-Isometric/`, `практики 2026/`, `desktop.ini` + +### Steam Sniper Phase 11 — Item Detail Page + +**Контекст:** Максим-транскриптом разобрал голосовое Лёхи от 15.04 19:24 (3:23). Просьба: клик по скину открывает детали как на lis-skins.com — все листинги с ценами, флоатом, стикерами. + +**Как работали параллельно с Codex:** +Я начал делать фичу, не переключив ветку — сидел прямо на `codex/steam-sniper-extension-ip`, не зная что Codex там же пишет ту же задачу. Codex запушил раньше (`c4dcd7b feat: restore images and add cached item detail`). Мои файлы (`item_detail.js`, CSS, модалка в HTML) через мерж оказались **идентичны** его — реально я переоткрыл тот же путь решения. Единственное моё отличие — фикс Decimal-бага в `_slim_listing` (у Codex endpoint возвращал пустые листинги из-за `Object of type Decimal is not JSON serializable`). Закоммитил как `651e149`. + +**Lesson:** перед крупной работой в репе с множеством авторов — `git status`, `git log origin`, `git branch --show-current`. Иначе рискуешь пересоздавать существующий код. + +Потом сверху накатил собственные доработки (`3b9b824`): async endpoint с `pending:true` мгновенно + polling с backoff на фронте + warm-cache loop раз в 10 мин + `name_tag`/StatTrak/Souvenir/★ бейджи + wear-tier pills + sticker-wear %. Смержили в main (`9b2dc22`), запушили. + +**Но UX всё равно был плохой:** спиннер "первый разбор lis-skins — до 30с" висел минутами. Замер на VPS показал: `api_csgo_full.json` весит **818 МБ** (не 30-80 как я в архитектуре считал). Warm-loop на 1 vCPU просто не успевал, а cold-click через один проход 818 МБ = минуты CPU. + +Написал брифинг `STEAM_SNIPER_PERFORMANCE_BRIEFING.md` для Codex с тремя вариантами (локальный snapshot / найти лёгкий endpoint / апгрейд VPS). Codex взял **Вариант 1** (snapshot) и сделал за 43 минуты: +- `listings_snapshot.py` — build+read API для SQLite +- `scripts/build_listings_snapshot.py` — CLI обёртка +- `scripts/sync_listings.ps1` — PowerShell билд+scp +- В `server.py` выкинул всё async/pending/warm (−289 строк) — endpoint теперь прямой SQLite SELECT +- Во фронте выкинул polling (−86 строк) +- `deploy.py` добавлен в project files `listings_snapshot.py` + `data/listings_snapshot.db` +- Тесты `test_listings_snapshot.py` (+84) + +Замеры Codex: build 1 732 808 листингов за 42.2 сек локально, cold-click на VPS ~356 мс (было минутами). Смержил `d56f081` в main, запушил. + +### Snapshot pipeline на cortex-vm + +Пользователь попросил перенести регулярную пересборку snapshot с его компа на `cortex-vm` (e2-small, 2 vCPU burstable / 2 GB RAM). VM была недоступна (SSH/HTTP молчали при RUNNING статусе) → `gcloud compute instances reset` → поднялась. + +Что сделано: +- Обновил `~/cortex` до `main` (+47 коммитов) +- `pip install --user ijson` +- Сгенерил `~/.ssh/vps_key` на VM, добавил публичный ключ в `authorized_keys` на VPS `72.56.37.150` +- Скрипт `~/sync_steam_sniper_snapshot.sh` — git pull → build → scp atomic → mv на VPS +- Cron `0 */4 * * *` — каждые 4 часа +- Первый прогон: 1 866 205 листингов, 648 МБ, **38 сек** на VM. VPS `/api/debug`: `built_at: 2026-04-17T07:39:36`, `available: true` + +**Временная схема — cortex-vm заканчивается в мае.** Тогда купим новый VPS (2 vCPU / 2+ GB RAM, ~1000-1400 ₽/мес у Timeweb) и перенесём туда весь pipeline (10 минут работы). + +## Что заметил + +- **Git-дисциплина vs пет-моно-репо.** Пользователь спросил "нахуя вся эта ебала с GitHub". Честный ответ: 80% git-фич ему лишние как одному разработчику. Главное зачем GitHub — канал координации с Codex (он иначе никак не синхронизируется). Ветки нужны только когда идём параллельно. Пушить в main — норм. +- **Параллельная работа с Codex.** Когда я и Codex делаем одну задачу, я должен ПЕРВЫМ проверить `git log origin/` перед началом, чтобы не дублировать. Ветка `codex/steam-sniper-extension-ip` содержала уже готовую фичу когда я начал её заново делать. +- **Doubting subagent output.** Researcher-агент написал "авторизация не требуется для поиска" по `api.lis-skins.com/v1/market/search`. Проверил curl'ом — `{"error":"missing_api_key"}`. Lessons Learned вчерашний: "Не доверять слепо." — пригодился. +- **Размер данных реально меряй.** Я оптимистично оценил JSON в 30-80 МБ. Фактически — 818 МБ. Один `curl -o` на VPS заменил бы всё архитектурное гадание. Всегда меряй до. + +## Что в следующий раз + +- Когда будет настроение — потестить VoiceType в Max Web / Telegram Web, не сломался ли paste без split'а (в теории не должен, pasted `\n` не триггерит submit на современных вебах). +- Когда cortex-vm закроется — апгрейд VPS и переезд snapshot-pipeline. +- Фронт item-detail: можно добавить pagination ("показать ещё 20") — сейчас всегда top-20. Не срочно. + +## Metrics + +- Коммиты в main: 4 (мои + Codex) — `a833f77`, `c4dcd7b`, `651e149`, `3b9b824`, `9b2dc22` (merge), `d56f081` +- Зависимости добавлены: `ijson>=3.5.0` +- Deploy на VPS: 3 раза (image_cache от Codex, мой патч async/warm, snapshot от Codex) +- Время сессии: с ~07:00 MSK, ~3.5 часа на все темы diff --git a/memory/diary/018_2026-04-18.md b/memory/diary/018_2026-04-18.md new file mode 100644 index 0000000..bec1b3f --- /dev/null +++ b/memory/diary/018_2026-04-18.md @@ -0,0 +1,114 @@ + +# Session 018 — 2026-04-18 — Steam Sniper UI-батч по фидбеку Лёхи + +## Что сделано + +### Транскрипции голосовых Лёхи (4 штуки за день) + +Один голос (11:45, 1:25) → 4 задачи из голоса: wishlist hit area, картинка скина в detail, белая тема, StatTrak-фильтр. Плюс текст 12:09: ещё раз белая тема + «обычное/StatTrak со счётчиком убийств» в фильтрах. + +16:14 (3:11) — детальный разбор карточки с lis-skins: wear-tier tabs, цена+скидка+Steam price, rarity «Тайное» подсвечивать, интерактивный float (клик по заголовку = сортировка), кнопка «Посмотреть на lis-skins» (а не Steam inspect), русский + английский title сверху. + +16:15 (1:33) — левая колонка каталога → на русский, английский в скобках опционально. Причина: «при монотонной работе быстрее знакомое слово видеть, не приходится каждый раз переключать внимание». + +17:13 (0:55) — **главный upsell на сегодня:** в карточке предмета (когда он в избранном/хотелках) добавить два поля — 🔴 buy-target и 🟢 sell-target — с уведомлением в TG при пересечении. На будущее — web-push, потому что TG у Лёхи через VPN. + +Пара бытовых голосовых (едет домой, про второй номер телефона) — проигнорил. + +### Steam Sniper UI-батч (12+ правок, всё на прод) + +**1. Wishlist ★/♥ кнопки** — были 22×22 с тонкими символами без чёткого состояния. Стали 34×34 с заливкой при active (красный для fav, жёлтый для wish), hover-подложка, scale-анимация, `z-index:2` у контейнера чтобы клик не проваливался в карточку. + +**2. Light theme** — CSS-variables уже были, добавил `[data-theme="light"]` с белыми карточками + `--overlay-bg`/`--grid-tint`/`--divider`/`--chip-bg`/`--img-shadow-alpha` чтоб заменить захардкоженные rgba. Toggle ☾/☀ в header, inline bootstrap в `` (без FOUC). В light тени drop-shadow ослабил с 0.4 до 0.18 — Лёха сразу заметил «тени слишком сильные». + +**3. Фильтр StatTrak/Souvenir/Normal** — в `/api/catalog` новый param `state`, в catalog.js dropdown. + +**4. Картинки скинов** — фикс `_get_item_image`: первичный fallback уже был (strip wear → strip State-префиксы). Добавил третий — **strip Doppler phase/gem suffix**: «Doppler Phase 1/2/3/4», «Ruby», «Sapphire», «Black Pearl», «Emerald». ByMykel хранит Doppler как один ключ на нож, lis-skins продаёт каждую фазу отдельным предметом. **486 → 0 ножей без фото** (из 3519). + +**5. Tab «Кейсы»** — два бага разом: +- Скины `AK-47 | Case Hardened` попадали в кейсы, потому что в `category.py` проверка `"Case" in clean` шла ДО weapon-lookup. Перенёс порядок + ограничил матч на base-часть до `|`. +- В `cases.js` картинки строились как `STEAM_IMG_BASE + item.url` (где url — lis-skins HREF) → 404. Заменил на `item.image` как в catalog.js. + +**6. Cache-Control** — браузер Chromium использует эвристический freshness и кэширует JS без revalidation. Middleware в server.py: `/static/*` и `/` теперь отдаются с `Cache-Control: no-cache, must-revalidate` → браузер всегда делает conditional GET с ETag. + +**7. Model-filter для ножей** — добавил `knife` в `_MODEL_CATEGORIES` (server.py) и `MODEL_FILTER_CATEGORIES` (catalog.js). Тест `_weapon_model()`: для `★ StatTrak™ Karambit | Fade (FN)` → `Karambit`. На проде — 21 модель (Bayonet 211, Flip 211, Falchion 207, Butterfly 205, Karambit 203…). + +**8. Hero-карточки** — Портфель/Дельта/Позиции → Избранное/Хотелки/Алерты. На трёх параллельных fetch'ах: `/api/lists?type=favorite`, `.../wishlist`, `/api/watchlist`. Третья карточка считает: +- `buyReady`: watchlist.buy items где `current_price_rub ≤ target_rub` +- `sellReady`: watchlist.sell items где `current_price_rub ≥ target_rub` +Карточка меняет рамку: красная / зелёная / оранжевая если оба. + +**9. UI-polish:** +- Input[type=number] — глобально скрыл нативные spinners +- .detail-filter select — добавил `appearance:none` + SVG-стрелку (тот же паттерн что у .catalog-sort/.form-group), нейтральный `#64748b` — работает в обеих темах + +**10. Русификация каталога** — `CAT_LABELS`: `Rifles/Knives/...` → `Винтовки/Ножи/...`. Плюс `dashboard.html` русифицировал заголовки sort-options, поиска. + +### Брифинги для Codex (детализированные) + +**`STEAM_SNIPER_CODEX_ITEM_DETAIL_EXPAND.md`** (426 строк) — расширение item detail по скрину Лёхи + голосовым 16:14/16:15: +- Инфра-блок с точными командами для cortex-vm (`gcloud compute ssh`, scp) и VPS (`~/.ssh/vps_key`, `paramiko`) +- Отдельно различие `deploy.py` (полный, затирает `listings_snapshot.db`) vs `deploy_quick.py` +- 10 пунктов A-K с цитатами Лёхи: wear tabs, фильтры, keychain, Steam price, rarity, картинка, «Посмотреть на lis», русский+английский title, интерактивный float, подсветка «Тайное», русификация каталога +- §6 разведка JSON-экспорта lis (одношотная команда через `gcloud compute ssh`) + +**`STEAM_SNIPER_CODEX_ALERTS.md`** (~150 строк) — алерт-механизм из 17:13: +- Миграция `list_items` (+target_below_rub, +target_above_rub, +last_notified_*_at) +- `PATCH /api/lists/target` endpoint +- UI инпуты 🔴/🟢 в модалке (только если предмет в fav/wish) +- Triggering в `_refresh_prices` с 6-часовым cooldown +- TG-уведомления через `requests.post` напрямую (без отдельной очереди). Попросил Нику положить `LESHA_TG_CHAT_ID` в `.env` на VPS +- Web push помечен как OPTIONAL (TG у Лёхи через VPN) + +### Параллельная работа с Codex — поймал fallout + +Codex начал работу по ALERTS-брифингу пока я досыпал — правил `db.py`, `server.py`, `stats.js`, `deploy_quick.py`. Я об этом узнал через system-reminders о модифицированных файлах. Задеплоил свой UI-polish → сервис упал 502: + +``` +fastapi.exceptions.FastAPIError: Invalid args for response field! +Hint: check that starlette.responses.JSONResponse | dict is a valid Pydantic field type. +``` + +Причина: в `PATCH /api/lists/target` Codex поставил return type `-> JSONResponse | dict`, и FastAPI пытается сгенерить response_model из Union, где dict не валиден как Pydantic field. Добавил `response_model=None` в декоратор — сервис поднялся. Заодно тот же фикс `POST /api/lists` — там уже был у Codex, но синтаксис не совпадал. + +### Прочее + +- Убил три зомби-uvicorn процесса (висели 4+ часов с Playwright-теста ранее в сессии) +- `deploy_quick.py` написан (был только `deploy.py` который льёт 648 МБ snapshot) — upload 7 файлов + `systemctl restart steam-sniper-dashboard`, 60 сек vs 15 мин. Позже Codex сам расширил FILES (добавил `db.py`, `lists.js`, `stats.js`, `watchlist.js`). +- Обзор Claude Design (Anthropic Labs, 17.04) — conversational дизайн-инструмент, Pro/Max/Team/Enterprise, работает на Opus 4.7. Для Steam Sniper прямой пользы нет, но хорошо прототипировать новый layout перед следующим брифингом Codex. +- YouTube-ролик "8 Claude Skills I Can't Live Without" (Ben AI) — оценил 3/10 на кликбейт. Для steam-sniper нерелевантен. Для cortex-репо в целом — возможно есть паттерны, но я не разбирал 8 скилов поштучно (пользователь не попросил). + +## Решения + +- **Hero-карточки на клиенте, без нового endpoint.** 3 параллельных fetch в `stats.js` — проще чем делать `/api/hero-stats`. Цена: 3 запроса вместо 1. При ~1 пользователь/минуту — пофиг. +- **Doppler fallback регексом, не хардкод-списком.** Pattern `\s+(phase\s+\d|ruby|sapphire|black\s+pearl|emerald)$` — покрывает все 486 ножей одним правилом вместо списка из 20 gem-вариантов. +- **deploy_quick.py как отдельный скрипт.** Не стал добавлять `--quick` флаг в `deploy.py` — проще отдельный файл на 100 строк, не надо ветвить логику. Лёг в `tools/steam-sniper/`. +- **Алерты для fav/wish — в Codex, а не сам.** Объём работы час+ (миграция БД, новый endpoint, UI, триггер, TG-интеграция). Брифинг для Codex быстрее окупится — он в этом коде уже плотно сидит. + +## Проблемы + +- **Parallel Codex → 502 на проде.** Деплой без проверки что Codex параллельно пишет тот же файл → FastAPI упал на `JSONResponse | dict`. Урок: перед `deploy_quick.py` делать `git status` и `git log origin` если Codex активен. Фикс занял 2 минуты. +- **HTTP cache Chromium не сбрасывается моментально.** Даже после `no-cache` заголовка и `sniper-v3 → v4` bump у service-worker, Playwright-браузер продолжал рендерить старый `cases.js` из disk cache. Для реальных пользователей проблема решится одним F5. Для теста через Playwright — приходится полагаться на curl-ы API. +- **Max-transcriber первый раз подтянул не тот 17:13.** Голосовое 00:11 (оказалось 00:55, я на скрине неправильно прочитал цифру) — агент сначала схватил какое-то другое голосовое про поездку домой. Повторный запуск с явной ссылкой на «длинное голосовое 17:13 про UI» вытащил нужное. + +## Что в следующий раз + +### Ждём Codex +- Закроет `STEAM_SNIPER_CODEX_ALERTS.md` — миграция `list_items`, UI 🔴/🟢 инпуты в модалке, триггер в `_refresh_prices`, TG-интеграция +- Оставшиеся пункты `STEAM_SNIPER_CODEX_ITEM_DETAIL_EXPAND.md`: русский title рядом с английским (если lis JSON отдаёт `name_ru` — нужна разведка), крупнее картинка (сейчас 140, хочется 200), явная подсветка «Тайное» + +### От пользователя нужно +- Положить `LESHA_TG_CHAT_ID=...` в `/opt/steam-sniper/.env` на VPS — без этого Codex не сможет закончить TG-integration + +### На потом +- Web push для алертов (Лёха TG через VPN — просит альтернативу) +- Когда cortex-vm закроется в мае → перенос snapshot pipeline на новый VPS Timeweb (2 vCPU / 2+ GB RAM) +- Дизайн-редизайн через Claude Design? Ника спросил можно ли без последствий — палитра да, layout нет (Codex активен) + +## Metrics + +- Задеплоено на прод (VPS 72.56.37.150): **~6 раз** через `deploy_quick.py` за сессию +- Коммиты: пока всё в рабочем каталоге, не закоммичено (Ника коммитит по желанию) +- Новые файлы: `static/js/theme.js`, `tools/steam-sniper/deploy_quick.py`, 2 брифинга на `~/Desktop` +- UI-изменения: ~12 отдельных правок +- Время сессии: много, несколько сессионных блоков через resume diff --git a/memory/diary/019_2026-04-21.md b/memory/diary/019_2026-04-21.md new file mode 100644 index 0000000..d10697c --- /dev/null +++ b/memory/diary/019_2026-04-21.md @@ -0,0 +1,106 @@ + +# 019 — 2026-04-21 + +## Контекст сессии + +Длинный день с кучей треков, все взаимосвязаны. Стартовал с жалобы на залипший VoiceType (новый микро Fifine), потом PharmOrder-бриф для работы, потом расшифровка голосовых Лёхи и реализация Chrome-extension под новые price-alerts, параллельно обнаружил что cortex-vm мертва, в конце фикс apteka-bot. + +## Сделано + +### 1. VoiceType — диагностика «завис, пишет 0 секунд» +Симптом: постоянные start→stop через 14мс, 0 bytes аудио. Жалоба связывалась с новым USB-микрофоном (Fifine K669B). +- Убил два `pythonw.exe`, открыл 3 процесса. Изначально подозревал двойной listener. +- Проверил startup-ярлыки (только один VoiceType.lnk → VBS), HKCU/HKLM Run, Scheduled Tasks — дубликатов нет. +- Добавлял audit-hook через `sys.addaudithook` для трассировки спавна процессов — не сработало, значит child появляется ДО импорта main. +- **Ключевой вывод:** `.venv\Scripts\pythonw.exe` это launcher-shim uv venv на Windows, всегда спавнит base Python (`C:\...\Python312\pythonw.exe`) как child. Два процесса — это один логический VoiceType. main() выполняется в child (видно по одному `Config loaded` в логе на каждый запуск). +- Убил hook, вернул main.py в исходное. VoiceType работает. +- Default микрофон в Windows — уже USB PnP (новый Fifine), открыл `mmsys.cpl,,1` + `ms-settings:sound` для настройки уровней. + +Реальная причина залипания в более ранних логах (ошибка `SetForegroundWindow` + быстрый цикл) осталась без фикса — отложено до воспроизведения. + +### 2. PharmOrder — бриф для Клода на работе +Бриф `C:\Users\User\Desktop\PHARMORDER_EXPORT_FIX_BRIEFING.md`. Покрывает: +- Полную архитектуру 3-х машин (касса, мамин ноут, VPS) с двумя flow-ами выгрузки (сайт PharmOrder и cashbox→relay_server) +- Диагноз симптома «касса пишет не выгрузила, а на VPS приходит» — это неидемпотентный `/api/export`, ответ теряется после записи pending +- 4 патча с готовым кодом: server.py (кэш по request_id), index.html (UUID+ретраи+20с AbortController), cashbox_app.py+relay_server.py (batch_id дедуп), sync_standalone.py (zayava_written.json локальный dedup) +- Порядок применения + команды для бэкапа + что спросить у юзера перед фиксом + +### 3. Steam Sniper — Chrome extension v1.2 под новые алерты +Расшифровал два голосовых Лёхи через `max-transcriber` агент. Главная фича: price target alerts — минимальная (🔴) и максимальная (🟢) цена + TG-уведомления. + +Обнаружил: **Codex уже доделал весь бэкенд** по брифу `STEAM_SNIPER_CODEX_ALERTS.md`: +- `db.py` — миграция `target_below_rub`/`target_above_rub` в `user_lists` +- `server.py:1481` — `PATCH /api/lists/target` +- `server.py:1512` — расширенный `GET /api/lists` (current_price_rub, alert_triggered) +- `server.py:1087` — `_check_list_alerts()` + вызов из `_refresh_prices` (596) +- `server.py:1017/1053` — TG через Bot API, `LESHA_TG_CHAT_ID` из env +- `item_detail.js:248+` — секция «Telegram alerts» в модалке +- `stats.js:16-39` — Hero alerts учитывает fav/wish + +Моя часть — Chrome extension: +- `extension/content.js` — inline-форма после выбора Favorite/Wishlist с 🔴 Ниже / 🟢 Выше, кнопки «Добавить»/«Без целей», валидация (число >0, below +# 020 — 2026-04-21 + +## Контекст + +Вечерняя сессия после 019 (утренняя). Стартовала с жалобы «опять VoiceType лежит», плавно перетекла в фикс UX Steam Sniper по транскриптам голосовых Лёхи, потом в PDF-пакет для Лёхи, потом — в мета-рефлексию про состояние системы контекста и большой аудит внешним агентом. + +## Сделано + +### 1. scripts/ — чистка мусора +Юзер заметил 26 PNG + `.playwright-mcp/` в `scripts/`, не понимал откуда. Датированы 17.04, время сессии UI-итераций Steam Sniper (session 018). Playwright MCP дампил скрины и кеш в cwd = `scripts/`. Снёс 26 PNG + 55 файлов `.playwright-mcp/` (mp3, page snapshots, consoleлоги), в `.gitignore` добавил `scripts/*.png`. Записал правило в `feedback_playwright-cwd.md`. + +### 2. VoiceType — autostart через vbs-watchdog +Сегодня в 21:29 VoiceType стартанул из Startup, но за 30с не дождался USB-микро Fifine (после логона ещё не был инициализирован) и умер с `Could not open microphone: Error querying device -1`. Startup\VoiceType.lnk работал, но без retry-логики. + +Task Scheduler пробовал (`schtasks /Create /SC ONLOGON /RL LIMITED`) → «Отказано в доступе» без admin-прав. Не стал требовать админку. + +Решение: переделал `D:\code\2026\3\voice-type\scripts\voicetype.vbs` в **watchdog-цикл** — wscript.exe остаётся живой в фоне, каждые 30с WMI-запрос `SELECT ... WHERE CommandLine LIKE '%voice_type.main%'` проверяет жив ли воркер (и `python.exe`, и `pythonw.exe` — потому что uv run запускает system python, а `.venv\Scripts\pythonw.exe` — venv shim). Если воркера нет — запускает `.venv\Scripts\pythonw.exe -m voice_type.main` через `shell.Run ... 0, False`. Startup-delay 15с. + +Для теста убил два процесса (venv + system python — конфликт было), запустил watchdog, после 45с проверил WMI — один основной pythonw + один дочерний (whisper-server). Без дубликатов. + +Правило в `project_voicetype-watchdog.md`. Ссылку добавил в MEMORY.md. + +### 3. Steam Sniper UX — фикс по голосовым Лёхи +max-transcriber агентом вытащил 3 голосовых с Max (web.max.ru) за день. Две ключевые жалобы: «ни на одном предмете нет таргета» и «цена не выставляется». + +**Причина найдена**: в `tools/steam-sniper/static/js/item_detail.js:246` — `if (!entries.length) return ''`. Секция «Telegram alerts» скрывалась полностью, если предмет не в Fav/Wishlist. По TG-ссылке на Glock работало (был в wishlist), по любому случайному предмету из каталога — нет. Лёха принимал это за баг. + +**Фикс**: на empty-state показываю CTA + две кнопки `list-toggle` с `data-list="favorite"/"wishlist"`. Глобальный handler в `lists.js` ловит клик, POSTит `/api/lists`, эмитит `events.on('lists:changed')` — новая подписка в `item_detail.js` перерисовывает модалку с полями 🔴/🟢. + +Hint под полями: «Введи число и нажми Enter (или кликни вне поля) — сохранится автоматически» — чтобы убрать неопределённость как сохраняется. + +SW: CACHE_NAME `sniper-v4 → v5`, добавил `item_detail.js` и `theme.js` в STATIC_ASSETS. Deploy через `deploy_quick.py` прошёл, verify через curl — `detail-alerts-empty` найден 3 раза на проде. + +### 4. PDF-пакет для Лёхи на рабочий стол +Юзер попросил: zip с extension + подробная инструкция **в PDF** (у Лёхи нет читалки для .md), для совсем далёкого от техники человека, с «просьба → что сделано» по каждому пункту его голосовых. + +Генерация через reportlab (`runtime/release/make_pdf.py`), Arial + Arial-Bold из `C:\Windows\Fonts\`. Эмодзи 🔴/🟢 Arial не рендерит — заменил на `` и `` через Paragraph XML-markup. Звезду ★ (U+2605) Arial тоже не отрисовал (странно, но факт), — убрал, в тексте оставил слова «Избранное / Хотелки». + +Итог: `C:\Users\User\Desktop\steam-sniper-LESHA.zip` (134KB, extension + 6-страничный PDF). + +### 5. Аудит memory-системы (большой) +Юзер спросил отстранённо «нормально ли у нас с контекстом, всё ли работает». Отправил в `general-purpose` агента с чётким брифом — холодный взгляд. Агент нашёл три вещи которые я сам не видел: + +1. **Diary расщеплён на две папки**: старые 001-005 в `.claude\projects\...\memory\diary\`, новые 009-019 в `memory/diary/` (репо). `pre-compact.py` писал в старую, ручной `/diary` — в новую. `/reflect` читал старую → сессии 009-019 для рефлексии не существовали. +2. `subagents-playbook.md` задублирован в двух местах, разный контент (118 строк репо vs 82 строки `.claude/projects/...`). +3. Архитектурная схема `memory/` в CLAUDE.md:28 — врёт: реально MEMORY.md и feedback-файлы в `.claude/projects/...`, а не в репо. + +Плюс мелочёвка: L0-заголовки декларируются, не соблюдаются. Active Experiment истёк 20 дней назад. Биохимия крови TODO висит хотя результаты сданы 03.04. + +### 6. Починка memory-системы +- **Merge diary**: перенёс 001-005 из `.claude/projects/...\memory\diary\` в `memory/diary/` репо. Второй 001 (от 20.03) переименован в `001b` по паттерну `014b`. Папка `.claude/projects/...\diary\` удалена. +- **pre-compact.py**: `DIARY_DIR = PROJECT_DIR / "memory" / "diary"` через `CLAUDE_PROJECT_DIR` env. Smoke-test: хук написал 020-ю запись в репо, прибил её после проверки. +- **/diary и /reflect команды**: пути `~/.claude/projects/...\memory\diary\` → `memory/diary/`. +- **subagents-playbook**: удалил 82-строчную копию в `.claude/projects/...`, ссылку в MEMORY.md переделал в текстовый путь (потому что cross-drive markdown-link не работает). +- **MEMORY.md**: удалён TODO про биохимию (результаты есть), Active Experiment → Completed Experiments. +- **CLAUDE.md**: обновил схему папок (memory/ в репо = diary + playbook; MEMORY.md + feedback живут в `.claude/projects/...`, не в git). Убрал L0-правило как формально не соблюдаемое. + +## Решения + +- **Diary source of truth = репо** (а не `.claude/projects/...`). Причина: версионируется в git, виден в истории, не зависит от user-specific Windows-путей. +- **MEMORY.md и feedback-файлы оставлены в `.claude/projects/...`** — переносить всё в репо = большой миграционный проект, не влезает в «10 минут перед сном». +- **Ссылка на subagents-playbook в MEMORY.md** сделана текстом-путём, не markdown-линком — потому что cross-drive относительные ссылки не работают. Это компромисс до тех пор пока MEMORY.md не переедет в репо. +- **L0-правило снято, а не «принудительно внедрено через скрипт»** — принципа «убрать формальный закон лучше чем иметь пустой» придерживаюсь. +- **Task Scheduler отвергнут в пользу watchdog-в-vbs** — admin-прав нет, watchdog проще и делает ту же работу. + +## Проблемы + +- **vps_key для Steam Sniper VPS protухший**: `ssh -i ~/.ssh/vps_key root@72.56.37.150` даёт `Permission denied (publickey)`. Скорее всего это ключ от PharmOrder VPS (194.87.140.204). Для Steam Sniper VPS нужен отдельный ключ или пароль. Не починил — в текущей сессии не прижало, всё было достижимо через HTTP API. +- **`/reflect` накопил хвост 016-019** (теперь и 020). Не запускал — юзер хотел спать. +- **Номера diary 006/007/008** отсутствуют в истории — либо потеряны при переезде, либо `/diary` сбивался. Не восстанавливал. + +## Остановились на + +- Система контекста приведена в консистентное состояние. Следующая сессия начинается с чистого merge. +- **Завтра утром стоит запустить `/reflect`** по 016-019-020 — материал накопился, теперь команда прочитает правильную папку. +- Лёха получит zip с инструкцией (на десктопе). End-to-end тест Steam Sniper алертов — за ним. +- cortex-vm по-прежнему мертва с 19.04 00:00 UTC — Codex должен был разбираться, статус неизвестен. Если не починит — `gcloud compute instances reset cortex-vm --zone=europe-west3-b`. diff --git a/memory/diary/021_2026-04-22.md b/memory/diary/021_2026-04-22.md new file mode 100644 index 0000000..f816191 --- /dev/null +++ b/memory/diary/021_2026-04-22.md @@ -0,0 +1,62 @@ + +# 021 — 2026-04-22 + +## Контекст + +Короткая сессия после resume. Юзер попросил субагента холодным глазом пройтись по репо — «может ты когда проверял на новой версии мог пропустить что нибудь». Прошёл — нашёл 4 неочевидные вещи которые я не видел. Починили. + +## Сделано + +### 1. Внешний аудит репо (general-purpose агент) +Брифинг: пройтись по tools/, .claude/, хукам, командам, документации. НЕ лезть в сетевые endpoints. + +**Что нашёл:** +- **Pre-commit hook и `scripts/ops.sh`** ссылались на `tools/heartbeat`, `tools/pipeline`, `tools/scaffold` — **всё удалено в archive ещё 16.04**. Значит реально тесты steam-sniper (104 штуки) **никогда не прогонялись при коммите**. False-green защитная сетка на месяц. +- **CLAUDE.md:51** декларирует «pre-commit = tests + secrets + lint», по факту — только secrets + lint для changed py-файлов, без тестов. +- `tools/kwork-monitor/` — 3 скрипта импортят PIL, Pillow нет в deps. Юзер сказал «он мне не нужен», не трогаем. +- `tools/ui-ux/` — пустой (только `__pycache__` с февраля 2026, без .py-файлов). +- `scripts/screenshot.bat` — зовёт `scripts\tools\screenshot.ps1`, директории `scripts/tools/` нет. +- `.claude/commands/heartbeat.md` и `/new-project.md` — зовут `tools/heartbeat/main.py` и `tools/scaffold/main.py`, оба в archive. +- `.claude/commands/reflect.md:31` и `eval/SKILL.md:24` ссылались на `memory/MEMORY.md` как будто в репо — противоречит нашему вчерашнему фиксу CLAUDE.md, что MEMORY.md живёт в user-folder. +- `CLAUDE.md:104` в Instruction precedence тоже ссылался на `memory/MEMORY.md` — тот же баг. +- `scripts/.claude/` был untracked — артефакт случайного запуска Claude из папки scripts/, содержал `settings.local.json` и agent-memory для max-transcriber. + +### 2. Фиксы +- `scripts/ops.sh`: `TOOLS_WITH_TESTS="tools/steam-sniper tools/metrics"`, `TOOLS_ALL` очищен от pipeline/scaffold. +- `.git/hooks/pre-commit:11`: `for d in tools/steam-sniper tools/metrics` вместо heartbeat. +- **Тесты реально прогоняются**: `bash scripts/ops.sh test` → 116 passed (104 steam-sniper + 12 metrics) за ~2.2с. +- Удалил `.claude/commands/heartbeat.md`, `.claude/commands/new-project.md`, `scripts/screenshot.bat`, `tools/ui-ux/` (включая __pycache__), `scripts/.claude/`. +- `.gitignore` += `scripts/.claude/` — на случай повтора артефакта. +- `CLAUDE.md:104`: `memory/MEMORY.md` → `~/.claude/projects/D--code-2026-2-cortex/memory/MEMORY.md`. + +### 3. Бонус — pytest в steam-sniper deps +`ops.sh test` на первом проходе упал с `program not found: pytest`. Причина: pytest был в `[project.optional-dependencies].dev`, а `uv sync --quiet` без `--extra dev` его не ставит. Steam-sniper — внутренний тул, dev/prod разделение бессмысленно. Переместил pytest в основные dependencies. После `uv sync` — 104/104 pass. + +### 4. Чистка permission prompt-спама +Юзер раздражался от промптов Claude Code на `rm` + `rmdir` в chain. Причина: `Bash(rm:*)` был в allow-list, но `rmdir` — нет, и весь chain блокировался из-за одного неразрешённого куска. +- Удалил 4 одноразовых Bash-записи с точными путями (они попали туда когда юзер жал «Yes, don't ask again»). +- Добавил паттерны: `rmdir`, `stat`, `diff`, `sleep`, `tasklist`, `schtasks`, `wscript`, `wscript.exe`, `start`, `set`. + +### 5. Правила в память +- **feedback_no-inflated-estimates.md** — завышенные таймы типа «30 минут» для мелких правок бесят. Реальность 5-10 минут. Два инцидента подряд (вчера и сегодня) — записал как правило. + +## Решения + +- **Kwork-monitor не трогать.** Юзер не юзает, добавлять Pillow или чистить dead-scripts не нужно. Оставлен как есть. +- **pytest в основные deps** вместо optional — проще защитной сетке, проще onboarding. Внутренний тул, не PyPI-пакет. +- **Хвосты из прошлых сессий (extension v1.2, static/js изменения)** вынесены в отдельный коммит — разные scope, не смешиваются с repo cleanup. +- **Аудитора зовём регулярно.** Вчера — память-система (нашёл расщеплённый diary), сегодня — репо (нашёл false-green pre-commit). **8 реальных проблем на 2 Agent-вызова.** Раз в 2-3 недели — имеет смысл делать. + +## Проблемы + +- **Тесты tests/test_api.py и tests/test_listings_snapshot.py** были M со вчерашнего — оказалось хвосты из session 018/019. Не падали — значит прошлый автор (я или Codex) оставил в рабочем состоянии. +- **Одноразовые Bash-правила в allow-list** — плохой паттерн. Когда юзер жмёт «Yes, don't ask again for similar commands» для chain с точными путями, в settings.local.json появляются мусорные точные записи вместо паттернов. Надо самому добавлять паттерны `Bash(:*)` когда впервые зову новую утилиту. +- **`archive/dead-tools-2026-04/.venv/`** — 201 МБ на диске (heartbeat 97M + pipeline 105M). В git только 23 файла. Не критично, но можно прибрать если место понадобится. + +## Остановились на + +- Защитная сетка РЕАЛЬНО защищает: 116 тестов прогоняются при каждом коммите. +- Репо вычищен от мёртвого кода (ui-ux, screenshot.bat, heartbeat/new-project slash-commands). +- CLAUDE.md консистентен про MEMORY.md (в user-folder, не в репо). +- Два коммита: (1) session 021 cleanup + diary, (2) accumulated steam-sniper hvosty (extension v1.2, js-файлы, deploy_quick.py, theme.js из sessions 018/019) — чтобы git status был чист для нового чата. +- **Юзер перезапускает чат.** diff --git a/memory/diary/022_2026-04-24.md b/memory/diary/022_2026-04-24.md new file mode 100644 index 0000000..37010bb --- /dev/null +++ b/memory/diary/022_2026-04-24.md @@ -0,0 +1,92 @@ + +# 022 — 2026-04-24 + +## Контекст + +Сессия на теплом старте. Юзер попросил разобрать последние 2 TG-дайджеста (23-24.04) и «интегрировать что релевантно». Попутно — тест интеграции, генерация материала для отправки (анализы крови), обсуждения концептов (Karpathy LLM-wiki, multi-chat handoff). Финал — первый за 6 сессий push на GitHub с PR #51. + +## Сделано + +### 1. Разбор дайджестов → плановые интеграции +Прочитал `runtime/digests_clean.txt` (23-24.04), отфильтровал реально релевантное. Верификация каждой ссылки через WebFetch (урок с OpenMythos, который kyegomez-hype, пригодился — не интегрировал). + +**Интегрировано:** +- **minimal_editing rule** ([nrehiew.github.io](https://nrehiew.github.io/blog/minimal_editing/)) → новая секция в `~/.claude/ai-rules/agent-behavior.md`. Подтверждённый замер: Opus 4.6 с правилом «preserve original code» даёт Levenshtein 0.060, GPT-5.4 — 0.395. +- **WhisperX `--diarize`** в `tools/max-transcribe/transcribe.py`. Новый `_transcribe_diarize()` с graceful fallback если `whisperx` или `HF_TOKEN` нет. Дефолтный путь не трогал. +- **huashu-design skill** клонирован в `.claude/skills/huashu-design/` (32MB, 148 файлов, без BGM). SKILL.md 58KB — полный фреймворк для HTML-прототипов / hi-fi / анимаций / слайдов с антислоп-чек-листом. + +**В reference (знать что есть, не ставить):** awesome-claude-design (68 DESIGN.md), browser-harness (experimental self-healing), agent-dispatch MCP (для multi-repo setup, не для монорепо), sereja.tech multi-repo tracking, Anthropic postmortem 23.04. + +### 2. Починка skill discovery через worktrees +**Симптом:** юзер в другом чате попросил «использовать новый скилл для дизайна» — Claude взял `frontend-design`, не `huashu-design`. + +**Диагностика:** Claude Code сканит `.claude/skills/` относительно cwd запуска. У нас 4 worktree + main repo. Проверка показала что skill **физически отсутствовал** в main и в `bold-yonath` (прошлый раз сказал что синхронизировал — мой check-скрипт наврал из-за пропущенного слеша при конкатенации путей, main был ок; реально миссил только bold-yonath). + +**Фикс:** пересинхронил все 5 копий (main + 4 worktree). Добавил в `MEMORY.md` явное правило Design skills priority: `huashu-design` для прототипов/слайдов/анимаций/мокапов, `frontend-design` только для production web-компонентов. Память грузится автоматом в каждой новой сессии — все будущие чаты увидят правило. + +**Урок для будущего:** при проверке наличия файлов через bash `for p in ... ;do` — внимательно со слешами в конкатенации. Один пропущенный `/` = ложный negative → ложная уверенность что всё синхронизировано. + +### 3. PDF-карта анализов на Desktop +Юзер сдал биохимию 03.04 (Гемотест, № 153135772) + позже фолаты/общий белок 05.04. Нужно было «отправить кому-то, чтобы прикольно и чётко». + +Попытка #1: через `pdf/` skill сгенерировал `Анализы_крови_2026-04-03.pdf` из MEMORY-сводки (выглядел как обычный медотчёт). Юзер просил «интерактивную карту с рекомендациями как мы делали для мамы». + +Попытка #2 через **huashu-design** (реальный тест что skill триггерится и работает): `Анализы_карта.html` на Desktop, 35KB. +- 32 показателя, 8 групп по системам +- Визуальные шкалы: точка где результат, зелёная полоса — норма +- Click-to-expand: «что это / мой результат / референс» +- Фильтры (всё / действие / наблюдение / норма) +- Блок рекомендаций в порядке приоритета (витамин D → ЛПВП → тестостерон в нижней трети → фолаты → что приятно удивило) +- Стиль: кремовый #F4EEE3 / ink / терракот #B8472A, Instrument Serif + IBM Plex Mono — НЕ slop + +Когда пришёл второй PDF с фолатами — дописал в тот же HTML. Общая картина: 1 требует действия (витамин D 27.9), 3 на наблюдении (ЛПВП 1.44, тестостерон 4.05 в нижней трети, базофилы 1.1%), 28 в норме. + +### 4. Skill тест — 1 HTML-слайд Pentagram-style +Перед картой анализов был микро-тест huashu-design: слайд 1920×1080 «Cortex — персональная AI-лаборатория», чёрный фон, Instrument Serif + JetBrains Mono, асимметрия, красный accent. Сохранён в `runtime/huashu_test.html`. Юзер: «вроде прикольно, хотя опять же нужна более объёмная задача» → стала карта анализов. + +### 5. Обсуждения без действий +- **Karpathy gist про LLM-wiki** ([gist.github.com/karpathy/442a6bf...](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f)) — концепт инкрементальной вики вместо RAG. У нас 80% этого паттерна уже работает (MEMORY + reference-файлы + `/reflect`). Чего нет: автолинковка, конфликт-детекция, entity-страницы. Вердикт: маржинальное улучшение, не делаем пока не начнёт реально мешать. +- **Multi-chat handoff** — юзер задумался можно ли сделать `/mega-handoff` который пройдётся по транскриптам нескольких чатов и сделает общий срез. Технически — сканить `~/.claude/projects//*.jsonl`. Варианты: (A) push-модель — каждый `/handoff` пишет в общий stream; (B) pull-модель — `/mega-handoff` читает jsonl всех сессий за период и спавнит субагентов на суммаризацию; (C) живое слияние через PostToolUse-хуки. Юзер сказал «не призываю делать, просто интересно» — не делал. + +### 6. PR #51 — первый push за 6 сессий +Перед PR юзер спросил про «+45511 строк» в UI. Разобрался: на GitHub ни одной ветки `claude/*` не было, все 6 коммитов Steam Sniper (sessions 017–021) сидели локально в main. Плюс staged попал `huashu-design` skill (41k строк чужого кода). + +**Решение:** убрал skill из staging, добавил `.claude/skills/huashu-design/` в `.gitignore` (как внешний репо, держим локально, восстанавливается через `fetch_huashu_bgm.sh` + `git clone`). Финальный PR: 3 файла, +103 строки. + +PR #51 → `main`: https://github.com/NickStr11/cortex/pull/51 +- `tools/max-transcribe/transcribe.py` (+68) — WhisperX diarize +- `.gitignore` (+6) — exclude huashu-design + BGM +- `scripts/fetch_huashu_bgm.sh` (+33) — идемпотентный BGM restore + +Pre-commit hook отработал: 104 steam-sniper + 12 metrics тестов прошли за ~2с, secrets clean, lint ок. + +## Решения +- **Skill huashu-design не коммитим в git** — внешний репо, держим локально во всех worktree, восстанавливается по инструкции. Аналогично BGM. +- **Design skills разведены правилом** — huashu-design для прототипов/дизайна, frontend-design для production компонентов. Правило в MEMORY.md. +- **LLM-wiki паттерн Карпатого — не делаем** пока текущая система не начнёт реально тормозить. +- **Multi-chat handoff — не делаем**, но идея и 3 варианта архитектуры зафиксированы. + +## Артефакты +- PR #51: https://github.com/NickStr11/cortex/pull/51 +- `C:\Users\User\Desktop\Анализы_карта.html` — интерактивная карта для отправки +- `C:\Users\User\Desktop\Анализы_крови_2026-04-03.pdf` — PDF-версия (попытка #1) +- `runtime/huashu_test.html` — skill smoke-test слайд +- `memory/reference_digest-2026-04-findings.md` (per-user) — что взяли из дайджестов и почему + +## Файлы тронутые +**В git (в PR #51):** +- `tools/max-transcribe/transcribe.py` +- `.gitignore` +- `scripts/fetch_huashu_bgm.sh` + +**Вне git:** +- `~/.claude/ai-rules/agent-behavior.md` — +minimal editing rule +- `~/.claude/projects/D--code-2026-2-cortex/memory/MEMORY.md` — +Design skills priority + ссылка на digest-findings + строка про `--diarize` +- `~/.claude/projects/D--code-2026-2-cortex/memory/reference_digest-2026-04-findings.md` — новый +- `.claude/skills/huashu-design/` — в 5 копиях (main + 4 worktree), gitignored + +## Открытое +- **Steam Sniper** — Лёха end-to-end TG-alerts так и не проверил (с 21.04). Не блокер этой сессии. +- **cortex-vm OOM** — не смотрел. Юзер не поднимал тему. +- **PharmOrder UUID idempotency patch** — не двигали. +- **huashu-design skill** — если Anthropic обновит API или автор обновит skill, надо пересинхронить во все worktree руками. Лучше завести `scripts/sync_external_skills.sh` когда станет лень. diff --git a/scripts/fetch_huashu_bgm.sh b/scripts/fetch_huashu_bgm.sh new file mode 100644 index 0000000..03b5ad4 --- /dev/null +++ b/scripts/fetch_huashu_bgm.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Restore BGM tracks for huashu-design skill (video/animation export). +# Tracks are gitignored (27MB total), fetch on demand. +set -euo pipefail + +TARGET="$(cd "$(dirname "$0")/.." && pwd)/.claude/skills/huashu-design/assets" +REPO="https://raw.githubusercontent.com/alchaincyf/huashu-design/main/assets" + +if [ ! -d "$TARGET" ]; then + echo "huashu-design skill not found. Clone first:" + echo " cd .claude/skills && git clone --depth 1 https://github.com/alchaincyf/huashu-design.git" + exit 1 +fi + +TRACKS=( + "bgm-ad.mp3" + "bgm-educational-alt.mp3" + "bgm-educational.mp3" + "bgm-tech.mp3" + "bgm-tutorial-alt.mp3" + "bgm-tutorial.mp3" +) + +for track in "${TRACKS[@]}"; do + if [ -f "$TARGET/$track" ]; then + echo "skip $track (exists)" + continue + fi + echo "fetch $track" + curl -fsSL "$REPO/$track" -o "$TARGET/$track" +done + +echo "Done. $(ls -1 "$TARGET"/bgm-*.mp3 2>/dev/null | wc -l) tracks in $TARGET" diff --git a/scripts/ops.sh b/scripts/ops.sh index 202b65e..2735429 100644 --- a/scripts/ops.sh +++ b/scripts/ops.sh @@ -3,8 +3,8 @@ set -e ROOT="$(cd "$(dirname "$0")/.." && pwd)" -TOOLS_WITH_TESTS="tools/heartbeat tools/metrics" -TOOLS_ALL="tools/heartbeat tools/metrics tools/kwork-monitor tools/tg-monitor tools/tg-bridge tools/tg-pharma tools/pipeline tools/scaffold" +TOOLS_WITH_TESTS="tools/steam-sniper tools/metrics" +TOOLS_ALL="tools/steam-sniper tools/metrics tools/kwork-monitor tools/tg-monitor tools/tg-bridge tools/tg-pharma" case "${1:-help}" in test) @@ -51,7 +51,7 @@ case "${1:-help}" in help|*) echo "Usage: bash ops.sh " echo "" - echo " test Run tests (heartbeat, metrics)" + echo " test Run tests (steam-sniper, metrics)" echo " check Typecheck all tools (pyright)" echo " lint Lint all tools (ruff)" echo " sync Install/sync deps for all tools" diff --git a/scripts/screenshot.bat b/scripts/screenshot.bat deleted file mode 100644 index 34216ad..0000000 --- a/scripts/screenshot.bat +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -powershell -ExecutionPolicy Bypass -File "%~dp0tools\screenshot.ps1" diff --git a/tools/max-transcribe/transcribe.py b/tools/max-transcribe/transcribe.py index 63cbe4a..8ed16a9 100644 --- a/tools/max-transcribe/transcribe.py +++ b/tools/max-transcribe/transcribe.py @@ -1,23 +1,27 @@ """Transcribe audio messages from Max (web.max.ru) chat. Usage: - python transcribe.py [--contact "Name"] [--last N] + python transcribe.py [--contact "Name"] [--last N] [--diarize] Examples: python transcribe.py https://web.max.ru/61245315 --contact "Леша" python transcribe.py https://web.max.ru/61245315 --last 5 + python transcribe.py https://web.max.ru/61245315 --diarize # speaker labels Requires: - Chrome running with CDP on port 9222 (logged into Max) - whisper-cli.exe + model in voice-type runtime - ffmpeg in PATH + - For --diarize: `pip install whisperx` + HF_TOKEN env (accept license at + huggingface.co/pyannote/speaker-diarization-3.1) Flow: 1. Connects to existing Chrome tab via CDP (no new windows) 2. Navigates to chat in that tab 3. Clicks play on each audio to extract CDN URLs - 4. Downloads (Referer: web.max.ru), converts via ffmpeg, transcribes via whisper-cli GPU - 5. Saves runtime/max_audio/transcriptions.md + 4. Downloads (Referer: web.max.ru), converts via ffmpeg + 5. Transcribes via whisper-cli GPU (fast path) or WhisperX (--diarize, multi-speaker) + 6. Saves runtime/max_audio/transcriptions.md """ from __future__ import annotations @@ -154,6 +158,61 @@ def _download(url: str, path: Path) -> bool: return False +def _transcribe_diarize(audio_path: Path) -> str: + """Multi-speaker transcription via WhisperX. Returns text with [SPEAKER_XX] labels.""" + try: + import whisperx # type: ignore + except ImportError: + return "[whisperx not installed — pip install whisperx]" + + hf_token = os.environ.get("HF_TOKEN") + if not hf_token: + return "[HF_TOKEN missing — set env var, accept pyannote license on HF]" + + wav_path = audio_path.with_suffix(".wav") + subprocess.run( + ["ffmpeg", "-y", "-i", str(audio_path), "-ar", "16000", "-ac", "1", "-f", "wav", str(wav_path)], + capture_output=True, timeout=60, + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0, + ) + if not wav_path.exists(): + return "[ffmpeg failed]" + + try: + device = "cuda" + model = whisperx.load_model("large-v3", device=device, compute_type="float16") + audio = whisperx.load_audio(str(wav_path)) + result = model.transcribe(audio, batch_size=16) + + align_model, meta = whisperx.load_align_model(language_code=result["language"], device=device) + result = whisperx.align(result["segments"], align_model, meta, audio, device, return_char_alignments=False) + + diarize = whisperx.DiarizationPipeline(use_auth_token=hf_token, device=device) + diarize_segments = diarize(audio) + result = whisperx.assign_word_speakers(diarize_segments, result) + + lines = [] + current_speaker = None + buf: list[str] = [] + for seg in result.get("segments", []): + spk = seg.get("speaker", "SPK?") + text = seg.get("text", "").strip() + if spk != current_speaker: + if buf: + lines.append(f"[{current_speaker}] {' '.join(buf)}") + current_speaker = spk + buf = [text] + else: + buf.append(text) + if buf: + lines.append(f"[{current_speaker}] {' '.join(buf)}") + return "\n".join(lines) or "[empty]" + except Exception as e: + return f"[whisperx error: {e}]" + finally: + wav_path.unlink(missing_ok=True) + + def _transcribe(audio_path: Path) -> str: wav_path = audio_path.with_suffix(".wav") subprocess.run( @@ -191,6 +250,7 @@ def main() -> None: parser.add_argument("chat_url", help="e.g. https://web.max.ru/61245315") parser.add_argument("--contact", default="", help="Contact name for output header") parser.add_argument("--last", type=int, default=None, help="Only process last N audio messages") + parser.add_argument("--diarize", action="store_true", help="Speaker diarization via WhisperX (slower, multi-speaker)") args = parser.parse_args() OUTPUT_DIR.mkdir(exist_ok=True) @@ -225,7 +285,7 @@ def main() -> None: kb = audio_path.stat().st_size // 1024 print(f"[{kb}KB] ", end="", flush=True) - text = _transcribe(audio_path) + text = _transcribe_diarize(audio_path) if args.diarize else _transcribe(audio_path) print(f"{text[:70]}{'...' if len(text) > 70 else ''}") results.append({"label": label, "text": text}) audio_path.unlink(missing_ok=True) diff --git a/tools/steam-sniper/category.py b/tools/steam-sniper/category.py index 0e734f0..cb6b918 100644 --- a/tools/steam-sniper/category.py +++ b/tools/steam-sniper/category.py @@ -131,15 +131,17 @@ def classify(name: str) -> str: if "Case Key" in clean or "Capsule Key" in clean or clean.endswith(" Key"): return "key" - # Cases and capsules - if "Case" in clean or "Capsule" in clean: - return "case" - - # Weapon lookup: extract base name (part before " | ") + # Weapon lookup FIRST — "AK-47 | Case Hardened" has "Case" in skin name + # but base "AK-47" is a weapon. base = clean.split(" | ")[0].strip() if base in _WEAPON_MAP: return _WEAPON_MAP[base] + # Cases and capsules — check only base part (before " | ") to avoid + # matching skin names like "Case Hardened". + if "Case" in base or "Capsule" in base: + return "case" + # Agent detection: items with " | FACTION" pattern if " | " in clean: faction_part = clean.split(" | ", 1)[1] diff --git a/tools/steam-sniper/dashboard.html b/tools/steam-sniper/dashboard.html index fcd4089..75b5d91 100644 --- a/tools/steam-sniper/dashboard.html +++ b/tools/steam-sniper/dashboard.html @@ -12,6 +12,14 @@ Steam Sniper + @@ -36,22 +44,26 @@ Обновлено +
-
Portfolio Value
+
Избранное
--
-
Delta
+
Хотелки
--
-
-
Items
-
--
+
+
Алерты
+
@@ -111,9 +123,9 @@