closes board-viewer-gitea-reader - src/gitea.ts: HTTP client (getFile, getLatestCommitIso, rawUrl); DI'd fetch - src/reader.ts: readBoard(client, repos) → TaskRecord[] - src/config.ts: TOML config loader with board_viewer_repos whitelist - 25 tests total (9 parser incl. 3 real-repo fixtures, 7 gitea, 5 reader, 4 config) - emoji contract ⚪🔴🟡🔵🟢 (open/in_progress/paused/blocked/done) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
98 KiB
Task Board
Updated: 2026-05-21 — drain-bookva-ym-stocks 🟢 DONE end-to-end. Bookva-Ozon FBS: 5173/5184 → stock=0 (5171 первый прогон + 2 ручной multi-warehouse патч К000405/К000632 на Афонская КГТ + realFBS). YM Bookva: 1049/1049 → count=0. Slovo (id=2) не затронут, торгует штатно: ozon stocks syncronization каждую минуту SUCCESS (ctx.data{idSeller:2, otherSellers:[]} читается корректно), sync stocks with warehouse идёт. 14 commits на master (включая 4 hot-fix циклов SDK): план/спека → guard fbsPostings → handler TDD (6 unit-тестов) → tasks.json drop YM-cron → drain CLI (7 unit-тестов, basePath + IN_SALE/PUBLISHED фильтры + @books/yandex-market-partner-api wrapper + bypass YM updatedAt SDK-bug + multi-warehouse enumeration). YM Agenda docs cleanup: 8 (2 активных + 6 stale dup от старого idempotency) → 0 через mongosh deleteMany. Leftover: 11 SKU на FBO в Bookva-Ozon (147720748, 6281157, Б400095, 19687905, Б2003910, Б401223, 2888034, 7754504, К000370, Б601643, Б604747) — обнулить через API продавцу невозможно (склады Ozon), нужен FBO withdrawal в кабинете руками. Bug в @books/yandex-market-partner-api/stocks.js:59 (updatedAt double-toISOString, идентичен 2026-04-27 fix'у в api service но в shared wrapper не починен) занесён в business-logic-bugs.md. Spec: .wiki/concepts/drain-bookva-ym-stocks.md, plan: .tasks/drain-bookva-ym-stocks-plan.md.
Updated: 2026-05-21 — заведена drain-bookva-ym-stocks 🔴 active. Soft drain каналов Bookva-Ozon (id_seller=1) и Yandex.Market (тот же seller=1, ym_seller=1): push stocks=0/count=0 через API + reconfig scheduler так, чтобы автосинхронизация не возвращала ненулевые значения. Slovo (id_seller=2) торгует штатно. 6 изменений: (1) guard в packages/utils/fbsPostings.js length > 0 — закрывает YM-leak через updateSellersProductsStocks→calculateAndUpdateYmStocks(1) который дёргается даже при otherSellers=[]; (2) task-runner/tasks/ozonStocksSyncronization.js читает ctx.data().idSeller + otherSellers; (3) tasks.json data {idSeller:2, otherSellers:[]} для obo task'ов; (4) drop YM-cron'ов (ym stocks syncronization + sync ym stocks with warehouse) из Agenda; (5) one-off CLI packages/tools/drain-bookva-ym-stocks.js (hybrid CLI+module, dry-run, --only ozon|ym, .bookva.cmd). Spec: .wiki/concepts/drain-bookva-ym-stocks.md. Без архива/has_stock=false/выключения канала — только stocks, обратимо. Кода ещё нет — план следующим шагом через superpowers:writing-plans.
Updated: 2026-05-21 — move-promo-bookva-to-slovo 🟢 DONE end-to-end. Bookva долгопрод (promo id=1, 663 active applied, -30%) свёрнут, перенесён на Slovo (новая id=3, 660 SKU, -30%, до 2026-06-04 14:00 MSK). По ходу всплыл 3-й bug в domain-promotion CLI — ozon-promotion-deactivate.js:137 сравнивает expectedPrice = snapshotPrice (вместо actionPrice по спеке domain-promotions) → 663 SKU помечены diverged, importPrices не вызван, Bookva-Ozon цены остались -30%, БД ушла в фантомное ended. Switch на ad-hoc oneshot packages/tools/_push-prices-from-snapshot.mjs (CLI: --source-promotion-id --target-channel-id [--mark-restored] [--chunk-size]) — обход defensive, прямой applyPriceToOzon + sync БД. Прогон: Step1 Bookva real-restore 663/663, Step2 Slovo cohort-restore 235/235, Step3 Bookva-snap→Slovo 660/663 (3 NOT_FOUND: 19119894, Б2008714, Б607029 — нет в Slovo каталоге), Step4 create id=3, Step5 add 660/660, Step6 activate 660/660 applied. Bug в deactivate занесён в business-logic-bugs.md (запись 2026-05-21). Файл _push-prices-from-snapshot.mjs оставлен в packages/tools/ (без хардкод-кред, через salesChannel.getSeller()) — пригодится для будущих cross-shop миграций или fix-сессий deactivate.js.
Updated: 2026-05-20 — заведена fbs-picking-list-pdf-thumbs 🔴 active. Bugfix на fbs-picking-list-pdf: prod run #37944 (idPickingList=162) — email с 38MB PDF режется SMTP-сервером (552 5.3.4). Корень — FbsPickingPlacePrint.vue:14 ставит <img :src="item.image" /> на CDN full-res JPG, puppeteer встраивает в PDF без даунсемпла (100 items × ~300KB = 30-40MB). Fix — новый thumb-endpoint /images/thumb в @books/api (sharp resize до 240px JPEG q80), allowlist *.ozone.ru (SQL подтверждает 105 347 рядов в cdn1.ozone.ru + nowatermark.ozone.ru), auth тот же ?token=. PDF упадёт до ~1-3MB, email пройдёт. TDD: pure-logic parseThumbParams+isAllowedHost (security/SSRF, rules 3+4). Bump @books/api 2.1.0→2.2.0, @books/web 2.1.0→2.2.0.
Updated: 2026-05-17 — slovo-product-creation-recovery 🟢 DONE end-to-end. После закрытия slovo-in-sale-gap-diagnose Slovo 4742 → 5153 (+411, модерация Pass 1 #2 досoзревала), gap 676 → 306 (95.3% от Bookva 5406). Остаточные 245 «out_of_stock» — артефакт priority chain bucketize'а в diag tool (has_stock=false сел приоритетом выше is_created=false); реально Ozon отвергает updateProductsStocks2 с PRODUCT_IS_NOT_CREATED пока карточка не сolzит is_created=true. Concept ozon-product-creation-blockers (ингестнут вчера, commit ec3d596) расписан → реализованы 2 tool'а: Pass 3 products-fix-creation-errors-from-mirror (парсит errors[].attribute_id по whitelist кодов default CONDITIONAL_ATTRIBUTE_ERROR, копирует значения этих атрибутов из Bookva через UpdateAttributes) + Pass 4 products-reimport-stuck (auto-detect stuck is_created=false + пустой errors[], re-import через ImportProductsBySKU без diff'а). TDD: 10 + 8 unit-тестов. Prod smoke: Pass 3 — 74/74 imported (task_id=4479563894, из 169 targeted Slovo / 102 Bookva matched), Pass 4 — 6/6 imported (task_id=4479600569, из 632 stuck / 626 без Bookva master). Bump @books/tools 0.9.0→0.10.0 MINOR. 185/185 tests green. Lesson: я слил час на параллельное расследование slovo-stock-sync-bug (создал и удалил task) не прочитав wiki — concept уже описывал точный fix. Юзер дважды redirect'нул (не там копаешь → вики читать с самого начала сессии!). Стоит запомнить как hard rule.
Updated: 2026-05-16 — slovo-in-sale-gap-diagnose 🟢 DONE. Diagnostic CLI analyst/ozon-in-sale-gap.js (13/13 тестов) выявил два root cause'а: (1) sellers.put_on_sale_delay_days=7 для Slovo — calculateNewStocks в packages/utils/stocks.js фильтрует units.put_on_sale_date <= now-7d, поэтому свежие книги (последний экземпляр, положен <7d назад) не считались available для Slovo → stock=0 → has_stock=false. Юзер поменял delay на 0. Manual trigger sync stocks with warehouse (idSeller=2) подтянул +194 IN_SALE (4548→4742, 84.0%→87.5%). (2) Pass 1 фильтр отсекал Б2/К, юзер: «только У не переносим». Filter обновлён в обоих CLI (products-import-by-sku.js + products-fill-improve-attributes.js), bump @books/tools 0.7.0→0.8.0. Pass 1 #2 с новым фильтром: 444 кандидата отправлено через 5 task_id (443 pending, 1 failed offer_id=3907989, 0 unmatched) — через 1-3 дня после модерации станут IN_SALE → прогноз ~5180 = ~95.6% от Bookva. Остаточный gap после dust settles: ~232 out_of_stock (5/15 sample — реальные нули, 10/15 — available>0 но не пушится — потенциальная business-logic-bug в stocks.js:195 где productInfo.stocks.present сравнивается на верхнем уровне, а реально поле внутри stocks[]) + ~50 state_failed:imported/validated/declined/etc. Memory: reference_legacy_offer_id_prefix_filter, reference_seller_put_on_sale_delay_days.
Updated: 2026-05-15 (Pass 2 closed + verified) — ozon-products-copy-bukva-to-slovo 🟢 DONE. Full prod run --limit 4000 → 1179 карточек в UpdateAttributes через 12 task_id, 0 payload-ошибок. Post-модерация замер: avg rating 39.37 → 41.60 (+2.23), bucket 8-12 сократился на 372 (карточки перешли в 1-7 improve), bucket 13+ на 23. Эффект verified программно через повторный GetProductRatingBySku. Sync product_sales_channels Slovo (id=2) — 571 новых связей создано, 183 ghost-карточки пропущены. 5 commits локально pending user-go на push.
Updated: 2026-05-15 (вечер #3) — Phase 1 (Pass 1) полностью выгружен на проде под IN_SALE-only решением. Prod batch: 608 IN_SALE-карточек Bookva → Slovo, 7 task_ids, дубликатов нет (verified GetProductList Slovo + offer_id count = 1). Случайно прогнал команду дважды (был баг в моём mental model — grep после первого node я перечитал как «replay summary»); Ozon во втором прогоне ответил status:skipped на все 608 (умно). В Slovo также живут 50 карточек из первого ALL+blacklist smoke (могут быть не-IN_SALE) — чистка отдельной задачей если юзер скажет. Push pending user-go. Phase 2 — отдельной сессией.
Updated: 2026-05-15 (вечер #2) — PIVOT ozon-products-copy-bukva-to-slovo на IN_SALE-only. Юзер: «нужны только те, котрые в Букве в продаже» (и Pass 2 тоже). visibility ALL+blacklist → IN_SALE-only, 2-слойный клиентский фильтр снят (IN_SALE Ozon-side уже отсекает archived/declined/state_failed). Local dry-run после pivot: Bookva IN_SALE 5506 → legacy 4976 → diff vs Slovo 4368 → 608 новых кандидатов (а не 13832). 4 commits: 0047eea spec, b2965f6 code, c460a6b README, c261dd8 bump 0.5.0→0.5.1. 608 — реальный объём Pass 1 на проде; 50 импортнутых из предыдущего batch'а — могли быть не-IN_SALE (вне нового scope), они в Slovo, чистка — отдельно если юзер скажет. Next: prod batch (юзер сказал «давай, нужно»).
Updated: 2026-05-15 (вечер) — ozon-products-copy-bukva-to-slovo Phase 1 done end-to-end, 6 commits на master, не push'ено. Smoke discovery → secondary-фильтр верифицирован (moderate_status='declined' || status_failed!==''). Refactor + .cmd + README + bump 0.4.0→0.5.0. Local dry-run: 13832 кандидата → secondary отсёк 131 → cap=50. Prod smoke --limit 50 verified программно: task_id=4462621390, 50/50 в imported, GetProductList Slovo по тем же offer_id вернул 50/50 ✅. Criterion 10 спеки — done без UI-проверки юзера. Phase 2 (Pass 2 — products-fill-improve-attributes.js) — отдельной сессией. Pending: user-go на push.
Updated: 2026-05-15 (поздний день) — заведена ozon-products-copy-bukva-to-slovo 🔴 active. Два прохода CLI в packages/tools/: Pass 1 — расширение существующего products-import-by-sku.js (visibility=ALL + blacklist archived/not_moderated/state_failed, CLI-флаги для кред вместо хардкода, .cmd-обёртка bookva-to-slovo); Pass 2 — новый CLI products-fill-improve-attributes.js (для существующих в Slovo карточек с непустым improve_attributes[] — забирает значения этих атрибутов из Bookva через GetProductAttributesV4 и пушит через UpdateAttributes). Доменная область books не используется — только Ozon Seller API; ключ соответствия — offer_id. Префикс-фильтры У/Б2/К — legacy, оставлены, разберёмся потом. Spec: .wiki/concepts/ozon-products-copy-bukva-to-slovo.md. Кода ещё нет — юзер ушёл на другую работу, разработка автономная.
Updated: 2026-05-15 — analyst-workflow-claude-md 🟢 DONE ✅ (commit 44d97b0): добавлена секция «Analyst workflow» в корневой CLAUDE.md (39 строк) — триггер-фразы, 3 шага (load design context → check existing → write new), inline-блок стиля (ESM, commander+isDirectRun, JSDoc, bin+exports, ozon-seller-api-client напрямую, sequelize+raw SQL), read-каналы, write-через-CLI, anti-patterns. tools-ci-path-filter 🟢 DONE (частично) ✅ (commit 102a92e): сужено до CLI-only-tools подпутей (analyst/, plans/, not-for-qwen/, output/, README/QWEN.md, *.cmd) — исключение packages/tools/ целиком сломало бы prod runtime (fbs-ship/sync-prices импортятся в task-runner+api). Оба коммита локально на master, не push'ены. analyst-workflow-review 🔵 remains blocked — будет разблокирован после push'а.
Updated: 2026-05-14 (ночь) — ozon-revenue-per-product 🟢 DONE end-to-end ✅. CLI-tool packages/tools/ozon-revenue-per-product.js реализован по плану (11 tasks через superpowers:subagent-driven-development): 4 pure-функции под TDD (18 unit-тестов), live Finance + FBS/FBO Posting API, pluggable formatters (console/json/xlsx все ✅ на smoke), .bookva.cmd + .slovo.cmd. README + bump @books/tools 0.3.2 → 0.4.0. Bonus fix: xlsx.js formatter null-cell crash (shared между analyst tools). Smoke Bookva 2026-05-01..02: 118 postings, take rate 37.76%. Wiki sync. Локально на master, не push'ено.
Updated: 2026-05-14 (вечер) — заведена ozon-revenue-per-product 🔴 active. CLI-tool в packages/tools/: за период по Ozon-магазину live из API считает net/gross/avg на проданный товар + take rate + FBS/FBO breakdown. Per-channel — .cmd-обёртки (паттерн репо). Spec одобрен: .wiki/concepts/ozon-revenue-per-product-design.md. Не БД, не MCP, не analyst/ (Finance API нет в БД — gap #1 db-analytics-baseline; user явно хочет live). Next: implementation plan через superpowers:writing-plans.
Updated: 2026-05-14 (день) — slovo-long-sale-promo 🟢 DONE end-to-end ✅. Cohort1 (67) + cohort2 (168) = 235 SKU добавлены, activate отработал, 235/235 applied + 0 failed (после retry'я 1 SKU). По следам — 2 bag в ozon-promotion-activate.js занесены в business-logic-bugs: (1) MinPrice clamp 9582fbe пропущен в activate.js строка 155 (только в add-products.js); (2) if (failed === 0) строка 173 блокирует transition scheduled→active при любой failure. Workaround — ad-hoc _retry-slovo-minprice.mjs + manual SQL. Промо id=2 long-sale-slovo живая до 2026-05-27 14:00 UTC. Wiki sync pending.
Updated: 2026-05-14 — заведена slovo-long-sale-promo 🟡 paused. Аналог bookva long-sale для канала Slovo (id_sales_channel=2). Воронка собрана по playbook'у ozon-promo-candidate-selection: cohort1 ≥60д+price∈[900,1500) → 67; cohort2 [30,60)д+price≥900 → 168; total 235 SKU (bookva был 664, slovo меньше — 81% slovo IN_SALE дешевле 900₽). Акция id=2 long-sale-slovo создана в scheduled, -30%, до 2026-05-27. Pending: ozon-promotion-add-products обе партии + ozon-promotion-activate. Кандидаты в tmp-designs/slovo-long-sale/.
Updated: 2026-05-13 (поздний вечер) — ozon-promotions-rename-and-rekey DONE locally (awaiting push) ✅. Скоуп расширили в процессе: помимо ozon_ префикса + drop id_product + snapshot_min_price, переехали promotions.id_seller → id_sales_channel (FK к sales_channels). Ozon-creds остались на sellers (переезд на channels — отдельная задача). Migration 017 применена на проде, упала дважды (safe-update-mode + FK-prefix-index drop), оба fix зашиты в файл. 103/103 tools-tests green. Bumps @books/data 0.0.11, @books/tools 0.3.0. Live promo id=1 (12 SKU долгопрод) — данные сохранены через RENAME+UPDATE JOIN.
Updated: 2026-05-13 (вечер) — заведена ozon-promotions-rename-and-rekey 🔴 active. После запуска live-promo выявлены 2 фундаментальных design-bug'а брейнсторма 12.05: (1) таблицы promotions/promotion_products/promotion_price_snapshots без префикса ozon_ хотя фича строго про Ozon (конвенция books — ozon_* для Ozon-domain); (2) FK id_product к books.products для чисто Ozon-сущности (Ozon offer_id + product_id достаточны, FK создаёт ложную связь и заставляет резолвить products.sku → offer_id в активате). Fix: rename 3 таблиц на ozon_*, drop id_product FK, использовать offer_id как натуральный ключ, добавить snapshot_min_price для proper restore. Live promo (id=1, 12 SKU) сохраняется через RENAME TABLE. domain-promotions-fix-real-ozon-wiring 🟢 closed by commit afb423f.
Updated: 2026-05-13 — заведена domain-promotions-fix-real-ozon-wiring 🔴 active. Pre-flight check для «12 SKU long-sale -30%» (12 товаров из tmp-designs/long-sale-prices-result.json, канал Bookva-Ozon, -30%, 2 недели) выявил блокер: все 4 CLI домен-промо шипнуты с stub'ами importPrices: async()=>{} и getOzonPrice: async()=>({price:0,oldPrice:0}) — в Ozon ничего не уходит, snapshot=0, иллюзия успеха (БД пишется). Плюс bug в promotion-add-products.js: lookup productSalesChannel по {idProduct} ожидает поля product_id/offer_id отсутствующие в схеме product_sales_channels. ozon_seller_products устарел на 4 года (last update 2022-03-30) — не использовать. Решение: products.sku == offer_id, Ozon product_id через productAPIGetProductInfoPrices(filter:{offer_id}). Long-sale-promo заблокирована до починки.
Updated: 2026-05-08 (ночь, prod-verified) — docker-runner-env-decoupling CLOSED end-to-end ✅ (scheduler 1.5.3 на проде, commit bbbb428). Восстановлен инвариант «добавить новый docker-tool = 0 правок volume default.json». ${config:KEY} resolver в dataToEnv(data, taskEnv, rootConfig), taskDef.env, hashTask включает env, docker.containerEnv удалён, добавлены web.internalUrl/api.internalUrl (внутренние имена для docker network proxy), PDF defaults 100×100mm в tasks.json (literal, не в default.json). 4 итерации релиза: 1.5.0 → 1.5.1 (internal split + ошибочно pdf section в default.json) → 1.5.2 (revert pdf section) → 1.5.3 (PDF 100×100). 21/21 unit-тест. Прод volume default.json обновлён юзером (api/web internalUrl, s3.bucket, docker engine settings, puppeteer.browserWSEndpoint = ws://chrome:3000?token=..., docker.containerEnv удалён). Smoke createPickingListPdf на проде успешен. Wiki sync (sources/docker-runner-env-decoupling.md).
Updated: 2026-05-08 (ночь) — docker-runner-env-decoupling CLOSED ✅ (scheduler 1.5.0). Восстановлен инвариант «добавить новый docker-tool = 0 правок volume default.json». ${config:KEY} resolver в dataToEnv(data, taskEnv, rootConfig), taskDef получает поле env { ... }, hashTask теперь включает env, docker.containerEnv удалён. 12 новых unit-тестов (21/21 green). Wiki + log sync. One-time prod migration на VPS volume /opt/books/job-scheduler/config/default.json — pending push approval (см. .tasks/docker-runner-env-decoupling.md → "Resume protocol").
Updated: 2026-05-08 (поздний вечер) — fbs-picking-list-pdf end-to-end на проде ✅. Run 15814 (idPickingList=88): chrome render 21KB → S3 → api PATCH success, pdf_remote_path в БД. По ходу 5 hot-fix циклов: (1) sqlite3+cpu-features YN0009 на task-runner+web → root dependenciesMeta.built=false; (2) chrome WS 404 → restart 2-week-old browserless; (3) PATCH api fetch failed → API_URL http://books-api:3021 internal; (4) [id].vue + [id]/print.vue Nuxt collision → rename в [id]/index.vue; (5) WEB_URL puppeteer ходил на books.kzntsv.site который Traefik роутит на api, исправлено на http://books-web:3000. По следам — заведена docker-runner-env-decoupling ⚪ (рефакторинг архитектуры env'ов docker-tools, делать в новой сессии). Pending: подобрать PDF_HEIGHT (юзер; default 177mm избыточен для 1-2 книг), full reserve-цепочка smoke (fbsPickingReserve с 2 attachments в email), wiki sync, close STATUS.
Updated: 2026-05-08 (вечер) — fbs-picking-list-pdf Tasks 1-17 + wiki сделаны локально (13 commits на master, не push'ено). 5 версий bumped (web/api/tools/scheduler/task-runner). 27 unit-тестов (10 api + 12 tools + 8 waitForTaskRun + 7 spawnPdfAndAttachReport — все green). Деривации: ctx.agenda нет → lib/agendaClient.js singleton; puppeteer-core ^5.5.0 (workspace) вместо ^23; tool kebab-case вместо camelCase. Pending user: (a) local smoke 4 сервисов; (b) push (project-discipline Rule 4 ask-mode); (c) prod migration 015; (d) prod containerEnv API_TOKEN через volume-mount; (e) prod trigger fbsPickingReserve verify.
Updated: 2026-05-08 (день) — fbs-picking-list-pdf implementation plan расписан → .tasks/fbs-picking-list-pdf-plan.md. 21 task, 7 phases (DB → print page → tool → scheduler config → chain → smoke → wiki). 3 open questions резолвлены: (1) image=node:22-bookworm-slim без chrome, переиспользуем remote chrome wss://chrome.kzntsv.site через puppeteer.connect(); (2) token-auth=query ?token= через existing 02-auth.js + bypass в auth.global.ts для print-роутов; (3) sync email-PDF=in-process polling task_runs (timeout 5min, на fail email с XLS-only + warning). Не новый workspace pdf-generator/, а tool в packages/tools/ + matrix entry. DB-write через api PATCH /fbs/pickingLists/:id/pdf, не direct sequelize.
Updated: 2026-05-08 — scheduler-docker-runner CLOSED ✅. Smoke testEcho на проде: run id=15456, status=success, log с 5×[N/5] hello + stderr done after 5 iterations, container_id non-null, --rm отработал, agendaJobId Bug#3 pattern. Phase 5 готов end-to-end. Инцидент по пути: Gitea API stuck → решился рестартом gitea+gitea-db (вики concepts/gitea-stuck-recovery.md). Native build deps (python3+make+g++) добавлены в scheduler/task-runner Dockerfiles. fbs-picking-list-pdf unblocked → 🔴 active. Total 27 commits в master (T1-T20 + 4 fix-коммита: yarn.lock, native build, summary).
Updated: 2026-05-07 (T20 wrap-up) — scheduler-docker-runner Phase 5 готов локально (20 commits, T1-T19 на master локально). Smoke тест testEcho ждёт push → build-tools.yml matrix → deploy. После verify закроется до ✅. fbs-picking-list-pdf 🟡 → 🔴 unblocked.
Updated: 2026-05-07 (поздний вечер) — implementation plan для scheduler-docker-runner написан: .tasks/scheduler-docker-runner-plan.md. 20 tasks с TDD red→green→commit, 4 pure-logic utils + handler с mocked dockerode + параметризованный Dockerfile + matrix workflow + smoke testEcho. Open questions резолвлены (pull retry 3x exp 5/15/45s, heartbeat 10s, smoke image bash:5.2 или dedicated). Next: запустить execution через superpowers:subagent-driven-development (recommended) или superpowers:executing-plans.
Updated: 2026-05-07 (вечер) — после TDD-разнарядки и discovery что Phase 5 docker-runner — stub. Заведена scheduler-docker-runner 🔴 active (Phase 5 в pluggable-scheduler, handlers/docker.js отсутствует). fbs-picking-list-pdf переведена в 🟡 paused — blocker на Phase 5. PDF теперь рендерится через docker-runner (новый workspace packages/pdf-generator/), task-runner image остаётся чистым. TDD: Phase 5 — TDD целиком (Q2/Q3/Q4); PDF — композит (visual/oneshot/wrapper для UI/migration/Dockerfile + TDD для pdf-generator core/auth/reserve-цепочки/email-mod).
Updated: 2026-05-07 — заведена fbs-picking-list-pdf 🔴 active. PDF-версия листа подбора рядом с XLS в reserve-цепочке, карточки по местам, портрет A4, mobile-first для сборки с телефона при RKN-блокировке web. Спека утверждена: .wiki/concepts/fbs-picking-list-pdf.md. Implementation plan ещё не написан.
_Updated: 2026-05-04 — заведена sync-prices-ozon-to-yandex 🔴 active. Tool в packages/tools/ который тянет live цены с указанного Ozon-канала и проставляет в YM-канал (1:1 на поле price, old_price генерится по legacy-алгоритму generateOldPrices.js). Hybrid CLI+module + handler-обёртка в одном файле; thin re-export stub в task-runner/tasks/ для auto-discovery реестра. tasks.json entry без cron (manual trigger). Дизайн одобрен (brainstorming sesh), код в работе. Также: paтern packages/task-runner/tasks/ объявлен устаревшим — новые handler-обёртки в packages/tools/.
_Updated: 2026-05-03 — analyst-report-formats CLOSED ✅. Реализованы 5 pluggable formatters (console/md/xlsx/html/json), CLI-dispatcher, output-path helper, 11 unit тестов. Wiki обновлена. Хук yarn.lock улучшен (проверяет dependencies/devDependencies, не весь package.json). Push в master (commit b6007f8). Pattern готов для первых analyst tools (staff-performance / listings-vs-orders / sales-seasonality).
Updated: 2026-05-01 (поздний вечер²) — books-analyst-mcp CLOSED целиком. Phase 1.3 (books-data-mcp) отменён архитектурно: вместо stdio MCP-сервера над @books/data аналитика будет писаться как обычные JS-файлы в packages/tools/analyst/<name>.js (паттерн hybrid CLI+module — соседи ozon-transactions-analysis.js, ozon-products-price-analysis.js). LLM работает в books/, sequelize-модели открываются Read-инструментом напрямую → MCP-stdio + ssh-tunnel + saved queries — лишний слой. План docs/superpowers/plans/2026-04-30-books-data-mcp.md помечен OBSOLETE первой строкой. Phase 1.4 (analyst-каталог) отменён вместе с 1.3. Phase 1.2 (yandex-market-mcp) остаётся отдельной задачей на полке. Решение в .wiki/concepts/analyst-tooling.md.
Updated: 2026-05-01 (поздний вечер) — заведена ozon-knowledge-base-ingest 🟡 paused. Цель — слить базу знаний seller-edu.ozon.ru/libra (комиссии/тарифы + цены/акции + дайджест) в .wiki/raw/seller-edu/ для LLM-аналитика. Эксперимент: Ozon Antibot палит CDP-протокол → markitdown/curl/playwright (headless/headful/stealth) — всё 403 либо "Доступ ограничен". Юзер параллельно saved страницу через obsidian-web-clipper → референсный MD (290 KB из 369 KB HTML, таблицы/якоря/frontmatter сохранены). Архитектурный вывод: clipper использует под капотом open-source defuddle (MIT, github.com/kepano/defuddle) — node.js библиотека для clean content extract + turndown. Финальная архитектура задачи: A3 (playwright CDP-attach к НЕ-playwright Chrome юзера) для HTML acquisition + defuddle npm для HTML→MD = byte-совместимо с тем что clipper делает руками. Pipeline сделать в packages/seller-edu-ingest/. Artefacts/scaffold в tmp-designs/seller-edu-experiment/. Memory: reference_ozon_seller_edu_antibot.
Updated: 2026-05-01 (вечер) — books-analyst-mcp Phase 1.1 ✅ closed. Real-API smoke 11/11 на кабинете bookva/буква (10 OK + reviews.list 403 как ожидалось без Premium). В smoke'е вылез баг handler-обёртки SDK request'а (все 11 tools посылали пустой body, Ozon возвращал 400 «filter required»); пофикшен двумя коммитами: 1af4fcf (wrapper показывает HTTP body в error text, bump 0.0.4) → de3360c (handler-ы переключены на плоский request, bump 0.0.5; покрыто handler-body.test.js 10 кейсов через Configuration.fetchApi-перехват). Wiki + log + decision log синхронизированы. Phase 1.2 (YM-MCP), 1.3 (books-data-mcp), 1.4 (analyst-wiring) — отложены, реактивация по запросу.
Updated: 2026-05-01 (день) — books-analyst-mcp 🔴 active. Scope Phase 1.1 сужен до только ozon-mcp (юзер: «набъём шишки на Озон, Яндекс потом будет легче»). 11 read-only tools, multi-shop через env-vars, SDK 0.0.30 покрывает все 3 TODO плана (Returns/Analytics/Reviews) реальными классами. Документация — через npm-mcp + результат в .wiki/packages/ozon-mcp.md. Plan: docs/superpowers/plans/2026-04-30-ozon-mcp.md. Per-task: books-analyst-mcp.md. YM-MCP / data-mcp / analyst-wiring — отложены за Phase 1.1.
Updated: 2026-05-01 (утро) — books-ops-mcp Phase 2 CLOSED. 4 mariadb tools (query/describe + scheduler.recentRuns/failedRuns) на проде с APP_VERSION=master-d426d03. Smoke 7/7 green; failedRuns(since='7d') вытащил 8 known bugs из business-logic-bugs end-to-end. CI был сломан днём (502 Bad Gateway → runner+gitea restart + workflow_dispatch unblock; recovery pattern в memory reference_gitea_ci_stuck_recovery). Spec+plan: docs/superpowers/{specs,plans}/2026-04-30-books-ops-mcp-phase2*. Wiki packages/ops-mcp.md расширена per-phase секцией. Skill .claude/skills/books-ops-mcp/SKILL.md обновлён на 12 tools.
Updated: 2026-04-30 (поздний вечер ²) — ntfy Bug #1 fix запушен (pending verify): gitea.run_id → gitea.run_number в RUN_URL обоих workflow (build.yml, deploy.yml). Root cause — Gitea UI route /repo/actions/runs/{run} парсит {run} как runIndex (per-repo sequential = run_number), а не глобальный run.ID. Build URL «работал» (HTTP 200, но открывал чужой run), deploy URL — 404 (run_id выше суммарного count). Verify: git commit --allow-empty -m "verify ntfy URL" && git push → tap ссылки в обоих ntfy-каналах. После verify закрыть ntfy или открыть Phase 3 (disk-alert).
Updated: 2026-04-30 (поздний вечер) — закрыты registry-gc (Phase 1+2+3+5 на проде, цикл retention работает на двух фронтах Gitea+VDS) и switch-npm-mcp-to-local-stdio (.mcp.json переведён с docker на локальный node — убирает «MCP unavailable» при daemon/volume-mount issue'ах). ntfy переведена в 🟡 paused — Phase 1+2+4 на проде, замечен баг #1 (битая ссылка на Gitea в deploy-уведомлении, в build-уведомлении ОК) — не блокер, ждёт возврата.
Updated: 2026-04-30 (вечер close) — books-ops-mcp Phase 1 CLOSED ✅. 8 tools на проде, hot-fix agendaState (aggregate pipeline) задеплоен и verified end-to-end. Skill, wiki, business-logic-bugs обновлены. Phase 2 (recentRuns/failedRuns через MariaDB) не делаем без конкретного триггера. Юзер закрыл досрочно (без 24h soak) — все evidence для close уже были.
Updated: 2026-04-30 (вечер) — books-ops-mcp Phase 1 на проде закрыт по фактическим возможностям. После Phase 1 deploy первый же вызов ops.scheduler.agendaState показал output 95 KB (превышал Claude cap). Hot-fix 5b56dc3: aggregate pipeline по name → 23 ряда (~9 KB), 33/33 unit-теста, push'ен и задеплоен (APP_VERSION=master-5b56dc3 в env). Все 8 tools работают на проде. По следам прохода — 4 бага бизнес-логики занесены в business-logic-bugs (commit 86be892). Создан project-scoped skill .claude/skills/books-ops-mcp/SKILL.md (commit 4470ce1) чтобы Claude не лез через ssh за тем, что покрыто tools. Wiki-страница @books/ops-mcp добавлена.
Updated: 2026-04-30 (день) — scheduler два hot-fix'а пушены (0d5ffd6 + b6dcdfc): (1) productsIndexSyncronization legacy zombie-handler убран из tasks.json после disable=true в Mongo; (2) критический: cron http-jobs не выполнялись из-за idempotency check на shared Mongo _id — все ~20 cron'ов годами не sync'ились (sync остатков!), fix agendaJobId = ${_id}:${Date.now()} в http-handler. После recreate'а scheduler юзер подтвердил «задвигался». Также написаны spec + 4 плана для books-analyst MCP-стека (docs/superpowers/{specs,plans}/2026-04-30-*), но юзер поставил books-ops-mcp ВЫШЕ analyst в приоритете — для ускорения фиксов и развязывания рук в SSH.
Updated: 2026-04-29 — registry-gc Phase 5 (VDS-side vdsDockerCleanup через books-docker-proxy) committed локально, ждёт user-go на push. Юзер уточнил: Gitea — 5TB пофиг, горит на VDS. Phase 1+2+3 (Gitea side) ранее запушены. poll-pending-ozon-imports Phase 1-5 запушена, pending Phase 6 verify.
Updated: 2026-04-28 — заведён scheduler-ui-improvements (имя модуля в логе + copy-all-info кнопка). business-logic-bugs пополнен fbsPickingReserve recipients: [] SMTP-вспышкой + закрыт loadProductsToWarehouse double-run (fix локально). Заведён scheduler-bugs (404 на detail-страницах тасков из-за отсутствующего tasks.json в api image, запушен). Заведён scheduler-core-replace (концепт замены Agenda на свой core, idea-only). agenda-admin-ui CLOSED. Заведена npx-runner-pattern (документирована, не реализована).
🟢 move-promo-bookva-to-slovo — DONE 2026-05-21 — свёрнут Bookva долгопрод, перенесён на Slovo (-30% до 04.06)
Status: done
Outcome: Bookva promo id=1 (663 SKU active, -30%) → ended + restored. Slovo promo id=2 (235 SKU active, -30%) → ended + restored (превентивно, чтобы snapshot copy не утянул slovo-цены в новой id=3). Создана slovo-promo id=3 long-sale-slovo-2026-05-21, -30%, до 2026-06-04 14:00 MSK, 660/660 applied. По пути найден 3-й bug в ozon-promotion-deactivate.js (defensive expected=snapshot вместо action) — обход через oneshot _push-prices-from-snapshot.mjs. 3 SKU 19119894, Б2008714, Б607029 отсутствуют в Slovo каталоге (добавлены в Bookva после Pass 1 миграции) — не критично для текущей задачи. Bookva-каталог остался в продаже с нормальными ценами (по решению юзера). Артефакты прогона в tmp-designs/bookva-cancel-to-slovo/.
Acceptance: все 4 пункта задачи юзера ✅ (отменены акции в Букве с сохранением списка / цены в Слове = ценам Букве / создана акция в Слове на 2 недели / применена к 660 из 663).
Blocker: —
Branch: master (никаких коммитов; БД-state на проде через ad-hoc oneshot)
🔴 fbs-picking-list-pdf-thumbs — bugfix: PDF листа подбора 38MB → SMTP 552; thumb-endpoint в api
Status: active
Where I stopped: написан task-файл, корень диагностирован (CDN full-res обложки puppeteer вшивает в PDF без даунсемпла); готов писать TDD red-tests для thumbLib.js.
Next action: написать packages/api/server/routes/images/thumbLib.test.js (parseThumbParams + isAllowedHost), убедиться что красные, затем impl + handler + sharp в package.json + swap src в FbsPickingPlacePrint.vue.
Blocker: —
Branch: master (commits локально, push после verify)
🟢 slovo-product-creation-recovery — DONE 2026-05-17 — Pass 3 + Pass 4 для снятия is_created=false блокеров
Status: done (awaiting push approval)
Outcome: Реализованы 2 новых CLI согласно concept'у ozon-product-creation-blockers. Pass 3 products-fix-creation-errors-from-mirror.js (TDD 10/10, pure helpers chunkArray + extractErrorAttrIdsByOfferId(items, codesWhitelist), reused filterCopyableAttrs из Pass 2): Slovo GetProductList(ALL) → GetProductInfoList → парсит attribute_id из errors[] по whitelist (default CONDITIONAL_ATTRIBUTE_ERROR) → Bookva matching → GetProductAttributesV4 → UpdateAttributes Slovo чанками 100. Pass 4 products-reimport-stuck.js (TDD 8/8, pure helpers chunkArray + isStuckCreating(item)): auto-detect Slovo карточек с is_created=false+пустой errors[] → Bookva IN_SALE match (silent skip без master) → ImportProductsBySKU без diff'а. Prod smoke apply: Pass 3 task_id=4479563894 74/74 imported (из 169 targeted / 102 Bookva matched), Pass 4 task_id=4479600569 6/6 imported (из 632 stuck / 626 без Bookva master). Bump @books/tools 0.9.0→0.10.0 MINOR (+2 bin/exports). README +2 секции. Wiki concept updated. 185/185 tests green (+18). Ожидаемый эффект: 80 карточек после next cron syncStocksWithWarehouse (каждые 8ч) попадут в IN_SALE.
Acceptance: все 7 пунктов task-файла ✅.
Blocker: —
Branch: master (5 коммитов локально, не push'ено — project-discipline Rule 4 ask-mode)
🟢 slovo-in-sale-gap-diagnose — DONE 2026-05-16 — diag tool + два root cause fix (delay + filter) + Pass 1 #2
Status: done
Outcome: Diagnostic CLI analyst/ozon-in-sale-gap.js (TDD 13/13: chunkArray + bucketizeInfoItem с priority chain archived > out_of_stock > no_price > failed > state_failed > moderate > status > state) + .cmd-обёртка + pluggable formatters через runFormat. Root cause #1: sellers.put_on_sale_delay_days для Slovo=7 vs Bookva=0 → 433 свежих книг с put_on_sale_date >=2026-05-09 не попадали в available SQL → stock=0. Юзер поставил delay=0. Manual syncStocksWithWarehouse(idSeller=2) +194 IN_SALE (4548→4742). Root cause #2: фильтр legacy У/Б2/К в Pass 1+2 был слишком широкий, юзер: «только У не переносим». Filter обновлён. Bump @books/tools 0.6.0→0.8.0 (0.7.0 — diag tool MINOR, 0.8.0 — filter change MINOR). Pass 1 #2 на проде: 444 кандидата (включая Б2/К), 5 task_id, 0 unmatched. Прогноз через 1-3 дня после модерации: Slovo ~5180 IN_SALE (~95.6% от Bookva 5418). Локально 1 коммит pending push.
Acceptance: все 7 пунктов из task-файла ✅ (CLI создан / pipeline 5 шагов / bucket-разбивка / .cmd-обёртка / README / bump MINOR / prod smoke с numbers).
Blocker: —
Branch: master
🟢 ozon-products-copy-bukva-to-slovo — DONE 2026-05-15 — Bookva → Slovo: массовая привязка по SKU + докачка контент-рейтинга
Status: done (Phase 1+2 prod-shipped; ожидает user-go на push)
Outcome: Pass 1 — products-import-by-sku.js extended (CLI-flags, IN_SALE pivot), prod 608 IN_SALE-кандидатов в Slovo через 7 task_id, 0 дубликатов. Pass 2 — новый CLI products-fill-improve-attributes.js (TDD 13/13, pure helpers chunkArray/buildImproveIdsByOfferId/filterCopyableAttrs), prod smoke --limit 100 → task_id=4463148527 → 31/31 imported (verified GetImportProductsInfo, 10/31 имеют пост-импортные модерационные warnings — исходные проблемы каталога). @books/tools 0.5.1 → 0.6.0 MINOR. 4 Pass 2 commits локально (77a1acf, 56d5150, 03b0e88, c5ae6e7) — не push'ено, pending user-go. SDK discovery закрыл open вопросы 3-4 (chunks 100/1000/100, dry-run console-table); скорректирована спека §pipeline §8 (UpdateAttributes возвращает только {task_id}, per-item через GetImportProductsInfo).
Next action: user-go на push origin/master. Дальнейшая батч-докачка — повторные --limit 100 (3825 ост. кандидатов).
Spec: .wiki/concepts/ozon-products-copy-bukva-to-slovo.md
Acceptance: Pass 1 — 10/10 ✅. Pass 2 — 9/9 ✅.
🟢 ozon-revenue-per-product — DONE 2026-05-14 — выручка с проданного товара по магазину Ozon
Status: done
Outcome: CLI-tool packages/tools/ozon-revenue-per-product.js + .bookva.cmd + .slovo.cmd. 4 pure-функции под TDD (18 тестов). Live Finance + FBS/FBO API, pluggable formatters. Smoke на Bookva ✅ (console/json/xlsx). README. @books/tools 0.4.0. Bonus fix shared xlsx.js formatter.
Blocker: —
Branch: master
🟢 slovo-long-sale-promo — DONE 2026-05-14 — аналог bookva long-sale для slovo (id=2)
Status: done
Outcome: End-to-end. Воронка собрана (cohort1 67 + cohort2 168 = 235 SKU), оба add-products пробежали без fail. Activate упал на 1 SKU (139258946) с MinPrice violation → ad-hoc retry script + manual SQL transition → 235/235 applied, 0 failed. Промо id=2 long-sale-slovo active, -30%, до 2026-05-27 14:00 UTC. По следам — 2 bag в business-logic-bugs (activate.js: clamp пропущен + status transition блокируется failure). Кандидаты + retry-log в tmp-designs/slovo-long-sale/. Deactivate в календарь на 2026-05-27.
✅ scheduler-docker-runner — CLOSED 2026-05-08 — Phase 5 в pluggable-scheduler runner: 'docker'
Status: closed
Outcome: End-to-end на проде. 4 pure-logic utils + handler.js (full lifecycle: pull-on-miss → semaphore → attach+demux+ring-buffer → wait+timeout/SIGTERM/SIGKILL → mapExit → finalize → remove always) + 12 unit-тестов + параметризованный packages/tools/Dockerfile (BASE_IMAGE+BIN_NAME) + matrix build-tools.yml. Smoke testEcho: run id=15456, status=success, container_id non-null, log = [1/5] hello×5 + stderr done after 5 iterations, --rm отработал, agendaJobId ${_id}:${ts} Bug#3 pattern. По пути решены: yarn.lock drift (YN0028), native build deps в alpine (python3+make+g++), volume-mount sync default.json на VPS, инцидент Gitea API stuck (рестарт gitea+gitea-db, вики обновлена). Bumped @books/job-scheduler 1.2.3→1.3.0.
🟢 ozon-promotions-rename-and-rekey — fix 3 design proeb'а: ozon_ prefix + drop id_product + id_seller→id_sales_channel
Status: done (awaiting push) — 103/103 tools tests green, migration 017 применена на prod, live promo id=1 (12 SKU) данные сохранены
Where I stopped: 2026-05-13 вечер. Scope расширили в процессе: помимо ozon_ prefix + drop id_product + snapshot_min_price, переехали promotions.id_seller → id_sales_channel (FK к sales_channels). Ozon-creds остались на sellers — отдельная задача. Migration упала на проде дважды (safe-update-mode + FK-prefix-index) — оба fix зашиты в файл.
Acceptance: (1) ✅ migration 017 + RENAME 3 таблиц, drop id_product, id_seller→id_sales_channel, snapshot_min_price; (2) ✅ 3 sequelize моделей переименованы, FK ассоциация на salesChannel; (3) ✅ 5 CLI переименованы, --product-ids→--offer-ids, workaround через books.products.sku удалён (probe Ozon by offer_id); (4) ✅ _lib/ozon-promotions/ + side-effects через salesChannel.getSeller(); (5) ✅ tests 103/103 (26 lib + 77 CLI); (6) ✅ bumps @books/data 0.0.10→0.0.11, @books/tools 0.2.2→0.3.0.
Blocker: —
Branch: master
✅ domain-promotions-fix-real-ozon-wiring — CLOSED 2026-05-13 — real Ozon SDK + min_price + 5 багов
Status: closed (commits 89f4522 + afb423f, not pushed)
Outcome: Подключены real Ozon SDK вызовы к 5 domain-promotion CLI. Фикс через несколько итераций после pre-flight check'а на live-promo «long-sale -30% / 12 SKU»: (1) stub importPrices/getOzonPrice → real (createOzonSideEffects); (2) bug productSalesChannel lookup → Product.findByPk → sku → Ozon API; (3) @books/data пропустил регистрацию 3 promotion-моделей — добавлены; (4) applyPriceToOzon throws на failure (не глотает в results[].success=false); (5) min_price default = price (Ozon отвергал push ниже min_price); (6) ozon-side-effects._unwrap достаёт body из ResponseError. Live promo id=1 применена через recovery oneshot — 12/12 цен -30% в Ozon верифицировано через ozon_prices_list. 58 unit-тестов green. Bump @books/tools 0.2.0→0.2.2, @books/data 0.0.9→0.0.10. Замечен в processе фундаментальный design defect — заведена follow-up ozon-promotions-rename-and-rekey.
🟡 fbs-picking-list-pdf — PDF-версия листа подбора в reserve-цепочке (auto в email рядом с XLS)
Status: implementation done (Tasks 1-17 + wiki), awaiting prod verify
Where I stopped: 2026-05-08 вечер. Tasks 1-17 + Task 20 (wiki) сделаны, 13 commits локально. 27 unit-тестов green. 5 версий bumped. Не push'ено.
Next action: (a) локально запустить 4 сервиса (api/web/scheduler/task-runner) и сделать end-to-end smoke (открыть /fbs/picking-lists/:id/print?token=<token> в браузере, проверить что layout без navbar, page-break per place; trigger fbsPickingReserve через UI, ждать [pdf] записей в task-runner консоли, ждать email с двумя attachment'ами); (b) разрешить push в master — auto-build+deploy всех 4 стеков через Gitea Actions; (c) на проде apply миграцию 015 (packages/data/migrations/015-add-pdf-remote-path.sql, idempotent); (d) на проде в scheduler default.json через volume-mount выставить docker.containerEnv.API_TOKEN реальным значением (текущий placeholder <SET_IN_PROD_VOLUME_MOUNT>); (e) trigger fbsPickingReserve на проде, verify pdf_remote_path заполнено + email с двумя attachment'ами.
TDD: компонент'ы по-разному покрыты. Q3 (api endpoint Q3 identifier security) — TDD pure-logic helpers (10 тестов). Q2/Q3 (tool puppeteer/S3/api PATCH) — 12 unit-тестов с mocked deps. Q4 (waitForTaskRun + spawnPdfAndAttachReport branching) — 15 unit-тестов. Visual/oneshot/wrapper — [skip-tdd: …] маркеры в commits.
Blocker: —
Branch: master (13 commits локально, awaiting push approval)
🟡 sync-prices-ozon-to-yandex — копир цен Ozon→YM (packages/tools/)
Status: paused — Phase 1 закоммичен, smoke OK, real apply упёрся в YM rate-limit на ~37 chunk'е.
Where I stopped: 2026-05-04. Local apply (dryRun=false) на каналах 1→3: первые 37 chunks (~18500 цен) ушли успешно, остальные ~164 chunks (~81 800 SKU) упали в ym-update-failed с generic «Response returned an error code» (SDK прячет body). Diag-probe одиночными запросами те же sku — оба 200 OK, значит payload и ключ валидные → причина похожа на YM rate-limit/quota windowing. Side effect: первые ~18500 SKU в YM-кабинете Bookva уже имеют новые цены + новые рандомные discountBase (sku 100001+ из верха списка по id_product). Не страшно — repeated apply просто перезапишет, цены идемпотентны.
Next action: (a) добавить fetchApi-middleware в sync-prices-ozon-to-yandex.js который читает body на не-2xx и кидает с status+body (по аналогии с de3360c для Ozon SDK); (b) тротлинг между chunks (1-2 sec) + retry-on-429 с backoff 30-60s (сейчас 2/4/8/16/32 — мало для YM-окон); (c) опционально — chunk size <500. После (a) re-probe одного из failed chunk'ов чтобы увидеть конкретный YM-error → root cause → fix → re-apply.
Blocker: —
Branch: master
🟡 ozon-knowledge-base-ingest — слить seller-edu.ozon.ru/libra в .wiki/raw/seller-edu/
Status: paused — архитектура A3 + defuddle выбрана, не реализована. Сотни страниц вручную не вариант; defuddle (open-source MIT, движок obsidian-web-clipper) даёт референсное MD, playwright connectOverCDP к настоящему Chrome юзера обходит anti-bot.
Where I stopped: 2026-05-01 поздний вечер. Эксперимент: markitdown solo / curl / playwright headless / headful / stealth — всё 403 либо "Доступ ограничен" (Ozon палит CDP-fingerprint). Юзер параллельно вручную saved страницу через obsidian-web-clipper — MD-качество референсное, показал что defuddle (=движок clipper'а) — путь. Финальная архитектура: A3 (CDP-attach к НЕ-playwright Chrome) для HTML acquisition + defuddle npm для HTML→MD + frontmatter писать самим. Артефакты эксперимента в tmp-designs/seller-edu-experiment/, пример качественного MD — .wiki/raw/2026-05-01-вознаграждения-...md. Лессон в memory reference_ozon_seller_edu_antibot.
Next action: реализовать pipeline (см. шаги 1-9 в task-файле). Prereq — юзеру настроить Chrome с --remote-debugging-port=9222 постоянно. Pipeline: discovery sitemap → defuddle pipeline test на 1 URL → seed комиссии/тарифы (throttle 8s/req, ≤30/сессия) → seed цены/акции → дайджест-cron в job-scheduler. Параллельно — проверить публичный Telegram-канал Озон как резервный канал дайджеста.
Blocker: —
Branch: master
✅ pluggable-scheduler — CLOSED 2026-04-28 (+ Phase 9 + private S3 fix 2026-04-28)
Status: closed
Outcome: Phase 1-7 + Phase 9 в проде. 23 http-таска в tasks.json с label/description/params для админки. Pure reconciler (config.jobs[]+lib/jobs/ удалены), все таски рендерят markdown-отчёт + always-upload в S3 (reportS3Key контракт после 2026-04-28 — bucket private, api-proxy с requireAdmin), task_runs self-cleanup. Mongo-флаг _fromYml мигрирован на _fromConfig в startup-миграции. Playbook docs/playbooks/pluggable-scheduler.md обновлён + ALS log-capture как Phase 5b + грабли 21-25.
✅ agenda-admin-ui — CLOSED 2026-04-28
Status: closed
Outcome: Все 5 фаз + post-launch polish + все 4 deferred follow-up закрыты в одной сессии. Раскатано в 74254ea. Главное: ALS console-capture в task_runs.log (ctrl-через-Z для дебага), markdown-отчёты через api-proxy на приватный S3 (старый MinIO Browser UI больше не вылезет), human-readable filename при скачивании, RunsTable порядок+ссылки, UPagination, sales-channels автоподсказки. Wiki синхронизирована: новая страница Task log capture, обновлены Task-runner handlers и SalesChannel.
🟡 poll-pending-ozon-imports — split loadProductsToOzon на submit + cron poll
Status: implementation done, awaiting prod verify
Where I stopped: Phase 1-5 + wiki сделаны локально (resume 2026-04-29). Tool/handler/poll-job/tasks.json/UI polling/api no-cache/wiki — всё в индексе. Push готов; следующий шаг — push в master, дождаться auto-deploy всех 4 стеков, manual trigger main run в проде, через 5-10 мин проверить что poll-job tick прошёл и mutate'ил result.
Next action: push → wait deploy → trigger main run на проде → verify (см. Resume protocol в task-файле).
Blocker: —
Branch: master
✅ docker-runner-env-decoupling — CLOSED 2026-05-08 — расщеплены infra-creds и task-contract
Status: closed (implementation done, локально, awaiting push approval + one-time prod volume migration)
Outcome: Восстановлен инвариант «добавить новый docker-tool = 0 правок volume default.json scheduler-а». Реализован syntax ${config:KEY} (string-form, partial interpolation supported) в dataToEnv(data, taskEnv, rootConfig). taskDef получил поле env { ... } в tasks.json. default.json причесан: docker.containerEnv удалён, появились api.{url,authToken}, web.url, s3.bucket. hashTask включает env → правка блока в tasks.json триггерит reconcile. 12 новых unit-тестов (21/21 green: dataToEnv: ${config:KEY} whole-string / partial / multiple refs / literal / unknown ref throws / data+taskEnv merge / numeric/boolean stringify / null skipped / undefined arg + handler-level taskDef.env merged into Env / no-env passthrough). Bumped @books/job-scheduler 1.4.0→1.5.0 [MINOR]. Wiki scheduler-docker-runner обновлён, decision-record в log.md. One-time prod migration на VPS volume /opt/books/job-scheduler/config/default.json — добавить секции api.{url,authToken}, web.url, s3.bucket, удалить блок docker.containerEnv. После — никаких правок при добавлении следующих docker-tools.
⚪ npx-runner-pattern — npx-таски как третий способ запуска (без third runner-type)
Status: ready
Where I stopped: Phase 5c в pluggable-scheduler playbook'е задокументирован (2026-04-28). Два пути: A) handler+spawn('npx',...) для http-runner-а, B) docker с image: node:N + command: ['npx',...]. В коде ничего ещё нет.
Next action: при первом реальном npx-кандидате (например audit-ci weekly или npm-check-updates) — Phase 1: reference-handler runNpxCommand.js. Phase 2 (docker command override) откладывается до имплементации docker-runner-а.
Blocker: —
Branch: —
🟡 scheduler-bugs — реестр багов /scheduler UI и /admin/scheduler/* api
Status: 2 hot-fix'а пушены 30.04, awaiting verify (юзер подтвердил «задвигался»), real-fix rewrite позже Where I stopped:
- Bug #1 (2026-04-28): 404 на
/admin/scheduler/tasks/<name>— open / fixing. - Bug #2 (2026-04-30):
productsIndexSyncronizationlegacy handler с зомби-локами. Mitigated черезdisabled:trueв Mongo + удалён task definition изtasks.json(5c17135). Real fix (rewrite или delete handler) — отдельной задачей. - Bug #3 (2026-04-30): критический — cron http-jobs не выполнялись годами.
handlers/http.js:14шлётString(job.attrs._id)какx-agenda-job-id. Для cron черезagenda.every()это shared id → task-runnerfindByAgendaJobIdнаходит старый run от первого dispatch'а → возвращает 200 без создания нового → handler не выполняется. Все ~20 cron http-handler'ов (sync остатков, postings, prices…) thinks работают (MongolastFinобновляется), реально не делают ничего. Fix:agendaJobId = ${_id}:${Date.now()}(ddbbffd, pushb6dcdfc). Юзер 30.04 ~04:00 МСК подтвердил «задвигался шедулер». Next action: наблюдать стабильность 24h. Если возникнут новые failures — копать. Real rewriteproductsIndexSyncronization— отдельной задачей или delete (если не нужен). Blocker: — Branch:master
✅ books-ops-mcp — CLOSED 2026-05-01 (Phase 1+2: 12 read-only tools для prod-диагностики)
Status: closed
Outcome: 12 tools на проде (8 Phase 1 + 4 Phase 2). Phase 1 (5b56dc3, 30.04): 4 docker + 2 mongo + 2 scheduler-mongo. Phase 2 (d426d03, 01.05): mariadb.query/describe (generic) + scheduler.recentRuns/failedRuns (narrow). Mongo + MariaDB через docker network proxy (172.20.0.0/16), без exposing ports. Defence-in-depth Phase 1 (3 слоя: proxy-ro POST→403, mongo whitelist, env mask) + Phase 2 (6 слоёв: ops_ro@'172.%' GRANT SELECT, multipleStatements:false, checkSql regex+ALLOWED_STARTS, enforceLimit force LIMIT, query timeout, capPayload 1MB cap). 3 unit-tested helpers (TDD) + integration tests via docker-compose + smoke 7/7 на проде. Один review-fix (comment-strip в enforceLimit) и один hot-fix failedRuns (prod-схема: status='error' + колонка error, не 'failed'/fail_reason). Wiki @books/ops-mcp расширена per-phase. Skill .claude/skills/books-ops-mcp/SKILL.md обновлён на 12 tools. Phase 3 (write tools под двойным confirm: unlockAgendaJob, disableAgendaJob) — только при 2-3 повторяющихся ручных операциях такого класса.
✅ books-analyst-mcp — CLOSED 2026-05-01 (поздний вечер²) — целиком
Status: closed (Phase 1.1 на проде; Phase 1.3+1.4 отменены архитектурно; Phase 1.2 — на полке)
Outcome: @books/ozon-mcp@0.0.5 — 11 read-only tools на проде (Phase 1.1, ozon-seller-api-client@0.0.30, multi-shop bookva/слово, smoke 11/11). Phase 1.3 (books-data-mcp) отменён 2026-05-01 (поздний вечер²): brainstorming показал — MCP-stdio + ssh-tunnel + saved queries в *.sql лишний слой, LLM работает в books/ напрямую и packages/tools/ уже задаёт паттерн hybrid CLI+module (соседи ozon-transactions-analysis.js, ozon-products-price-analysis.js). Аналитика теперь пишется как обычные JS-файлы в packages/tools/analyst/<name>.js по запросу, без отдельной инфраструктуры. План docs/superpowers/plans/2026-04-30-books-data-mcp.md помечен OBSOLETE первой строкой. Phase 1.4 (analyst-каталог) отменён вместе с 1.3 — отдельный каталог не нужен. Phase 1.2 (yandex-market-mcp) — план остаётся отдельной задачей, реактивация по запросу. Решение зафиксировано в .wiki/concepts/analyst-tooling.md.
✅ analyst-report-formats — CLOSED 2026-05-03 — pluggable formatters для analyst tools
Status: closed
Outcome: Реализован pattern pluggable formatters для аналитических CLI-tools. 5 formatters: console (stdout, console.table), json (stdout), md (GFM таблицы + chart placeholder), xlsx (ExcelJS, sheets per table, bold headers, auto-width), html (HTML table + vega-embed, CDN default + --standandalone). Infrastructure: _lib/cli-format.js (dispatcher с --format/--out/--standalone), _lib/output-path.js (timestamped paths в tmp-designs/analyst-reports/), _test/ (11 unit тестов). Wiki: обновлены analyst-tooling.md (секция "Output formats"), новая страница tools-analyst.md, index.md + log.md entries. Хук yarn.lock улучшен: теперь проверяет dependencies/devDependencies изменения, не весь package.json (ложные срабатывания при scripts changes устранены). Push в master (commit b6007f8). Тестовые кейсы (staff-performance, listings-vs-orders, sales-seasonality) — отдельными задачами по мере возникновения.
🟡 scheduler-ui-improvements — UX-идеи для /scheduler админки
Status: paused
Where I stopped: заведено 2026-04-28. Две идеи: (1) имя исполняемого модуля в логе (через stack-walk на каждую console-строку при data.debug=true); (2) кнопка «Copy all» на /scheduler/runs/[id] которая собирает status+params+log+result в plain-text для вставки Claude'у в чат.
Next action: реализуется при первом серьёзном дебаг-сценарии. Низкий приоритет.
Blocker: —
Branch: —
⚪ scheduler-core-replace — заменить Agenda+Mongo на свой core поверх MariaDB
Status: idea (not started)
Where I stopped: 2026-04-28 концептуальное обсуждение pros/cons/подводных камней. Crash recovery был преувеличен мной как блокер — это setInterval + UPDATE WHERE locked_at < NOW() - X ~50 LOC. Реальные сложности: lock TTL trade-off (handler в task-runner отдельным процессом), idempotency на retry, catch-up policy. Middle-ground — тонкая обёртка cron-parser + MariaDB scheduled_tasks ~300 LOC.
Next action: не трогать. Триггеры пересмотра: @agendajs/mongo-backend ломается на npm-bump, нужны priority/dependencies/per-task concurrency, или желание снять Mongo-контейнер с VPS.
Blocker: soak Phase 7 pluggable-scheduler ещё свежий — преждевременно ломать стабильное.
Branch: —
⚪ tools-architecture-revision — ревизия границ packages/tools vs packages/task-runner/tasks
Status: ready (defined, не приоритет)
Where I stopped: задача заведена 2026-04-27 при дизайне productSalesChannel-sync — старая конвенция «tools атомарны, не лезут в БД» отменена (выбран X: tool пишет в БД сам). Юзер: «подходы нужно будет подвергнуть ревизии». Накопить 2-3 sync-таска, потом ревизировать pattern.
Next action: дождаться 2-3 примеров реализации, потом brainstorm границ + обновить .wiki/packages/tools.md и concepts/task-runner-handlers.md.
Blocker: —
Branch: —
🟡 feedback-changes — фидбэк юзеров + изменения API маркетплейсов
Status: paused
Where I stopped: Все коммиты партии запушены (46c20e3 upload polling, 6ae3a12 главная, ebe1ebd /jobs 500, 9d61850 web FBS-роуты, ae9b6dd иконки Pagination/SelectMenu). Юзер не приносил новой партии.
Next action: ждать фидбэка; в бэклоге — блок метрик дашборда + переименование API-роутов FBS (web уже переименован, API не тронут).
Blocker: —
Branch: master
🟡 pwa — installable PWA для @books/web
Status: paused
Where I stopped: spec (2a48230) + impl (b0a6820) + task-wiki (7a28f91) запушены. На проде раскатано.
Next action: ручная проверка на устройствах — iPhone Safari (Add to Home Screen), Android Chrome (install prompt), Lighthouse PWA audit. По итогам — закрыть или завести follow-up.
Blocker: —
Branch: master
🟡 web-design-system — единая дизайн-система @books/web
Status: paused
Where I stopped: Большая итерация UX/UI запушена (lucide bundle вместо CDN, мобильный navbar с hamburger, ProductSkuLink + useTabTarget, /fbs/orders как сетка карточек, USwitch вместо чекбокса в /fbs/summary, hover-open dropdown через UPopover, scrollbar-gutter:stable, spinner-overlay, реордер orders header, label «Канал продаж», SKU+title pattern на /epz/search).
Next action: ждать новой партии фидбэка по UX.
Blocker: —
Branch: master
🟡 business-logic-bugs — реестр багов бизнес-логики
Status: paused
Where I stopped: bug #2 (Ozon labels portrait, 09962bd) и bug #3 (upload-history badge, 0aae243 + 4358806) закрыты. bug #1 (ozonFbsProduct notNull, 2940123) запушен — ждёт прод-логи task-runner на пропадание notNull Violation.
Next action: при возврате — проверить прод-логи task-runner после раскатки 4 локальных коммитов (заодно с bug #1 verification). Либо ждать новых багов.
Blocker: —
Branch: master
🟡 vps-migration — переезд CentOS 7 → Debian 12
Status: paused
Where I stopped: план в concepts/vps-os-migration, оценить сроки.
Next action: при готовности — оценить сроки и blast radius.
Blocker: —
Branch: master
✅ registry-gc — CLOSED 2026-04-30 (Gitea-side retention + VDS-side prune)
Status: closed
Outcome: Полный цикл retention в проде на двух фронтах. Gitea-side: mode=min build cache + registryGc cron 0 4 * * 0 MSK (keep last 3 versions per package) + Gitea built-in [cron.cleanup_packages] OLDER_THAN=24h (blob compaction). VDS-side: vdsDockerCleanup cron 0 5 * * * MSK через books-docker-proxy (4 stage prune containers/dangling/old-images/buildkit-cache, volumes намеренно не трогаем). Notify в books-alerts через ntfy summary после каждого run. Phase 4 (disk-pressure alert) опциональный — открыть отдельной задачей при надобности.
🟡 ntfy — push-уведомления (Telegram заблокирован РКН)
Status: paused — Phase 1+2+4 на проде; Bug #1 fix запушен 30.04 поздний вечер², pending verify.
Where I stopped: 2026-04-30 поздний вечер². Bug #1 (Click-URL → 404 в books-deploys, чужой run в books-builds) — root cause: Gitea UI route /repo/actions/runs/{run} парсит {run} как runIndex (per-repo seq = gitea.run_number), а не gitea.run_id. Поправлены оба workflow (build.yml env RUN_URL, deploy.yml env RUN_URL).
Next action: push верификационный коммит git commit --allow-empty -m "verify ntfy URL" && git push → дождаться build+deploy → tap ссылки в обоих ntfy-каналах, должен открыться ИМЕННО smoke run сверху списка. После verify — закрыть task или начать Phase 3 (disk-alert).
Blocker: —
Branch: master
🟡 playbook — оперативный playbook для инцидентов VPS / API
Status: paused (scope сужен — реюзабельные плейбуки выехали)
Where I stopped: scope-расщепление 2026-04-27: реюзабельные «как сделать» плейбуки (CI/CD, scheduler) живут в docs/playbooks/ и закрыты. Эта задача теперь только про incident playbooks (что делать когда сломалось) — не начато.
Next action: структурировать сценарии (API down, registry full, certificate expiry), оформить в docs/runbooks/ или docs/playbooks/incidents/.
Blocker: —
Branch: master
✅ [switch-npm-mcp-to-local-stdio] — DONE 2026-04-30 — переключён npm-mcp в .mcp.json с docker на локальный node stdio. Контекст и обоснование — в общей вики: concepts/local-stdio-mcp-deploy (там же таблица альтернатив B/C/D на случай, если разделяемый конфиг пакетов не подойдёт для books).
Текущий блок в books/.mcp.json (entry npm-mcp):
"npm-mcp": {
"type": "stdio",
"command": "docker",
"args": [
"run", "--rm", "-i",
"-v", "c:/Users/vitya/projects/npm-mcp/packages.config.json:/app/packages.config.json:ro",
"-v", "npm-mcp-cache:/data",
"registry.kzntsv.site/npm-mcp:latest"
]
}
Заменить на:
"npm-mcp": {
"type": "stdio",
"command": "node",
"args": ["C:/Users/vitya/projects/npm-mcp/build/index.js"]
}
Прочие записи (context7, books-ops) не трогать. Путь к build/index.js указан под Windows-машину; на другой машине, если books открывается там — подставить локальный путь к клону npm-mcp (или замаппить через user-scope claude mcp add --scope local, вариант B из общей вики, чтобы не коммитить машинно-специфичный путь).
Решение принято в сессии npm-mcp 2026-04-30: docker-путь периодически даёт «MCP unavailable» (daemon, volume mounts на Windows), а сервер уже скомпилирован локально и реестр доступен с хоста — Docker не даёт ничего, кроме лишней точки отказа.
Status: done 2026-04-30
Outcome: В books/.mcp.json entry npm-mcp переключён с docker run на node C:/Users/vitya/projects/npm-mcp/build/index.js. Прочие записи (context7, books-ops) не трогали. Юзеру: после следующего рестарта Claude Code в books — проверить что mcp__npm-mcp__* инструменты доступны и search_docs отвечает. Если открываем books на другой машине — подставить локальный путь к клону npm-mcp (или вариант B из общей вики concepts/local-stdio-mcp-deploy — user-scope mcp add, чтобы не коммитить машинно-специфичный путь).
Branch: master
⚪ [refresh-project-bootstrap] — Run the project-bootstrap skill in this repo to refresh its layout to the canonical state (git, .gitignore, README.md, .wiki/, .tasks/, CLAUDE.md). The skill handles both greenfield bootstrap and refresh of existing repos.
Status: ready
Where I stopped: (not started)
Next action: Invoke the project-bootstrap skill at the repo root, confirm each file write, push to main.
Branch: n/a
🟢 [bootstrap-upgrade-pilot] — CLOSED 2026-05-07 — project-bootstrap v1.10.0 upgrade pilot успешен
Status: done
Outcome: Bootstrap v1.10.0 идемпотентно добавил 3 новых триггера в CLAUDE.md: follow tdd-criteria, delegate to interns when allowed, recommend, don't menu. Существующие строки нетронуты, пользовательские комментарии (первые 6 строк) сохранены. Идемпотентность подтверждена — повторный прогон находит 0 missing triggers. Skill-deps check (Step 5.6) корректно валидирует наличие всех 11 fulfiller'ов на хосте. Commit 5ca16d9. Массовая раскатка на остальные проекты — следуя приоритету: _meta (координатор) → claude-skills → npm-mcp → books (уже done) → остальные.
🟢 [domain-promotions-pointers] — CLOSED 2026-05-12 — Bootstrap-pointers для design domain-promotions. Секция "Domain conventions" добавлена в .wiki/CLAUDE.md.
Status: done
Outcome: В .wiki/CLAUDE.md добавлена секция "Domain conventions" с блоком "Mandatory: read design context before implementation". Три источника в порядке: (1) global wiki concepts/domain-promotions через mcp__projects-meta__knowledge_get, (2) brainstorm trace ~/projects/.workshop/.archive/2026-05-12-books-domain-promotions.md, (3) локальный overview.md для быстрой ориентации. Следующие агенты будут знать где лежит полный design context а не угадывать из where_stopped one-liner'ов.
🟢 [domain-promotions-schema] — CLOSED 2026-05-13 — Миграция + Sequelize-модели для доменных акций-скидок
Status: closed
Outcome: Migration 016 (b732360): три CREATE TABLE IF NOT EXISTS — promotions, promotion_products, promotion_price_snapshots. Event-based таймстампы в promotion_products (addedAt/appliedAt/restoredAt/removedAt/failedAt). Индексы: promotions(status, start_at), promotion_products(id_promotion, removed_at), promotion_price_snapshots(id_promotion, id_product) UNIQUE. FK: promotion→seller (CASCADE), product→promotion (CASCADE), snapshot→promotion+product (CASCADE). 3 sequelize-модели с camelCase/snake_case field mapping. [skip-tdd: oneshot] [skip-tdd: wrapper] — migration (Q7) + model declarations ≤10 significant lines (Q8). Smoke idempotency — pending prod apply.
✅ domain-promotions-lib — CLOSED 2026-05-13 — f61c679 — 4 pure-logic модуля + 24/24 unit-теста green
Status: closed
Outcome: resolve-cheapest-winner.js (pure, 5 тестов), snapshot-source.js (thunk-injected, 4 теста), defensive-restore.js (tolerance-based, 7 тестов), apply-price-to-ozon.js (chunked+retry, 8 тестов). Pure ядро изолировано от I/O через thunk-инжекцию. CLI-таски импортируют через import { resolveCheapestWinner } from './_lib/promotions/resolve-cheapest-winner.js'. Разблокирует 5 CLI-таск.
✅ domain-promotions-create-cli — CLOSED 2026-05-13 — 35a0308 — validation TDD 8/8 green + Commander wrapper
Status: closed
Outcome: promotion-create.js с гибридным export+CLI. Валидация: endAt>startAt (2 теста), discountPercent 0-100 (2 теста), ISO8601 даты (2 теста), seller exists (1 тест), успешное создание (1 тест). Commander обёртка [skip-tdd: wrapper].
✅ domain-promotions-add-products-cli — CLOSED 2026-05-13 — 2573454 — inline apply + idempotency 14/14 green
Status: closed
Outcome: promotion-add-products.js. Scheduled: товары добавляются с addedAt без appliedAt. Active: inline snapshot+resolve+apply через lib модули. Идемпотентность: повторный вызов skip'ает уже добавленные, воскрешает removed. Commander обёртка [skip-tdd: wrapper].
✅ domain-promotions-remove-products-cli — CLOSED 2026-05-13 — 9d4f2f4 — soft remove + defensive restore 7/7 green
Status: closed
Outcome: promotion-remove-products.js. Soft remove: запись остаётся с removedAt, без DELETE. Active+applied: inline defensive restore с cheapest-wins. Divergence не валит операцию. Commander обёртка [skip-tdd: wrapper].
✅ domain-promotions-activate-cli — CLOSED 2026-05-13 — f68ff22 — scheduled→active + snapshot+apply 8/8 green
Status: closed
Outcome: promotion-activate.js. Status guard: только scheduled→active. На каждый товар: snapshot source → resolve cheapest-winner → apply price. Поддержка actionPriceOverride. API ошибка на одном товаре не валит остальных. Commander обёртка [skip-tdd: wrapper].
✅ domain-promotions-deactivate-cli — CLOSED 2026-05-13 — 31fde95 — active→ended + defensive restore 7/7 green
Status: closed
Outcome: promotion-deactivate.js. Status guard: active→ended, не ended/cancelled. Каждый applied товар: resolve cheapest-winner (self-excluding) → defensive restore (push/diverged). Pending товары: soft mark removedAt. Commander обёртка [skip-tdd: wrapper].
🟢 [domain-promotions-review] — Code-review checkpoint для брейнсторма domain-promotions (промоушен 2026-05-12).
Спецификация: concepts/domain-promotions в global wiki (читать через mcp__projects-meta__knowledge_get slug=concepts/domain-promotions).
Pre-impl bootstrap: domain-promotions-pointers (заполнил .wiki/CLAUDE.md Domain conventions — без него review бы читал stub).
Импл-таски (review против их acceptance criteria): domain-promotions-schema, domain-promotions-lib, domain-promotions-create-cli, domain-promotions-add-products-cli, domain-promotions-remove-products-cli, domain-promotions-activate-cli, domain-promotions-deactivate-cli.
Кто делает: не имплементер. Следующая сессия в этом проекте (другая модель / другой день / другой агент) поднимает таску с чистым контекстом. «Я только что это написал» bias = главный риск.
Чек-лист ревью:
- Прочитать спецификацию (acceptance criteria каждой импл-таски).
git log --onelineshipped-коммитов (по slug или scope в commit-message).- Для каждой импл-таски: прогнать соответствующие тесты, реально проверить что они доходят до своих веток (не coverage-illusion).
- Сверить дизайн-decisions со shipped-кодом (signature, params, error-paths, безопасность).
- Особое внимание: cheapest-wins overlap (сценарии
A→B→A end→B endиA→B→B end→A end), defensive-restore divergence detect, soft-remove семантика (removedAtбез DELETE). - Findings — отдельные follow-up tasks (
domain-promotions-<gap>-fixили подобное) черезtasks_create.
Закрытие: только когда все findings зафайлены ИЛИ ревьюер подтвердил «нет findings» в close-note.
Status: done Where I stopped: Review done 2026-05-13. 7 findings filed (3 critical, 3 medium, 1 low) → 4 follow-up tasks created: fix-snapshot-persistence, fix-status-timing, fix-removed-filter, fix-divergence-persist. 68/68 tests green but coverage illusion — production paths (snapshot write, Ozon API calls) not covered. Next action: (none — kept until merged) Blocker: bootstrap: domain-promotions-pointers; impl-tasks: domain-promotions-schema, domain-promotions-lib, domain-promotions-create-cli, domain-promotions-add-products-cli, domain-promotions-remove-products-cli, domain-promotions-activate-cli, domain-promotions-deactivate-cli Branch: n/a
🟢 [domain-promotions-fix-snapshot-persistence] — Fix CRITICAL F1+F2+F3 from review: (1) idPromotionProduct→idPromotion+idProduct in snapshot lookup, (2) write PromotionPriceSnapshot.create() in activate+add-products inline, (3) pass product_id to defensiveRestore()
Status: done Where I stopped: Fixed F1 (idPromotion+idProduct lookup instead of idPromotionProduct), F2 (PromotionPriceSnapshot.create in activate+add-products), F3 (product_id passed to defensiveRestore). 5 tests added (activate F2, deactivate F1+F3, add-products F2, remove-products F1+F3). 80/80 green. Next action: (none — kept until merged) Branch: n/a
🟢 [domain-promotions-fix-status-timing] — Fix MEDIUM F4 from review: move promo.status change AFTER processing products in activate+deactivate, not before
Status: done
Where I stopped: Fixed F4: status change moved after product processing loop with if (failed === 0) guard in both activate and deactivate. 2 tests added. 80/80 green.
Next action: (none — kept until merged)
Branch: n/a
🟢 [domain-promotions-fix-removed-filter] — Fix MEDIUM F5 from review: activatePromotion must filter removedAt IS NULL, per spec "Snapshot+apply всех removedAt IS NULL"
Status: done
Where I stopped: Fixed F5: added removedAt: null filter to PromotionProduct.findAll in activatePromotion. 1 test added verifying where clause. 80/80 green.
Next action: (none — kept until merged)
Branch: n/a
🟢 [domain-promotions-fix-divergence-persist] — Fix LOW F6+F7 from review: (1) write divergedAt/divergedFrom/divergedTo to PromotionPriceSnapshot on divergence, (2) add integration tests covering production paths (snapshot write→find→restore→diverge)
Status: done Where I stopped: Fixed F6+F7: divergence fields (divergedAt/divergedFrom/divergedTo) written to snapshot on divergence in both deactivate and remove-products. Integration test (F7) added covering full snapshot chain. 3 tests added. 80/80 green. Next action: (none — kept until merged) Branch: n/a
🟢 [analyst-workflow-claude-md] — DONE 2026-05-15 — секция Analyst workflow в корневом CLAUDE.md
Status: done
Where I stopped: Закоммичено 44d97b0. Секция 39 строк после we're on Windows: триггер-фразы (выручка / средний чек / сезонность / staff perf / X by month / export / one-off), 3 шага (load .wiki/concepts/ → check packages/tools/{README,analyst/} → write new), inline-блок стиля 11 пунктов (ESM, hybrid CLI+module, commander+isDirectRun, JSDoc, bin+exports, ozon/yandex client напрямую, sequelize+raw SQL, camel/snake mapping, {meta, tables} return shape, форматтеры _lib/, .cmd-обёртки), read-каналы (wiki/README/plans/sequelize), write-канал, anti-patterns (6 пунктов).
Next action: (none — kept until merged)
Branch: master (local, not pushed)
🟢 [tools-ci-path-filter] — DONE 2026-05-15 (частично, спека ложна) — skip api/sched/tr build на CLI-only-tools diff
Status: done (narrowed scope)
Where I stopped: Закоммичено 102a92e в .gitea/workflows/build.yml. Спека «исключить весь packages/tools» — ложная: @books/tools/* импортится в packages/task-runner/tasks/*.js (13+ файлов: fbs-ship-, sync-prices-, load-products-to-ozon, awaiting-packaging-, sync-product-sales-channel-, etc.) и в packages/api/server/routes/fbs/* (4 файла). Выпиливание tools/ из api_pat/sched_pat/tr_pat целиком сломало бы prod runtime. Сужено до tools_skip_pat='^packages/tools/(analyst|plans|not-for-qwen|output)/|^packages/tools/(README|QWEN)\.md$|^packages/tools/[^/]+\.cmd$' — эти подпути проверены grep'ом на 0 импортов из runtime. Top-level packages/tools/*.js НЕ исключаем (там смесь runtime-imported и CLI-only). Test/lint job для tools — в .gitea/workflows/* отсутствуют вообще, спека приняла желаемое за сущее.
Next action: (none — kept until merged). Follow-up для расширения win: move analyst-CLI с top-level (ozon-revenue-per-product, ozon-transactions-analysis, ozon-products-price-analysis, archive-product, unarchive-product, products-import-by-sku, get-products-info-by-sku, update-drugs-blocked-products, ozon-promotion-*) в analyst/ subdir.
Branch: master (local, not pushed)
🔵 [analyst-workflow-review] — Review-checkpoint для брейнсторма books-analyst-workflow (промоушен 2026-05-14). Спецификация: workshop archive 2026-05-14-books-analyst-workflow.md. Impl-таски: analyst-workflow-claude-md, tools-ci-path-filter. Кто делает: НЕ имплементер. Следующая сессия с чистым контекстом (другая модель, другой день, другой агент) — бороться с bias я только что это написал. Чек-лист: 1) прочитать спецификацию из workshop archive; 2) проверить books CLAUDE.md секция Analyst workflow добавлена и читается на чистый старт сессии без двусмысленностей; 3) реально прогнать 2-3 триггер-фразы в свежей сессии (посчитай выручку, сезонность, отчёт) и убедиться что workflow корректно срабатывает: я читаю packages tools README/analyst до того как писать новый; 4) проверить CI: коммит strictly в packages tools не запускает deploy-job; обратное (коммит в packages api/data) триггерит как раньше; 5) finding ones — отдельные follow-up tasks через tasks_create. Закрытие: только когда все findings зафайлены или ревьюер подтвердил нет findings.
Status: blocked Where I stopped: (not started) Next action: Дождаться зелёных blocker-тасок (analyst-workflow-claude-md и tools-ci-path-filter). Затем прогнать чек-лист (см. description). Findings — отдельные follow-up tasks через tasks_create. Blocker: impl-tasks: analyst-workflow-claude-md, tools-ci-path-filter Branch: n/a