From 2b82a83cfbe30f1e883be5e9449d8c0bdaa67e15 Mon Sep 17 00:00:00 2001 From: vitya Date: Sun, 23 Aug 2026 11:35:24 +0300 Subject: [PATCH] meta(handoff): v2 fully complete (numbers 831..966, 9 restored blocks, counter=966) --- .tasks/NEXT_SESSION.md | 43 +++++---- scripts/migrate-tasks-v2-numbers.mjs | 129 +++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 scripts/migrate-tasks-v2-numbers.mjs diff --git a/.tasks/NEXT_SESSION.md b/.tasks/NEXT_SESSION.md index 0d85bae..776bc85 100644 --- a/.tasks/NEXT_SESSION.md +++ b/.tasks/NEXT_SESSION.md @@ -1,33 +1,32 @@ --- -_last_updated_: 2026-08-23T08:35:00Z -session_id: migration-v2-files-cleanup-2026-08-23 +_last_updated_: 2026-08-23T08:52:00Z +session_id: migration-v2-numbers-done-2026-08-23 --- # Next session handoff -## Текущий статус: миграция v2 ПОЛНАЯ (блоки + файлы) +## Текущий статус: миграция v2 ЗАВЕРШЕНА ПОЛНОСТЬЮ (блоки + файлы + номера + counter=966) -Этап 3 закрыт ранее (#806). Follow-up по аудиту workshop (157 файлов) выполнен по распоряжению оператора: «переименуй и присвой номера всему нужному во всех проектах». +Директивы оператора (через workshop 08-29) выполнены. Письмо: `.workshop/.agents/inbox/2026-08-23T08-50-00Z-.admin.md`. -### Сделано (follow-up) -- **136 файлов** → `yyyy-mm-dd-.md` (дата из git log, fallback mtime) в 14 репо (.common 33, .admin 22, coworker-skill 22, sched 20, skills 19, books 5, snolla 4, + мелкие). Коммиты+push; coworker-skill — локально (github). -- **kreknin-repair-md3-rebuild** — живая таска восстановлена: блок на доске `.admin`, **#830**, файл `2026-08-03-00830-...md`. counter 829→**830** (agenda, запушен, сервер подтвердил). -- Повторный скан: 0 файлов без v2-имени в обрабатываемых репо. Парсер MCP v2 OK. -- Письмо workshop: `.workshop/.agents/inbox/2026-08-23T08-30-00Z-.admin.md`. +### Итог +- **829 задач** мигрированы (этап 3, #806 закрыта) → **9 восстановленных блоков** → 838 блоков на досках (books 80, snolla 152, sched 41, penrose 1, + прочие). +- **144 файла** получили полные v2-имена `yyyy-mm-dd-#####-.md`: 136 (архив/могилы/спеки, номера 831..966 по датам) + 8 `-plan`-компаньонов (номер родителя). +- **counter = 966** (agenda, сервер подтверждён). Следующая таска → #967. +- Скан: 0 файлов не в v2-формате (во всех обрабатываемых репо). Парсер MCP v2 OK. +- Скрипты: `.admin/scripts/migrate-tasks-v2.mjs` (основная миграция), `migrate-tasks-v2-files.mjs` (ренейм), `migrate-tasks-v2-numbers.mjs` (нумерация+восстановление блоков). + +### Номера восстановленных тасок +shelf-sale #902, reprice-cli #934, reverse-engineer-schematics #936, admin-api-batch-info-wave #940, r6-daemon #956, r6-docs #957, r6-lastrun #958, r6-manual #959, r6-ssh #960, kreknin-repair #830. ### Осталось (по убыванию) -1. **meeting-room** — merge-conflict (UU). Разрешить → тогда миграция 2 тасок + 12 файлов отдельно (`node scripts/migrate-tasks-v2-files.mjs --apply` после фикса; номера 831+). -2. **Кандидаты на проверку оператором** (файлы без признаков завершения, блоки НЕ восстановлены — спек/записи): `sched/r6-*` (9), `snolla/admin-api-batch-info-wave`, `penrose-quantizer/reverse-engineer-schematics`, `books/ozon-promotion-reprice-cli` + `slovo-shelf-sale-25pct`. Если оператор скажет «живые» → восстановить блоки с номерами 831+. -3. **projects-meta-mcp** (архив) + **yt-tools** (untracked github) — согласованные скипы. -4. **coworker-skill** — мигрирован+переименован локально, не запушен. Решение по push за оператором. -5. MCP-кэш — stale, обновится циклом поллера. - -### Инфра-заметки -- Скрипт переименования: `.admin/scripts/migrate-tasks-v2-files.mjs` (dry-run/apply; SPECIAL — крекнин-кейс; SKIP — 5 репо). -- Дата файла: `git log --follow --diff-filter=A` (первое добавление), fallback mtime. -- Формат закрытых записей: `yyyy-mm-dd-.md` (без номера — номера только для тасок на доске). +1. **meeting-room** — merge-conflict. Разрешить → миграция тасок + файлы отдельно (номера 967+). +2. **Кандидаты, НЕ восстановленные** (не были в списке оператора): `sched/storage-adapter-mariadb-mysql`, `run-log-offset`, `runtime-task-api` (SHIPPED 0.28.0 — done-запись), `live-sync-recovery`, `lock-heartbeat`, `run-metadata-parity`, `chore-typecheck-clean` — просто пронумерованы. Если оператор захочет блоки — легко добавить. +3. **coworker-skill** — всё мигрировано/пронумеровано локально, НЕ запушено (github). Решение по push. +4. **projects-meta-mcp** (архив) + **yt-tools** (untracked) + **projects-wiki** (дубль .wiki) — согласованные скипы. +5. MCP-кэш — stale, обновится поллером. ### Решения оператора (не закрыты) -- Судьба 5 кандидатов из п.2 (живые или закрыть). -- llm-web-proxy-qwen-web: закрывать или нет (из прошлого хэндоффа). -- coworker-skill push (meta-leak риск на github). +- Судьба 7 sched-кандидатов из п.2. +- llm-web-proxy-qwen-web: закрывать или нет. +- coworker-skill push (meta-leak на github). diff --git a/scripts/migrate-tasks-v2-numbers.mjs b/scripts/migrate-tasks-v2-numbers.mjs new file mode 100644 index 0000000..c5c31bd --- /dev/null +++ b/scripts/migrate-tasks-v2-numbers.mjs @@ -0,0 +1,129 @@ +// Number all date-only .tasks files (yyyy-mm-dd-.md) → full v2 (yyyy-mm-dd-NNNNN-.md). +// Directive 2026-08-23 (operator via workshop): numbers for ALL leftover files + restore blocks +// for the 9 candidate tasks (sched r6-* ×5, snolla admin-api-batch-info-wave, penrose +// reverse-engineer-schematics, books reprice-cli + shelf-sale). Counter 830 → 830+count. +// Numbers assigned in creation-date order (spec: number encodes creation order), tiebreak repo/slug. +import { execSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; + +const ROOT = "C:/Users/vitya/projects"; +const APPLY = process.argv.includes("--apply"); +const SKIP = new Set(["meeting-room", "projects-meta-mcp", "yt-tools", "projects-wiki", "alpha"]); +const NEXT = 831; // counter was 830 (kreknin took #830) + +// (repo, slug) → {status, desc} for restored blocks +const RESTORE = { + "sched/r6-daemon-storage-option": { st: "⚪", desc: "r6 D1: createDaemon({ storage }) — daemon принимает storage-адаптер (дока 07.storage обещает, createDaemon — нет)" }, + "sched/r6-docs-batch": { st: "⚪", desc: "Доки по репорту r6 (F1/F4/F6/F7/F8/F9 — не код, правки текстов)" }, + "sched/r6-lastrun-status": { st: "⚪", desc: "r6 F10: в Schedules/Tasks нет статуса последнего рана (GET /api/schedules)" }, + "sched/r6-manual-trigger-lastrun": { st: "⚪", desc: "r6 F5: POST /tasks/:name/run (triggerTask) не трогает task.lastRunId/lastRunAt" }, + "sched/r6-ssh-fingerprint-error": { st: "⚪", desc: "r6: SSH fingerprint error при подключении воркеров" }, + "snolla/admin-api-batch-info-wave": { st: "⚪", desc: "POST /:resource/info для ВСЕХ шаблонизируемых сущностей (0.8.0; паттерн ozon-seller-api, решение vitya)" }, + "penrose-quantizer/reverse-engineer-schematics": { st: "⚪", desc: "Разобрать схемы Penrose Quantizer (оригинал + BugBrand), pin mapping + отличия в вики" }, + "books/ozon-promotion-reprice-cli": { st: "⚪", desc: "Штатный CLI смены discount_percent у активной доменной акции (сейчас reprice ручной двухшаговый)" }, + "books/slovo-shelf-sale-25pct": { st: "🔴", desc: "Slovo долгопрод −25% на 2 месяца (канал id_sales_channel=2), аналог domain-promotions" }, +}; + +function git(repoDir, args) { + try { + return execSync(`git -C "${repoDir}" ${args}`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim(); + } catch { return ""; } +} + +function collect() { + const dirs = new Set(fs.readdirSync(ROOT).filter(d => { try { return fs.statSync(path.join(ROOT, d)).isDirectory(); } catch { return false; } })); + [".common", ".wiki", ".workshop", ".factory", ".admin"].forEach(d => dirs.add(d)); + const files = []; + for (const repo of dirs) { + if (SKIP.has(repo)) continue; + const td = path.join(ROOT, repo, ".tasks"); + if (!fs.existsSync(path.join(td, "STATUS.md"))) continue; + for (const sub of ["", "done"]) { + const dir = sub ? path.join(td, "done") : td; + if (!fs.existsSync(dir)) continue; + for (const f of fs.readdirSync(dir)) { + // date-only name: yyyy-mm-dd-.md (slug starts with a letter — numbered files have 5 digits after date) + if (!/^\d{4}-\d{2}-\d{2}-[a-z][a-z0-9-]*\.md$/.test(f)) continue; + const m = f.match(/^(\d{4}-\d{2}-\d{2})-(.+)\.md$/); + files.push({ repo, dir: sub, date: m[1], slug: m[2], old: f }); + } + } + } + files.sort((a, b) => + a.date < b.date ? -1 : a.date > b.date ? 1 + : a.repo < b.repo ? -1 : a.repo > b.repo ? 1 + : a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0); + files.forEach((f, i) => (f.number = NEXT + i)); + return files; +} + +function makeBlock(r, num) { + const spec = RESTORE[`${r.repo}/${r.slug}`]; + const st = spec ? spec.st : "⚪"; + const desc = spec ? spec.desc : r.slug; + const file = path.join(ROOT, r.repo, ".tasks", r.dir, r.old); + let where = ""; + try { + const content = fs.readFileSync(file, "utf8"); + const lines = content.split(/\r?\n/).filter(Boolean).slice(0, 14); + const goal = lines.find(l => /^## (Goal|Цель|Проблема)/.test(l)); + if (goal) { + const idx = lines.indexOf(goal); + const next = lines[idx + 1]; + if (next && !next.startsWith("#")) where = next.slice(0, 220); + } + } catch {} + return `## ${st} [#${num} ${r.slug}] — ${desc} + +**Status:** ${st === "🔴" ? "active" : "ready"} +**Created:** ${r.date} +**Where I stopped:** ${where || "Файл-спека без блока; восстановлен при миграции v2 (см. per-task файл)."} +**Next action:** См. per-task файл ${r.date}-${String(num).padStart(5, "0")}-${r.slug}.md (Goal/Open questions). +**Branch:** n/a +`; +} + +function main() { + const files = collect(); + const byRepo = {}; + for (const f of files) byRepo[f.repo] = (byRepo[f.repo] || 0) + 1; + const restored = files.filter(f => RESTORE[`${f.repo}/${f.slug}`]); + console.log(`файлов к нумерации: ${files.length} (номера ${NEXT}..${NEXT + files.length - 1}), восстановление блоков: ${restored.length}`); + for (const [r, n] of Object.entries(byRepo).sort((a, b) => b[1] - a[1])) console.log(` ${r.padEnd(20)} ${n}`); + console.log("восстановить:", restored.map(r => `#${r.number} ${r.repo}/${r.slug}`).join("; ")); + if (!APPLY) { console.log("\ndry-run (--apply для выполнения)"); return; } + + const byRepoFiles = {}; + for (const f of files) (byRepoFiles[f.repo] = byRepoFiles[f.repo] || []).push(f); + for (const [repo, list] of Object.entries(byRepoFiles)) { + const repoDir = path.join(ROOT, repo); + const conflicted = git(repoDir, "status --porcelain").split("\n").some(l => /^(UU|AA|DD|AU|UA|DU|UD)/.test(l)); + if (conflicted) { console.log(`skip ${repo}: conflicted`); continue; } + let renamed = 0; + for (const f of list) { + const dir = f.dir ? path.join(repoDir, ".tasks", "done") : path.join(repoDir, ".tasks"); + const src = path.join(dir, f.old); + const dst = path.join(dir, `${f.date}-${String(f.number).padStart(5, "0")}-${f.slug}.md`); + if (fs.existsSync(src)) { fs.renameSync(src, dst); renamed++; } + } + // insert restored blocks (append at board end) + const td = path.join(repoDir, ".tasks"); + const statusPath = path.join(td, "STATUS.md"); + let status = fs.readFileSync(statusPath, "utf8").replace(/\r?\n$/, ""); + let blocksAdded = 0; + for (const f of list.filter(x => RESTORE[`${x.repo}/${x.slug}`])) { + status += "\n---\n" + makeBlock(f, f.number); + blocksAdded++; + } + if (blocksAdded) fs.writeFileSync(statusPath, status); + if (renamed || blocksAdded) { + git(repoDir, "add -- .tasks/"); + git(repoDir, `commit -m "meta(tasks): number ${renamed} legacy files to v2 + restore ${blocksAdded} blocks" -q`); + console.log(`done: ${repo} (renamed ${renamed}, blocks ${blocksAdded})`); + } + } + console.log(`\nИтог: counter должен стать ${NEXT + files.length - 1} (записать в agenda).`); +} + +main();