diff --git a/.tasks/NEXT_SESSION.md b/.tasks/NEXT_SESSION.md index ad57b94..0d85bae 100644 --- a/.tasks/NEXT_SESSION.md +++ b/.tasks/NEXT_SESSION.md @@ -1,34 +1,33 @@ --- -_last_updated_: 2026-08-23T07:30:00Z -session_id: migration-v2-executed-2026-08-23 +_last_updated_: 2026-08-23T08:35:00Z +session_id: migration-v2-files-cleanup-2026-08-23 --- # Next session handoff -## Текущий статус: миграция .tasks v2 ВЫПОЛНЕНА (этап 3 закрыт) +## Текущий статус: миграция v2 ПОЛНАЯ (блоки + файлы) -`tasks-migration-47-projects` закрыта (MCP `c529c9e8`, now #806). Корректировки письма workshop 2026-08-23 применены. Письмо-отчёт workshop: `.admin/.agents/inbox/2026-08-23T07-24-00Z-.workshop.md`. +Этап 3 закрыт ранее (#806). Follow-up по аудиту workshop (157 файлов) выполнен по распоряжению оператора: «переименуй и присвой номера всему нужному во всех проектах». -### Сделано (итоги) -- **48 репо, 829 задач** мигрированы: имена `yyyy-mm-dd-00001-slug.md` (5 знаков, без `#`), 🟢→`.tasks/done/`, шапки `[#n slug]`, `**Created:**` (без дублей), блокеры слаги→номера (вкл. backtick). verify парсером MCP: OK. -- `counter = 829` в `OpeItcLoc03/agenda/task-counter` (перезапись post-шаг), запушен. -- Все репо запушены (43 коммита миграции + agenda). Скрипт: `.admin/scripts/migrate-tasks-v2.mjs` (флаги `--apply/--verify/--repos=/--skip=/--extra=`), план `/tmp/tasks-v2-plan.json`. -- Смок-репо (snolla/.admin/artmone.pro) откачены до прогона. `#2`→#818, `#3`→#824, `#4`→#825. +### Сделано (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`. -### Открытые хвосты (по убыванию важности) -1. **meeting-room** — unresolved merge (UU) → не мигрирован (2 таски). Разрешить конфликт, затем миграция отдельно (`node scripts/migrate-tasks-v2.mjs --apply --repos=meeting-room`, номера получат 830+). -2. **projects-meta-mcp** — заархивирован на сервере (таски в `.common`) → не мигрирован. Если архивация подтверждена — можно удалить локальный `.tasks` или оставить. -3. **yt-tools** — `.tasks/` не в git + remote github → не мигрирован. Решение: юзеру (track в gitea-хост или оставить). -4. **coworker-skill** — мигрирован ЛОКАЛЬНО, НЕ запушен (remote github). Ждёт решения юзера про push (meta-leak риск). -5. **projects-wiki** — дубль-клон `.wiki` (тот же репо `projects-wiki.git`). Мигрирован только `.wiki`. Кандидат на удаление дубля. -6. **MCP-кэш** — stale (synced 2026-08-22). Обновится циклом поллера; проверить `meta_status` при следующей сессии. -7. Старые текстовые ссылки `#237` в досках не переписывались (не машиночитаемы). - -### Решения юзера (из прошлого хэндоффа, не закрыты) -- `llm-web-proxy-qwen-web`: закрывать или нет (жизнь в браузере работает, стриминг/тулы вживую не гонял) — ждёт решения. -- Новое: судьба 4 репо из «Открытые хвосты» выше. +### Осталось (по убыванию) +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, обновится циклом поллера. ### Инфра-заметки -- Скрипт миграции детерминирован: collect → план → apply (именования перезапускаемы, idempotent). -- Дата задачи: git first-add файла → самое раннее `[slug]` в истории STATUS.md → created-by → mtime. -- Блокеры резолвятся repo-local сначала, потом глобально (first-wins). +- Скрипт переименования: `.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` (без номера — номера только для тасок на доске). + +### Решения оператора (не закрыты) +- Судьба 5 кандидатов из п.2 (живые или закрыть). +- llm-web-proxy-qwen-web: закрывать или нет (из прошлого хэндоффа). +- coworker-skill push (meta-leak риск на github). diff --git a/scripts/migrate-tasks-v2-files.mjs b/scripts/migrate-tasks-v2-files.mjs new file mode 100644 index 0000000..eaed86b --- /dev/null +++ b/scripts/migrate-tasks-v2-files.mjs @@ -0,0 +1,96 @@ +// Rename leftover v1-named .tasks files → yyyy-mm-dd-.md (date from git history). +// Scope: files with NO block in STATUS.md (archived-block companions, graves, spec files). +// Special: .admin/kreknin-repair-md3-rebuild → LIVE task → restore block + number 830. +// Companion files (foo-plan.md where foo has a block) are skipped. +// Skipped repos: meeting-room (merge conflict), projects-meta-mcp (archived), +// yt-tools (untracked+github), projects-wiki (dup clone), alpha (no git). +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 HEADER = /^##\s+\S+\s+\[(?:#\d+\s+)?([^\]]+)\]/gm; +const SKIP = new Set(["meeting-room", "projects-meta-mcp", "yt-tools", "projects-wiki", "alpha"]); +const SPECIAL = new Set(["kreknin-repair-md3-rebuild"]); // live → restore block + number 830 + +function git(repoDir, args) { + try { + return execSync(`git -C "${repoDir}" ${args}`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim(); + } catch { return ""; } +} + +function firstAddDate(repoDir, rel) { + const out = git(repoDir, `log --follow --diff-filter=A --format=%aI -- "${rel}"`); + const lines = out.split("\n").filter(Boolean); + if (!lines.length) return null; + return lines[lines.length - 1].slice(0, 10); +} + +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 plan = []; + 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; + const status = fs.readFileSync(path.join(td, "STATUS.md"), "utf8"); + const blocks = new Set([...status.matchAll(HEADER)].map(m => m[1])); + 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)) { + if (!f.endsWith(".md") || /^\d{4}/.test(f)) continue; + if (["STATUS.md", "ARCHIVE.md", "NEXT_SESSION.md"].includes(f)) continue; + const slug = f.slice(0, -3); + if (blocks.has(slug)) continue; + if (SPECIAL.has(slug)) continue; + const base = slug.replace(/-(plan|spec|design|docs|notes|research|draft|review)$/, ""); + if (base !== slug && blocks.has(base)) continue; + const rel = path.relative(ROOT, path.join(dir, f)).replace(/\\/g, "/"); + const date = firstAddDate(path.join(ROOT, repo), rel) || fs.statSync(path.join(dir, f)).mtime.toISOString().slice(0, 10); + plan.push({ repo, dir: sub, slug, old: f, date }); + } + } + } + return plan; +} + +function main() { + const plan = collect(); + const byRepo = {}; + for (const p of plan) byRepo[p.repo] = (byRepo[p.repo] || 0) + 1; + console.log(`файлов к переименованию: ${plan.length}`); + for (const [r, n] of Object.entries(byRepo).sort((a, b) => b[1] - a[1])) console.log(` ${r.padEnd(20)} ${n}`); + if (!APPLY) { + console.log("\ndry-run (--apply для выполнения). Примеры:"); + for (const p of plan.slice(0, 8)) console.log(` ${p.repo}/${p.dir ? "done/" : ""}${p.old} → ${p.date}-${p.slug}.md`); + return; + } + const byRepoFiles = {}; + for (const p of plan) (byRepoFiles[p.repo] = byRepoFiles[p.repo] || []).push(p); + for (const [repo, files] of Object.entries(byRepoFiles)) { + const repoDir = path.join(ROOT, repo); + if (!fs.existsSync(path.join(repoDir, ".git"))) { console.log(`skip ${repo}: no git`); continue; } + 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 n = 0; + for (const p of files) { + const dir = p.dir ? path.join(repoDir, ".tasks", "done") : path.join(repoDir, ".tasks"); + const src = path.join(dir, p.old); + const dst = path.join(dir, `${p.date}-${p.slug}.md`); + if (!fs.existsSync(src)) continue; + fs.renameSync(src, dst); + n++; + } + if (n) { + git(repoDir, "add -- .tasks/"); + git(repoDir, `commit -m "meta(tasks): rename ${n} legacy .tasks files to v2 date-prefix names" -q`); + console.log(`renamed+committed: ${repo} (${n})`); + } + } + console.log("\nСПЕЦИАЛЬНО (вне батча): kreknin-repair-md3-rebuild → блок + #830."); +} + +main();