Files
admin/scripts/migrate-tasks-v2-files.mjs

97 lines
4.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Rename leftover v1-named .tasks files → yyyy-mm-dd-<slug>.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();