@snapspecter/mcp-meta-mind
Version:
Meta Mind MCP Server - Advanced Model Context Protocol server for intelligent task management, workflow orchestration, and automatic archiving with hierarchical structures and agent specialization
286 lines โข 12.6 kB
JavaScript
import * as fs from "node:fs/promises";
import * as path from "node:path";
import * as os from "node:os";
import { TaskRepository } from "./taskRepository.js";
const DEFAULT_DATA_DIR = path.join(os.homedir(), ".meta_mind");
const ACTIVE_TASKS_FILE_PATH = path.join(DEFAULT_DATA_DIR, "active_tasks.json");
const COMPLETED_TASKS_FILE_PATH = path.join(DEFAULT_DATA_DIR, "completed_tasks.json");
const BACKUP_DIR = path.join(DEFAULT_DATA_DIR, "backup");
export class MigrationUtility {
taskRepository;
constructor() {
this.taskRepository = new TaskRepository();
}
/**
* Check if migration is needed (old JSON files exist)
*/
async isMigrationNeeded() {
try {
await fs.access(ACTIVE_TASKS_FILE_PATH);
return true;
}
catch {
return false;
}
}
/**
* Perform complete migration from JSON files to SQLite
*/
async performMigration() {
const result = {
success: false,
migratedRequests: 0,
migratedTasks: 0,
migratedArchivedTasks: 0,
errors: [],
};
try {
console.log("Starting migration from JSON files to SQLite...");
// Create backup directory
await fs.mkdir(BACKUP_DIR, { recursive: true });
// Step 1: Migrate active tasks
const activeTasksResult = await this._migrateActiveTasks();
result.migratedRequests = activeTasksResult.requestCount;
result.migratedTasks = activeTasksResult.taskCount;
result.errors.push(...activeTasksResult.errors);
// Step 2: Migrate completed/archived tasks
const completedTasksResult = await this._migrateCompletedTasks();
result.migratedArchivedTasks = completedTasksResult.archivedTaskCount;
result.errors.push(...completedTasksResult.errors);
// Step 3: Backup original files
await this._backupOriginalFiles();
result.success = result.errors.length === 0;
console.log(`Migration completed with ${result.errors.length} errors.`);
console.log(`Migrated: ${result.migratedRequests} requests, ${result.migratedTasks} active tasks, ${result.migratedArchivedTasks} archived tasks`);
return result;
}
catch (error) {
result.errors.push(`Fatal migration error: ${error instanceof Error ? error.message : String(error)}`);
return result;
}
}
/**
* Migrate active tasks from JSON file to SQLite
*/
async _migrateActiveTasks() {
const result = {
requestCount: 0,
taskCount: 0,
errors: [],
};
try {
const fileContent = await fs.readFile(ACTIVE_TASKS_FILE_PATH, "utf-8");
const data = JSON.parse(fileContent);
// Update metadata counters if needed
if (data.metadata) {
this.taskRepository.setMetadata("lastRequestId", data.metadata.lastRequestId.toString());
this.taskRepository.setMetadata("lastTaskId", data.metadata.lastTaskId.toString());
}
// Migrate each request and its tasks
for (const request of data.requests) {
try {
// Create request in database using existing ID
const stmt = this.taskRepository["db"].prepare(`
INSERT INTO requests (requestId, originalRequest, splitDetails, completed, createdAt, updatedAt)
VALUES (?, ?, ?, ?, ?, ?)
`);
stmt.run(request.requestId, request.originalRequest, request.splitDetails || "", request.completed ? 1 : 0, request.createdAt, request.updatedAt);
result.requestCount++;
// Migrate tasks for this request
for (const task of request.tasks) {
try {
await this._migrateTask(task, request.requestId);
result.taskCount++;
}
catch (error) {
result.errors.push(`Error migrating task ${task.id}: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
catch (error) {
result.errors.push(`Error migrating request ${request.requestId}: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
catch (error) {
if (error.code === "ENOENT") {
console.log("No active_tasks.json file found, skipping active tasks migration.");
}
else {
result.errors.push(`Error reading active tasks file: ${error instanceof Error ? error.message : String(error)}`);
}
}
return result;
}
/**
* Migrate completed/archived tasks from JSON file to SQLite
*/
async _migrateCompletedTasks() {
const result = {
archivedTaskCount: 0,
errors: [],
};
try {
const fileContent = await fs.readFile(COMPLETED_TASKS_FILE_PATH, "utf-8");
const data = JSON.parse(fileContent);
for (const bundle of data.archivedTaskBundles) {
try {
// Migrate root task
await this._migrateArchivedTask(bundle.archivedRootTask, bundle.originalRequestId, bundle.originalRequestText, bundle.archivedAt);
result.archivedTaskCount++;
// Migrate subtasks
for (const subtask of bundle.archivedSubtasks) {
await this._migrateArchivedTask(subtask, bundle.originalRequestId, bundle.originalRequestText, bundle.archivedAt);
result.archivedTaskCount++;
}
}
catch (error) {
result.errors.push(`Error migrating archived bundle for request ${bundle.originalRequestId}: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
catch (error) {
if (error.code === "ENOENT") {
console.log("No completed_tasks.json file found, skipping archived tasks migration.");
}
else {
result.errors.push(`Error reading completed tasks file: ${error instanceof Error ? error.message : String(error)}`);
}
}
return result;
}
/**
* Migrate a single task to the database
*/
async _migrateTask(task, requestId) {
const stmt = this.taskRepository["db"].prepare(`
INSERT INTO tasks (
taskId, requestId, parentId, title, description, status, priority, type,
dependsOn, subtaskIds, failureReason, suggestedRetryStrategy, completedDetails,
artifactsGenerated, environmentContext, summaryFilePath, costData,
feedbackHistory, retryCount, createdAt, updatedAt
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '{}', '{}', 0, ?, ?)
`);
stmt.run(task.id, requestId, task.parentId || null, task.title, task.description || null, task.status, task.priority, task.type || null, task.dependsOn && task.dependsOn.length > 0
? JSON.stringify(task.dependsOn)
: null, task.subtaskIds && task.subtaskIds.length > 0
? JSON.stringify(task.subtaskIds)
: null, task.failureReason || null, task.suggestedRetryStrategy || null, task.completedDetails || null, task.artifactsGenerated && task.artifactsGenerated.length > 0
? JSON.stringify(task.artifactsGenerated)
: null, task.environmentContext || null, task.summaryFilePath || null, task.createdAt, task.updatedAt);
}
/**
* Migrate a single archived task to the database
*/
async _migrateArchivedTask(task, originalRequestId, originalRequestText, archivedAt) {
const stmt = this.taskRepository["db"].prepare(`
INSERT INTO archived_tasks (
taskId, originalRequestId, originalRequestText, parentId, title, description,
status, priority, type, dependsOn, subtaskIds, failureReason, suggestedRetryStrategy,
completedDetails, artifactsGenerated, environmentContext, summaryFilePath,
costData, feedbackHistory, retryCount, createdAt, updatedAt, archivedAt
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '{}', '{}', 0, ?, ?, ?)
`);
stmt.run(task.id, originalRequestId, originalRequestText, task.parentId || null, task.title, task.description || null, task.status, task.priority, task.type || null, task.dependsOn && task.dependsOn.length > 0
? JSON.stringify(task.dependsOn)
: null, task.subtaskIds && task.subtaskIds.length > 0
? JSON.stringify(task.subtaskIds)
: null, task.failureReason || null, task.suggestedRetryStrategy || null, task.completedDetails || null, task.artifactsGenerated && task.artifactsGenerated.length > 0
? JSON.stringify(task.artifactsGenerated)
: null, task.environmentContext || null, task.summaryFilePath || null, task.createdAt, task.updatedAt, archivedAt);
}
/**
* Backup original JSON files
*/
async _backupOriginalFiles() {
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
try {
await fs.copyFile(ACTIVE_TASKS_FILE_PATH, path.join(BACKUP_DIR, `active_tasks_${timestamp}.json`));
console.log("Backed up active_tasks.json");
}
catch (error) {
// File might not exist, that's okay
}
try {
await fs.copyFile(COMPLETED_TASKS_FILE_PATH, path.join(BACKUP_DIR, `completed_tasks_${timestamp}.json`));
console.log("Backed up completed_tasks.json");
}
catch (error) {
// File might not exist, that's okay
}
}
/**
* Clean up original JSON files after successful migration
*/
async cleanupOriginalFiles() {
try {
await fs.unlink(ACTIVE_TASKS_FILE_PATH);
console.log("Removed original active_tasks.json");
}
catch (error) {
// File might not exist
}
try {
await fs.unlink(COMPLETED_TASKS_FILE_PATH);
console.log("Removed original completed_tasks.json");
}
catch (error) {
// File might not exist
}
}
/**
* Get migration status
*/
async getMigrationStatus() {
const hasOldFiles = await this.isMigrationNeeded();
const hasSqliteDatabase = this.taskRepository["db"] !== undefined;
return {
hasOldFiles,
hasSqliteDatabase,
needsMigration: hasOldFiles && hasSqliteDatabase,
};
}
}
/**
* Standalone migration function that can be called from command line
*/
export async function runMigration() {
const migration = new MigrationUtility();
const status = await migration.getMigrationStatus();
if (!status.needsMigration) {
if (!status.hasOldFiles) {
console.log("No old JSON files found. Migration not needed.");
}
else {
console.log("SQLite database not initialized. Cannot perform migration.");
}
return;
}
console.log("Old JSON files detected. Starting migration...");
const result = await migration.performMigration();
if (result.success) {
console.log("โ
Migration completed successfully!");
console.log(`๐ Migrated: ${result.migratedRequests} requests, ${result.migratedTasks} active tasks, ${result.migratedArchivedTasks} archived tasks`);
const cleanup = process.argv.includes("--cleanup");
if (cleanup) {
await migration.cleanupOriginalFiles();
console.log("๐งน Original files cleaned up.");
}
else {
console.log("๐พ Original files backed up. Use --cleanup flag to remove them.");
}
}
else {
console.error("โ Migration failed with errors:");
result.errors.forEach((error) => console.error(` โข ${error}`));
process.exit(1);
}
}
// If run directly
if (import.meta.url === `file://${process.argv[1]}`) {
runMigration().catch((error) => {
console.error("Fatal migration error:", error);
process.exit(1);
});
}
//# sourceMappingURL=migration.js.map