/** * One-off relink helper. * * Re-runs the linking engine on a project without pulling in the server-only * modules — same pattern as scripts/backfill.ts. Run with: * set -a && source .env.local && set +a * npx tsx scripts/relink.ts */ import PocketBase from "pocketbase"; import { resolveCommitCard, type LinkCandidate } from "../src/lib/linking/resolve"; const PB_URL = process.env.NEXT_PUBLIC_PB_URL!; async function main() { const projectId = process.argv[2]; if (!projectId) throw new Error("Usage: tsx scripts/relink.ts "); const pb = new PocketBase(PB_URL); pb.autoCancellation(false); await pb.collection("_superusers").authWithPassword( process.env.PB_SUPERUSER_EMAIL!, process.env.PB_SUPERUSER_PASSWORD! ); const cards = await pb.collection("cards").getFullList({ filter: `project = "${projectId}"`, fields: "id,number,scope_rule,start_date,created,archived", }); const candidates: LinkCandidate[] = cards.map((card) => ({ id: card.id, number: card.number, scopeRule: card.scope_rule ?? "", startDate: card.start_date ?? "", created: card.created, archived: Boolean(card.archived), })); const commits = await pb.collection("commits").getFullList({ filter: `project = "${projectId}"`, fields: "id,message,scope,authored_at,is_merge,card,link_source", }); let linked = 0; let unlinked = 0; let preserved = 0; for (const commit of commits) { if (commit.link_source === "manual") { preserved++; continue; } const resolution = resolveCommitCard( { message: commit.message ?? "", scope: commit.scope ?? "", authoredAt: commit.authored_at, isMerge: Boolean(commit.is_merge), }, candidates ); const nextCard = resolution.cardId ?? ""; const nextSource = resolution.source; if (commit.card === nextCard && commit.link_source === nextSource) continue; await pb.collection("commits").update(commit.id, { card: nextCard, link_source: nextSource, }); if (nextCard) linked++; else unlinked++; } console.log( JSON.stringify({ examined: commits.length - preserved, linked, unlinked, preserved }) ); } main().catch((e) => { console.error(e); process.exit(1); });