// 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();