UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

1,450 lines (1,318 loc) 155 kB
import { promises as fs } from 'fs'; import path from 'path'; import { SQLiteManager } from './sqlite-manager.js'; import { randomUUID } from 'crypto'; export interface MigrationResult { success: boolean; error?: string; migratedItems: number; errors: string[]; duration: number; backupPath?: string; } export interface MigrationStatus { needsMigration: boolean; isRequired: boolean; hasLegacyData: boolean; backupExists: boolean; lastMigration?: number; } /** * Data Migration Manager * Handles migration from file-based JSON storage to SQLite */ export class DataMigration { private db: SQLiteManager; private legacyDataPath: string; private backupPath: string; private useInternalMethods: boolean; constructor(db: SQLiteManager, dataPath = '.atlas', useInternalMethods = false) { this.db = db; this.legacyDataPath = path.join(dataPath, 'data'); this.backupPath = path.join(dataPath, 'backup'); this.useInternalMethods = useInternalMethods; } /** * Get data using appropriate method (internal during initialization, public otherwise) */ private async dbGet<T>(sql: string, params: any[] = []): Promise<{ success: boolean; data?: T; error?: string }> { if (this.useInternalMethods) { return this.db.getForMigration<T>(sql, params); } else { return this.db.get<T>(sql, params); } } /** * Run query using appropriate method (internal during initialization, public otherwise) */ private async dbRun(sql: string, params: any[] = []): Promise<{ success: boolean; error?: string }> { if (this.useInternalMethods) { const result = await this.db.runForMigration(sql, params); return { success: result.success, error: result.error }; } else { const result = await this.db.run(sql, params); return { success: result.success, error: result.error }; } } /** * Check if migration is needed */ async checkMigrationStatus(): Promise<MigrationStatus> { try { // Check if legacy data exists const hasLegacyData = await this.hasLegacyData(); // Check if backup exists const backupExists = await this.pathExists(this.backupPath); // Check if migration was already completed const migrationRecord = await this.dbGet<{ value: string }>( 'SELECT value FROM atlas_metadata WHERE key = ?', ['migration_status'] ); const lastMigrationRecord = await this.dbGet<{ value: string }>( 'SELECT value FROM atlas_metadata WHERE key = ?', ['last_migration_timestamp'] ); // Check if database actually has migrated data const hasMigratedData = await this.hasDataInDatabase(); const isRequired = hasLegacyData && (!hasMigratedData || migrationRecord.data?.value !== 'completed'); const lastMigration = lastMigrationRecord.data?.value ? parseInt(lastMigrationRecord.data.value) : undefined; return { needsMigration: isRequired, isRequired, hasLegacyData, backupExists, lastMigration }; } catch (error) { console.error('Failed to check migration status:', error); return { needsMigration: false, isRequired: false, hasLegacyData: false, backupExists: false }; } } /** * Perform the migration */ async migrate(): Promise<MigrationResult> { const startTime = Date.now(); const errors: string[] = []; let migratedItems = 0; let backupPath: string | undefined; try { console.error('🔄 Starting data migration from JSON files to SQLite...'); // Disable foreign key constraints during migration await this.dbRun('PRAGMA foreign_keys = OFF'); console.error('🔧 Disabled foreign key constraints for migration'); // Create backup first backupPath = await this.createBackup(); console.error(`📦 Backup created at: ${backupPath}`); // Migrate only active modules (skip deprecated/removed modules) // Core project data migratedItems += await this.migrateProjectData(errors); // Active 12-factor modules migratedItems += await this.migrateAgileData(errors); migratedItems += await this.migrateKanbanData(errors); migratedItems += await this.migrateDocumentationData(errors); migratedItems += await this.migrateMemoryData(errors); migratedItems += await this.migrateADRData(errors); migratedItems += await this.migrateDevelopmentData(errors); migratedItems += await this.migrateWorkspaceData(errors); migratedItems += await this.migrateProcessAutomationData(errors); migratedItems += await this.migrateProductRequirementsData(errors); migratedItems += await this.migrateDataManagementData(errors); migratedItems += await this.migrateRAGData(errors); // Skip deprecated/removed modules: console.error('⚠️ Skipping deprecated modules: approval-wrapper, deployment-management, deprecation, interaction-framework, memory, repo-setup, smart-estimation, workflow-recipes'); console.error('⚠️ Skipping inactive modules: business-guidance, code-analysis, error-analysis, issue-tracking, product-roadmap, testing-framework, performance-monitoring, security'); // Re-enable foreign key constraints await this.dbRun('PRAGMA foreign_keys = ON'); console.log('🔧 Re-enabled foreign key constraints'); // Update migration status await this.updateMigrationStatus('completed'); const duration = Date.now() - startTime; console.log(`✅ Migration completed in ${duration}ms. Migrated ${migratedItems} items.`); return { success: true, migratedItems, errors, duration, backupPath }; } catch (error) { // Re-enable foreign key constraints even on error try { await this.dbRun('PRAGMA foreign_keys = ON'); console.log('🔧 Re-enabled foreign key constraints after error'); } catch (pragmaError) { console.warn('⚠️ Failed to re-enable foreign key constraints:', pragmaError); } const duration = Date.now() - startTime; const errorMessage = error instanceof Error ? error.message : 'Unknown error'; errors.push(`Migration failed: ${errorMessage}`); console.error('❌ Migration failed:', error); return { success: false, migratedItems, errors, duration, backupPath }; } } /** * Create backup of existing data */ private async createBackup(): Promise<string> { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupDir = path.join(this.backupPath, `backup-${timestamp}`); await fs.mkdir(backupDir, { recursive: true }); // Copy all JSON files to backup const files = await this.findJsonFiles(this.legacyDataPath); for (const file of files) { const relativePath = path.relative(this.legacyDataPath, file); const backupFile = path.join(backupDir, relativePath); await fs.mkdir(path.dirname(backupFile), { recursive: true }); await fs.copyFile(file, backupFile); } return backupDir; } /** * Migrate agile management data */ private async migrateAgileData(errors: string[]): Promise<number> { let count = 0; try { // Ensure default project exists to avoid foreign key constraints await this.ensureDefaultProject(); console.log('✅ Default project ensured for agile data'); // Migrate sprints const sprintsFile = path.join(this.legacyDataPath, 'agile', 'sprints.json'); if (await this.pathExists(sprintsFile)) { const sprints = await this.readJsonFile(sprintsFile, errors); if (Array.isArray(sprints)) { for (const sprint of sprints) { await this.migrateSprint(sprint); count++; } } } // Migrate stories const storiesFile = path.join(this.legacyDataPath, 'agile', 'stories.json'); if (await this.pathExists(storiesFile)) { const stories = await this.readJsonFile(storiesFile, errors); if (Array.isArray(stories)) { for (const story of stories) { await this.migrateStory(story); count++; } } } // Migrate epics const epicsFile = path.join(this.legacyDataPath, 'agile', 'epics.json'); if (await this.pathExists(epicsFile)) { const epics = await this.readJsonFile(epicsFile, errors); if (Array.isArray(epics)) { for (const epic of epics) { await this.migrateEpic(epic); count++; } } } } catch (error) { errors.push(`Agile data migration error: ${error instanceof Error ? error.message : 'Unknown error'}`); } return count; } /** * Migrate kanban data */ private async migrateKanbanData(errors: string[]): Promise<number> { let count = 0; try { const boardsFile = path.join(this.legacyDataPath, 'kanban', 'boards.json'); if (await this.pathExists(boardsFile)) { const boards = await this.readJsonFile(boardsFile); if (Array.isArray(boards)) { for (const board of boards) { await this.migrateKanbanBoard(board); count++; } } } } catch (error) { errors.push(`Kanban data migration error: ${error instanceof Error ? error.message : 'Unknown error'}`); } return count; } /** * Migrate documentation data */ private async migrateDocumentationData(errors: string[]): Promise<number> { let count = 0; try { const docsFile = path.join(this.legacyDataPath, 'documentation', 'documents.json'); if (await this.pathExists(docsFile)) { const docs = await this.readJsonFile(docsFile); if (Array.isArray(docs)) { for (const doc of docs) { await this.migrateDocument(doc); count++; } } } } catch (error) { errors.push(`Documentation migration error: ${error instanceof Error ? error.message : 'Unknown error'}`); } return count; } /** * Migrate business guidance data */ private async migrateBusinessData(errors: string[]): Promise<number> { let count = 0; try { // Migrate business plans const businessFile = path.join(this.legacyDataPath, 'business', 'plans.json'); if (await this.pathExists(businessFile)) { const plans = await this.readJsonFile(businessFile); if (Array.isArray(plans)) { for (const plan of plans) { await this.migrateBusinessPlan(plan); count++; } } } // Migrate market analyses const marketAnalysesFile = path.join(this.legacyDataPath, 'business', 'market-analyses.json'); if (await this.pathExists(marketAnalysesFile)) { const analyses = await this.readJsonFile(marketAnalysesFile); if (Array.isArray(analyses)) { for (const analysis of analyses) { await this.migrateMarketAnalysis(analysis); count++; } } } // Migrate competitor analyses const competitorFile = path.join(this.legacyDataPath, 'business', 'competitor-analyses.json'); if (await this.pathExists(competitorFile)) { const analyses = await this.readJsonFile(competitorFile); if (Array.isArray(analyses)) { for (const analysis of analyses) { await this.migrateCompetitorAnalysis(analysis); count++; } } } // Migrate financial projections const financialFile = path.join(this.legacyDataPath, 'business', 'financial-projections.json'); if (await this.pathExists(financialFile)) { const projections = await this.readJsonFile(financialFile); if (Array.isArray(projections)) { for (const projection of projections) { await this.migrateFinancialProjection(projection); count++; } } } // Migrate startup assessments const assessmentFile = path.join(this.legacyDataPath, 'business', 'startup-assessments.json'); if (await this.pathExists(assessmentFile)) { const assessments = await this.readJsonFile(assessmentFile); if (Array.isArray(assessments)) { for (const assessment of assessments) { await this.migrateStartupAssessment(assessment); count++; } } } // Migrate pitch decks const pitchDeckFile = path.join(this.legacyDataPath, 'business', 'pitch-decks.json'); if (await this.pathExists(pitchDeckFile)) { const decks = await this.readJsonFile(pitchDeckFile); if (Array.isArray(decks)) { for (const deck of decks) { await this.migratePitchDeck(deck); count++; } } } // Migrate startup metrics const metricsFile = path.join(this.legacyDataPath, 'business', 'startup-metrics.json'); if (await this.pathExists(metricsFile)) { const metrics = await this.readJsonFile(metricsFile); if (Array.isArray(metrics)) { for (const metric of metrics) { await this.migrateStartupMetrics(metric); count++; } } } // Migrate business reviews const reviewFile = path.join(this.legacyDataPath, 'business', 'business-reviews.json'); if (await this.pathExists(reviewFile)) { const reviews = await this.readJsonFile(reviewFile); if (Array.isArray(reviews)) { for (const review of reviews) { await this.migrateBusinessReview(review); count++; } } } } catch (error) { errors.push(`Business data migration error: ${error instanceof Error ? error.message : 'Unknown error'}`); } return count; } /** * Migrate memory data */ private async migrateMemoryData(errors: string[]): Promise<number> { let count = 0; try { const memoryFile = path.join(this.legacyDataPath, 'memory', 'memories.json'); if (await this.pathExists(memoryFile)) { const memories = await this.readJsonFile(memoryFile); if (Array.isArray(memories)) { for (const memory of memories) { await this.migrateMemory(memory); count++; } } } } catch (error) { errors.push(`Memory data migration error: ${error instanceof Error ? error.message : 'Unknown error'}`); } return count; } /** * Migrate individual sprint */ private async migrateSprint(sprint: any): Promise<void> { const id = sprint.id || randomUUID(); const projectId = sprint.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO agile_sprints (id, project_id, name, goal, status, start_date, end_date, duration, team, story_points_planned, story_points_completed, stories_total, stories_completed, velocity, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, sprint.name || 'Unnamed Sprint', sprint.goal || null, sprint.status || 'planning', sprint.startDate ? new Date(sprint.startDate).getTime() : null, sprint.endDate ? new Date(sprint.endDate).getTime() : null, sprint.duration || 14, JSON.stringify(sprint.team || []), sprint.storyPointsPlanned || 0, sprint.storyPointsCompleted || 0, sprint.storiesTotal || 0, sprint.storiesCompleted || 0, sprint.velocity || 0, sprint.createdAt ? new Date(sprint.createdAt).getTime() : now, sprint.updatedAt ? new Date(sprint.updatedAt).getTime() : now ] ); } /** * Migrate individual story */ private async migrateStory(story: any): Promise<void> { const id = story.id || randomUUID(); const projectId = story.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO agile_stories (id, project_id, sprint_id, epic_id, title, description, status, story_points, priority, assignee, tags, acceptance_criteria, design_document_url, implementation_document_url, documentation_status, documentation_last_reviewed, documentation_reviewers, groomed_with_user_feedback, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, story.sprintId || null, story.epicId || story.epic || null, story.title || 'Untitled Story', story.description || null, story.status || 'todo', story.storyPoints || 0, story.priority || 'medium', story.assignee || null, JSON.stringify(story.tags || []), JSON.stringify(story.acceptanceCriteria || []), story.designDocumentUrl || null, story.implementationDocumentUrl || null, story.documentationStatus || 'pending', story.documentationLastReviewed ? new Date(story.documentationLastReviewed).getTime() : null, JSON.stringify(story.documentationReviewers || []), story.groomedWithUserFeedback || false, story.createdAt ? new Date(story.createdAt).getTime() : now, story.updatedAt ? new Date(story.updatedAt).getTime() : now ] ); } /** * Migrate individual epic */ private async migrateEpic(epic: any): Promise<void> { const id = epic.id || randomUUID(); const projectId = epic.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO agile_epics (id, project_id, title, description, status, story_points, priority, target_quarter, business_value, success_criteria, dependencies, risks, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, epic.title || 'Untitled Epic', epic.description || null, epic.status || 'planning', epic.storyPoints || 0, epic.priority || 'medium', epic.targetQuarter || null, epic.businessValue || null, JSON.stringify(epic.successCriteria || []), JSON.stringify(epic.dependencies || []), JSON.stringify(epic.risks || []), epic.createdAt ? new Date(epic.createdAt).getTime() : now, epic.updatedAt ? new Date(epic.updatedAt).getTime() : now ] ); } /** * Migrate kanban board */ private async migrateKanbanBoard(board: any): Promise<void> { const id = board.id || randomUUID(); const projectId = board.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO kanban_boards (id, project_id, name, description, columns, cards, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, board.name || 'Untitled Board', board.description || null, JSON.stringify(board.columns || []), JSON.stringify(board.cards || []), board.createdAt ? new Date(board.createdAt).getTime() : now, board.updatedAt ? new Date(board.updatedAt).getTime() : now ] ); } /** * Migrate document */ private async migrateDocument(doc: any): Promise<void> { const id = doc.id || randomUUID(); const projectId = doc.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO documents (id, project_id, title, content, type, path, tags, status, author, version, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, doc.title || 'Untitled Document', doc.content || '', doc.type || 'markdown', doc.path || null, JSON.stringify(doc.tags || []), doc.status || 'draft', doc.author || null, doc.version || 1, doc.createdAt ? new Date(doc.createdAt).getTime() : now, doc.updatedAt ? new Date(doc.updatedAt).getTime() : now ] ); } /** * Migrate business plan */ private async migrateBusinessPlan(plan: any): Promise<void> { const id = plan.id || randomUUID(); const projectId = plan.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO business_plans (id, project_id, plan_type, content, metadata, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, plan.planType || 'general', JSON.stringify(plan.content || {}), JSON.stringify(plan.metadata || {}), plan.status || 'draft', plan.createdAt ? new Date(plan.createdAt).getTime() : now, plan.updatedAt ? new Date(plan.updatedAt).getTime() : now ] ); } /** * Migrate market analysis */ private async migrateMarketAnalysis(analysis: any): Promise<void> { const id = analysis.id || randomUUID(); const projectId = analysis.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO market_analyses (id, project_id, industry, target_market, geographic_scope, market_size, growth_rate, trends, opportunities, challenges, recommendations, competitors, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, analysis.industry || '', analysis.targetMarket || '', analysis.geographicScope || 'global', analysis.marketSize || null, analysis.growthRate || null, JSON.stringify(analysis.trends || []), JSON.stringify(analysis.opportunities || []), JSON.stringify(analysis.challenges || []), JSON.stringify(analysis.recommendations || []), JSON.stringify(analysis.competitors || []), analysis.createdAt ? new Date(analysis.createdAt).getTime() : now, analysis.updatedAt ? new Date(analysis.updatedAt).getTime() : now ] ); } /** * Migrate competitor analysis */ private async migrateCompetitorAnalysis(analysis: any): Promise<void> { const id = analysis.id || randomUUID(); const projectId = analysis.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO competitor_analyses (id, project_id, industry, business_idea, competition_level, competitors, gaps, opportunities, recommendations, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, analysis.industry || '', analysis.businessIdea || '', analysis.competitionLevel || null, JSON.stringify(analysis.competitors || []), JSON.stringify(analysis.gaps || []), JSON.stringify(analysis.opportunities || []), JSON.stringify(analysis.recommendations || []), analysis.createdAt ? new Date(analysis.createdAt).getTime() : now, analysis.updatedAt ? new Date(analysis.updatedAt).getTime() : now ] ); } /** * Migrate financial projection */ private async migrateFinancialProjection(projection: any): Promise<void> { const id = projection.id || randomUUID(); const projectId = projection.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO financial_projections (id, project_id, business_model, timeline, currency, revenue, expenses, net_income, cash_flow, break_even_month, total_investment_needed, roi, scenarios, assumptions, recommendations, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, projection.businessModel || '', projection.timeline || 36, projection.currency || 'USD', JSON.stringify(projection.revenue || {}), JSON.stringify(projection.expenses || {}), JSON.stringify(projection.netIncome || {}), JSON.stringify(projection.cashFlow || {}), projection.breakEvenMonth || null, projection.totalInvestmentNeeded || null, projection.roi || null, JSON.stringify(projection.scenarios || null), JSON.stringify(projection.assumptions || []), JSON.stringify(projection.recommendations || []), projection.createdAt ? new Date(projection.createdAt).getTime() : now, projection.updatedAt ? new Date(projection.updatedAt).getTime() : now ] ); } /** * Migrate startup assessment */ private async migrateStartupAssessment(assessment: any): Promise<void> { const id = assessment.id || randomUUID(); const projectId = assessment.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO startup_assessments (id, project_id, stage, score, categories, strengths, weaknesses, next_steps, stage_recommendations, overall, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, assessment.stage || 'idea', assessment.score || 0, JSON.stringify(assessment.categories || []), JSON.stringify(assessment.strengths || []), JSON.stringify(assessment.weaknesses || []), JSON.stringify(assessment.nextSteps || []), JSON.stringify(assessment.stageRecommendations || []), assessment.overall || null, assessment.createdAt ? new Date(assessment.createdAt).getTime() : now, assessment.updatedAt ? new Date(assessment.updatedAt).getTime() : now ] ); } /** * Migrate pitch deck */ private async migratePitchDeck(deck: any): Promise<void> { const id = deck.id || randomUUID(); const projectId = deck.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO pitch_decks (id, project_id, business_idea, template, slides, presentation_tips, key_messages, markdown, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, deck.businessIdea || '', deck.template || 'standard', JSON.stringify(deck.slides || []), JSON.stringify(deck.presentationTips || []), JSON.stringify(deck.keyMessages || []), deck.markdown || '', deck.createdAt ? new Date(deck.createdAt).getTime() : now, deck.updatedAt ? new Date(deck.updatedAt).getTime() : now ] ); } /** * Migrate startup metrics */ private async migrateStartupMetrics(metrics: any): Promise<void> { const id = metrics.id || randomUUID(); const projectId = metrics.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO startup_metrics (id, project_id, metrics_type, current_metrics, goals, timeframe, trends, benchmarks, recommendations, health_score, alerts, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, metrics.metricsType || 'saas', JSON.stringify(metrics.currentMetrics || {}), JSON.stringify(metrics.goals || {}), metrics.timeframe || 'monthly', JSON.stringify(metrics.trends || []), JSON.stringify(metrics.benchmarks || []), JSON.stringify(metrics.recommendations || []), metrics.healthScore || null, JSON.stringify(metrics.alerts || []), metrics.createdAt ? new Date(metrics.createdAt).getTime() : now, metrics.updatedAt ? new Date(metrics.updatedAt).getTime() : now ] ); } /** * Migrate business review */ private async migrateBusinessReview(review: any): Promise<void> { const id = review.id || randomUUID(); const projectId = review.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO business_reviews (id, project_id, business_name, current_stage, overall_health_score, strengths, gaps, strategic_paths, immediate_actions, data_collected, review_date, next_review_date, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, review.businessName || '', review.currentStage || 'idea', review.overallHealthScore || 0, JSON.stringify(review.strengths || []), JSON.stringify(review.gaps || []), JSON.stringify(review.strategicPaths || []), JSON.stringify(review.immediateActions || []), JSON.stringify(review.dataCollected || {}), review.reviewDate || new Date().toISOString(), review.nextReviewDate || new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString(), review.createdAt ? new Date(review.createdAt).getTime() : now, review.updatedAt ? new Date(review.updatedAt).getTime() : now ] ); } /** * Migrate memory */ private async migrateMemory(memory: any): Promise<void> { const id = memory.id || randomUUID(); const projectId = memory.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO memories (id, project_id, title, type, content, tags, metadata, importance, category, source, created_by, embedding_vector, similarity_score, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, memory.title || null, memory.type || 'general', memory.content || '', JSON.stringify(memory.tags || []), JSON.stringify(memory.metadata || {}), memory.importance || 'medium', memory.category || null, memory.source || null, memory.createdBy || null, memory.embeddingVector || null, memory.similarityScore || null, memory.createdAt ? new Date(memory.createdAt).getTime() : now, memory.updatedAt ? new Date(memory.updatedAt).getTime() : now ] ); } /** * Update migration status */ private async updateMigrationStatus(status: string): Promise<void> { const now = Date.now(); await this.dbRun( 'INSERT OR REPLACE INTO atlas_metadata (key, value, updated_at) VALUES (?, ?, ?)', ['migration_status', status, now] ); await this.dbRun( 'INSERT OR REPLACE INTO atlas_metadata (key, value, updated_at) VALUES (?, ?, ?)', ['last_migration_timestamp', now.toString(), now] ); } /** * Migrate ADR data */ private async migrateADRData(errors: string[]): Promise<number> { let count = 0; try { // Look for ADR files in various possible locations const adrPaths = [ path.join(this.legacyDataPath, 'adr'), path.join(this.legacyDataPath, 'adr-management'), path.join(this.legacyDataPath, 'adrs') ]; for (const adrPath of adrPaths) { if (await this.pathExists(adrPath)) { // Check for index file const indexFile = path.join(adrPath, 'index.json'); if (await this.pathExists(indexFile)) { const index = await this.readJsonFile(indexFile); if (index && index.adrs && Array.isArray(index.adrs)) { for (const adrSummary of index.adrs) { // Read the full ADR file const adrFile = path.join(adrPath, `${adrSummary.id}.json`); if (await this.pathExists(adrFile)) { const adr = await this.readJsonFile(adrFile); if (adr) { await this.migrateADR(adr); count++; } } } } } // Also check for individual ADR files without index const adrFiles = await this.findJsonFiles(adrPath); for (const adrFile of adrFiles) { if (!adrFile.endsWith('index.json')) { const adr = await this.readJsonFile(adrFile); if (adr && adr.id && adr.id.startsWith('ADR-')) { await this.migrateADR(adr); count++; } } } } } } catch (error) { errors.push(`ADR data migration error: ${error instanceof Error ? error.message : 'Unknown error'}`); } return count; } /** * Migrate individual ADR */ private async migrateADR(adr: any): Promise<void> { const id = adr.id || `ADR-${Date.now().toString().slice(-4)}`; const projectId = adr.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO adr_records (id, project_id, title, status, date, deciders, template, context, decision, consequences, tags, decision_drivers, considered_options, pros_and_cons, supersedes, superseded_by, related_to, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, adr.title || 'Untitled ADR', adr.status || 'proposed', adr.date ? new Date(adr.date).getTime() : now, JSON.stringify(adr.deciders || []), adr.template || 'nygard', adr.context || '', adr.decision || '', adr.consequences || '', JSON.stringify(adr.tags || []), JSON.stringify(adr.decisionDrivers || []), JSON.stringify(adr.consideredOptions || []), JSON.stringify(adr.prosAndCons || []), JSON.stringify(adr.supersedes || []), adr.supersededBy || null, JSON.stringify(adr.relatedTo || []), adr.createdAt ? new Date(adr.createdAt).getTime() : now, adr.updatedAt ? new Date(adr.updatedAt).getTime() : now ] ); // Migrate status history if available if (adr.statusHistory && Array.isArray(adr.statusHistory)) { for (const historyEntry of adr.statusHistory) { await this.dbRun( `INSERT OR REPLACE INTO adr_status_history (id, adr_id, project_id, from_status, to_status, changed_at, changed_by, reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), id, projectId, historyEntry.from || null, historyEntry.to || 'proposed', historyEntry.date ? new Date(historyEntry.date).getTime() : now, historyEntry.changedBy || 'unknown', historyEntry.reason || null ] ); } } } /** * Migrate development module data */ private async migrateDevelopmentData(errors: string[]): Promise<number> { let count = 0; try { const devFile = path.join(this.legacyDataPath, 'development', 'development.json'); if (await this.pathExists(devFile)) { const devData = await this.readJsonFile(devFile); if (devData) { // Migrate features if (devData.features) { for (const featureId in devData.features) { const feature = devData.features[featureId]; await this.migrateDevelopmentFeature(feature); count++; } } // Migrate TDD sessions if (devData.sessions) { for (const sessionId in devData.sessions) { const session = devData.sessions[sessionId]; await this.migrateTDDSession(session); count++; } } // Migrate TDD config if (devData.config) { await this.migrateTDDConfig(devData.config); count++; } } } } catch (error) { errors.push(`Development data migration error: ${error instanceof Error ? error.message : 'Unknown error'}`); } return count; } /** * Migrate individual development feature */ private async migrateDevelopmentFeature(feature: any): Promise<void> { const id = feature.id || randomUUID(); const projectId = feature.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO development_features (id, project_id, name, description, status, test_coverage, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, feature.name || 'Untitled Feature', feature.description || '', feature.status || 'planning', feature.testCoverage || 0.0, feature.createdAt ? new Date(feature.createdAt).getTime() : now, feature.updatedAt ? new Date(feature.updatedAt).getTime() : now ] ); // Migrate test cases if (feature.testCases && Array.isArray(feature.testCases)) { for (const testCase of feature.testCases) { await this.dbRun( `INSERT OR REPLACE INTO development_test_cases (id, feature_id, project_id, name, description, type, status, expected_behavior, actual_behavior, error_message, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ testCase.id || randomUUID(), id, projectId, testCase.name || 'Untitled Test', testCase.description || '', testCase.type || 'unit', testCase.status || 'pending', testCase.expectedBehavior || '', testCase.actualBehavior || null, testCase.errorMessage || null, testCase.createdAt ? new Date(testCase.createdAt).getTime() : now, testCase.updatedAt ? new Date(testCase.updatedAt).getTime() : now ] ); } } } /** * Migrate TDD session */ private async migrateTDDSession(session: any): Promise<void> { const id = session.id || randomUUID(); const projectId = session.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO development_tdd_sessions (id, feature_id, project_id, current_phase, started_at, completed_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ id, session.featureId || 'unknown', projectId, session.currentPhase || 'red', session.startedAt ? new Date(session.startedAt).getTime() : now, session.completedAt ? new Date(session.completedAt).getTime() : null, session.createdAt ? new Date(session.createdAt).getTime() : now, session.updatedAt ? new Date(session.updatedAt).getTime() : now ] ); // Migrate phase history if (session.phaseHistory && Array.isArray(session.phaseHistory)) { for (const phase of session.phaseHistory) { await this.dbRun( `INSERT OR REPLACE INTO development_tdd_phase_history (id, session_id, project_id, phase, started_at, completed_at, notes) VALUES (?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), id, projectId, phase.phase || 'red', phase.startedAt ? new Date(phase.startedAt).getTime() : now, phase.completedAt ? new Date(phase.completedAt).getTime() : null, phase.notes || null ] ); } } } /** * Migrate TDD configuration */ private async migrateTDDConfig(config: any): Promise<void> { const projectId = config.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO development_tdd_config (project_id, enforce_test_first, minimum_test_coverage, require_tests_before_implementation, auto_generate_test_templates, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, [ projectId, config.enforceTestFirst !== false, config.minimumTestCoverage || 80.0, config.requireTestsBeforeImplementation !== false, config.autoGenerateTestTemplates !== false, now, now ] ); } /** * Migrate workspace module data */ private async migrateWorkspaceData(errors: string[]): Promise<number> { let count = 0; try { const workspaceFile = path.join(this.legacyDataPath, 'workspace', 'workspace.json'); if (await this.pathExists(workspaceFile)) { const workspaceData = await this.readJsonFile(workspaceFile); if (workspaceData) { // Migrate workspaces if (workspaceData.workspaces) { for (const workspaceId in workspaceData.workspaces) { const workspace = workspaceData.workspaces[workspaceId]; await this.migrateWorkspace(workspace, workspaceData.activeWorkspaceId === workspaceId); count++; } } } } } catch (error) { errors.push(`Workspace data migration error: ${error instanceof Error ? error.message : 'Unknown error'}`); } return count; } /** * Migrate individual workspace */ private async migrateWorkspace(workspace: any, isActive: boolean): Promise<void> { const id = workspace.id || `ws-${randomUUID()}`; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO workspaces (id, name, description, root_path, active, settings, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ id, workspace.name || 'Untitled Workspace', workspace.description || '', workspace.rootPath || process.cwd(), isActive, JSON.stringify(workspace.settings || {}), workspace.createdAt ? new Date(workspace.createdAt).getTime() : now, workspace.updatedAt ? new Date(workspace.updatedAt).getTime() : now ] ); // Migrate repositories if (workspace.repositories && Array.isArray(workspace.repositories)) { for (const repo of workspace.repositories) { await this.dbRun( `INSERT OR REPLACE INTO workspace_repositories (id, workspace_id, name, path, type, is_primary, remote_url, current_branch, last_sync, dependencies, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ repo.id || `repo-${randomUUID()}`, id, repo.name || 'Untitled Repository', repo.path || '', repo.type || 'local', repo.primary || false, repo.remote || null, repo.branch || null, repo.lastSync ? new Date(repo.lastSync).getTime() : null, JSON.stringify(repo.dependencies || []), repo.createdAt ? new Date(repo.createdAt).getTime() : now, repo.updatedAt ? new Date(repo.updatedAt).getTime() : now ] ); } } } /** * Migrate process automation module data */ private async migrateProcessAutomationData(errors: string[]): Promise<number> { let count = 0; try { const processesPath = path.join(this.legacyDataPath, 'process-automation', 'processes'); const executionsPath = path.join(this.legacyDataPath, 'process-automation', 'executions'); const templatesPath = path.join(this.legacyDataPath, 'process-automation', 'templates'); // Migrate processes if (await this.pathExists(processesPath)) { const processFiles = await this.findJsonFiles(processesPath); for (const processFile of processFiles) { const process = await this.readJsonFile(processFile); if (process) { await this.migrateProcess(process); count++; } } } // Migrate executions if (await this.pathExists(executionsPath)) { const processDirs = await fs.readdir(executionsPath, { withFileTypes: true }); for (const processDir of processDirs) { if (processDir.isDirectory()) { const executionFiles = await this.findJsonFiles(path.join(executionsPath, processDir.name)); for (const executionFile of executionFiles) { const execution = await this.readJsonFile(executionFile); if (execution) { await this.migrateProcessExecution(execution); count++; } } } } } // Migrate templates if (await this.pathExists(templatesPath)) { const templateFiles = await this.findJsonFiles(templatesPath); for (const templateFile of templateFiles) { const template = await this.readJsonFile(templateFile); if (template) { await this.migrateProcessTemplate(template); count++; } } } } catch (error) { errors.push(`Process automation data migration error: ${error instanceof Error ? error.message : 'Unknown error'}`); } return count; } /** * Migrate individual process */ private async migrateProcess(process: any): Promise<void> { const id = process.id || randomUUID(); const projectId = process.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO process_definitions (id, project_id, name, description, version, persona, category, tags, variables, on_success, on_failure, is_active, created_by, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, projectId, process.name || 'Untitled Process', process.description || '', process.version || '1.0.0', process.persona || null, process.metadata?.category || null, JSON.stringify(process.metadata?.tags || []), JSON.stringify(process.variables || {}), null, // on_success - not in legacy format null, // on_failure - not in legacy format process.metadata?.isActive !== false, process.metadata?.createdBy || 'system', process.metadata?.createdAt ? new Date(process.metadata.createdAt).getTime() : now, now ] ); // Migrate activities if (process.activities && Array.isArray(process.activities)) { for (let i = 0; i < process.activities.length; i++) { const activity = process.activities[i]; await this.dbRun( `INSERT OR REPLACE INTO process_activities (id, process_id, activity_order, activity_id, type, name, description, config, retry_policy, timeout, required, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), id, i, activity.id || randomUUID(), activity.type || 'tool', activity.name || 'Untitled Activity', activity.description || '', JSON.stringify(activity.config || {}), activity.retryPolicy ? JSON.stringify(activity.retryPolicy) : null, activity.timeout || null, activity.required !== false, now ] ); } } // Migrate triggers if (process.triggers && Array.isArray(process.triggers)) { for (const trigger of process.triggers) { await this.dbRun( `INSERT OR REPLACE INTO process_triggers (id, process_id, trigger_id, type, name, enabled, config, ai_suggested, reasoning, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), id, trigger.id || randomUUID(), trigger.type || 'manual', trigger.name || 'Untitled Trigger', trigger.enabled !== false, JSON.stringify(trigger.config || {}), trigger.aiSuggested || false, trigger.reasoning || null, now, now ] ); } } } /** * Migrate individual process execution */ private async migrateProcessExecution(execution: any): Promise<void> { const id = execution.id || randomUUID(); const projectId = execution.projectId || 'default-project'; const now = Date.now(); await this.dbRun( `INSERT OR REPLACE INTO process_executions (id, process_id, project_id, process_version, status, triggered_by, trigger_type, variables, error_message, error_code, started_at, completed_at, duration, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, execution.processId || 'unknown', projectId, execution.processVersion || '1.0.0', execution.status || 'pending', execution.triggeredBy || 'system', execution.triggerType || 'manual', JSON.stringify(execution.variables || {}), execution.error?.message || null, execution.error?.code || null, execution.startedAt ? new Date(execution.startedAt).getTime() : now, execution.completedAt ? new Date(execution.completedAt).getTime() : null, execution.duration || null, now ] ); // Migrate activity results if (execution.activityResults) { for (const [activityId, result] of Object.entries(execution.activityResults as any)) { const typedResult = result as any; await this.dbRun( `INSERT OR REPLACE INTO process_activity_results (id, execution_id, ac