Files
fusion/scripts/reimport-tasks.mjs
gsxdsm 17b9d851c3 chore: add scripts/reimport-tasks.mjs recovery utility
One-off helper to re-import specific FN-* tasks from .fusion/tasks/<id>/task.json
back into the live SQLite DB after a restore. Mirrors the column list used by
db-migrate.ts:migrateTasks but uses INSERT OR IGNORE so existing rows are
never overwritten. Used to rebuild FN-5415..FN-5425 after the FN-5407 backup
corruption recovery.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:25:15 -07:00

141 lines
4.5 KiB
JavaScript

// One-off: re-import specific FN-* tasks from .fusion/tasks/<id>/task.json into
// the live SQLite DB. Uses INSERT OR IGNORE — never overwrites existing rows.
// Mirrors the column list used by packages/core/src/db-migrate.ts:migrateTasks.
import { DatabaseSync } from "node:sqlite";
import { readFileSync, existsSync } from "node:fs";
import { join } from "node:path";
const FUSION_DIR = join(process.cwd(), ".fusion");
const DB_PATH = join(FUSION_DIR, "fusion.db");
const TASKS_DIR = join(FUSION_DIR, "tasks");
const TARGET_IDS = process.argv.slice(2);
if (TARGET_IDS.length === 0) {
console.error("usage: node scripts/reimport-tasks.mjs FN-5414 FN-5415 ...");
process.exit(2);
}
const toJson = (v) => JSON.stringify(v ?? []);
const toJsonNullable = (v) => (v == null ? null : JSON.stringify(v));
const db = new DatabaseSync(DB_PATH);
db.exec("PRAGMA busy_timeout = 5000");
db.exec("PRAGMA foreign_keys = ON");
const insertStmt = db.prepare(`
INSERT OR IGNORE INTO tasks (
id, title, description, priority, "column", status, size, reviewLevel, currentStep,
worktree, blockedBy, paused, baseBranch, branch, executionStartBranch, baseCommitSha, modelPresetId,
modelProvider, modelId, validatorModelProvider, validatorModelId,
mergeRetries, recoveryRetryCount, nextRecoveryAt,
error, summary, thinkingLevel, createdAt, updatedAt,
columnMovedAt, dependencies, steps, log, attachments, steeringComments,
comments, workflowStepResults, prInfo, issueInfo,
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
mergeDetails, breakIntoSubtasks, noCommitsExpected, enabledWorkflowSteps, modifiedFiles, sliceId
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
`);
const existsStmt = db.prepare(`SELECT 1 AS hit FROM tasks WHERE id = ?`);
let imported = 0;
let skipped = 0;
let missing = 0;
for (const id of TARGET_IDS) {
const path = join(TASKS_DIR, id, "task.json");
if (!existsSync(path)) {
console.log(`[skip] ${id}: no task.json on disk`);
missing++;
continue;
}
if (existsStmt.get(id)) {
console.log(`[skip] ${id}: already exists in DB`);
skipped++;
continue;
}
let task;
try {
task = JSON.parse(readFileSync(path, "utf-8"));
} catch (err) {
console.warn(`[fail] ${id}: invalid JSON — ${err.message}`);
skipped++;
continue;
}
const priority =
task.priority === "critical" || task.priority === "high" || task.priority === "low"
? task.priority
: "normal";
// Migration normalizes steeringComments + comments together; for fresh
// imports it's safe to just trust the persisted JSON.
const steeringComments = task.steeringComments ?? [];
const comments = task.comments ?? [];
insertStmt.run(
task.id,
task.title ?? null,
task.description ?? "",
priority,
task.column,
task.status ?? null,
task.size ?? null,
task.reviewLevel ?? null,
task.currentStep || 0,
task.worktree ?? null,
task.blockedBy ?? null,
task.paused ? 1 : 0,
task.baseBranch ?? null,
task.branch ?? null,
task.executionStartBranch ?? null,
task.baseCommitSha ?? null,
task.modelPresetId ?? null,
task.modelProvider ?? null,
task.modelId ?? null,
task.validatorModelProvider ?? null,
task.validatorModelId ?? null,
task.mergeRetries ?? null,
task.recoveryRetryCount ?? null,
task.nextRecoveryAt ?? null,
task.error ?? null,
task.summary ?? null,
task.thinkingLevel ?? null,
task.createdAt,
task.updatedAt,
task.columnMovedAt ?? null,
toJson(task.dependencies),
toJson(task.steps),
toJson(task.log),
toJson(task.attachments),
toJson(steeringComments),
toJson(comments),
toJson(task.workflowStepResults),
toJsonNullable(task.prInfo),
toJsonNullable(task.issueInfo),
task.sourceIssue?.provider ?? null,
task.sourceIssue?.repository ?? null,
task.sourceIssue?.externalIssueId ?? null,
task.sourceIssue?.issueNumber ?? null,
task.sourceIssue?.url ?? null,
toJsonNullable(task.mergeDetails),
task.breakIntoSubtasks ? 1 : 0,
task.noCommitsExpected ? 1 : 0,
toJson(task.enabledWorkflowSteps),
toJson(task.modifiedFiles),
task.sliceId ?? null,
);
console.log(`[ok] ${id}: imported (col=${task.column} status=${task.status ?? "-"})`);
imported++;
}
db.close();
console.log(`\ndone: imported=${imported} skipped=${skipped} missing=${missing}`);