#!/usr/bin/env node /** * Automated batch migration of ClaudeFramework docs to task-mcp database * * Migrates: * - docs/investigations/*.md → project_archives (archive_type: investigation) * - docs/*AUDIT*.md → project_archives (archive_type: audit) * - docs/*.md → project_archives (archive_type: research) * - guides/*.md → project_archives (archive_type: research) * - delegation/*.md → project_archives (archive_type: research) */ import { readFileSync, readdirSync, renameSync, mkdirSync, existsSync, statSync } from 'fs'; import { join, basename, dirname } from 'path'; import { homedir } from 'os'; import dotenv from 'dotenv'; import { archiveAdd } from './dist/tools/archives.js'; // Load environment dotenv.config(); // Configuration const CF_DIR = join(homedir(), 'Development/Infrastructure/ClaudeFramework'); const BACKUP_DIR = join(CF_DIR, '.migrated-to-mcp'); // Exclusions const SKIP_FILES = [ 'README.md', 'CHANGELOG.md', 'LICENSE.md', 'CONTRIBUTING.md', 'CLAUDE.md', // Keep active instructions 'planning.md', // Keep active planning 'PRD.md' // Keep product requirements ]; /** * Determine archive type based on file path and name */ function getArchiveType(filePath) { const filename = basename(filePath); const dirName = dirname(filePath); // Investigation files if (dirName.includes('/investigations/')) { return 'investigation'; } // Audit files if (filename.includes('AUDIT') || filename.includes('_AUDIT')) { return 'audit'; } // Research/documentation files return 'research'; } /** * Extract title from markdown content (first H1) */ function extractTitle(content, filename) { const lines = content.split('\n'); for (const line of lines) { if (line.startsWith('# ')) { return line.slice(2).trim().substring(0, 500); } } // Fallback to filename return filename .replace('.md', '') .replace(/_/g, ' ') .replace(/-/g, ' ') .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(' ') .substring(0, 500); } /** * Migrate a single file */ async function migrateFile(filePath) { const filename = basename(filePath); try { const content = readFileSync(filePath, 'utf-8'); const title = extractTitle(content, filename); const fileSize = statSync(filePath).size; const archiveType = getArchiveType(filePath); console.log(`\n[${new Date().toISOString().substring(11, 19)}] Migrating: ${filePath.replace(CF_DIR, '')}`); console.log(` Title: ${title}`); console.log(` Size: ${Math.round(fileSize / 1024)}KB`); console.log(` Type: ${archiveType}`); // Call archive_add const result = await archiveAdd({ project: 'CF', archive_type: archiveType, title, content, original_path: filePath, file_size: fileSize }); console.log(` ✓ Migrated successfully`); // Move to backup const relativePath = filePath.replace(CF_DIR + '/', ''); const backupPath = join(BACKUP_DIR, relativePath); const backupDir = dirname(backupPath); if (!existsSync(backupDir)) { mkdirSync(backupDir, { recursive: true }); } renameSync(filePath, backupPath); console.log(` ✓ Moved to backup`); return { success: true, filename, title, fileSize, archiveType }; } catch (error) { console.error(` ✗ Error: ${error.message}`); return { success: false, filename, error: error.message }; } } /** * Find all markdown files to migrate */ function findMarkdownFiles() { const files = []; const dirs = [ join(CF_DIR, 'docs'), join(CF_DIR, 'guides'), join(CF_DIR, 'delegation') ]; function scanDir(dir) { if (!existsSync(dir)) return; const entries = readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = join(dir, entry.name); if (entry.isDirectory()) { scanDir(fullPath); } else if (entry.isFile() && entry.name.endsWith('.md')) { if (!SKIP_FILES.includes(entry.name)) { files.push(fullPath); } } } } dirs.forEach(scanDir); return files.sort(); } /** * Main migration function */ async function main() { console.log('================================================================================'); console.log('Batch Migration: ClaudeFramework docs → task-mcp database'); console.log('================================================================================\n'); // Find all files const filesToMigrate = findMarkdownFiles(); console.log(`Found ${filesToMigrate.length} markdown files to migrate\n`); if (filesToMigrate.length === 0) { console.log('✓ No files to migrate!'); return; } // Show sample paths console.log('Sample files:'); filesToMigrate.slice(0, 5).forEach(f => { console.log(` - ${f.replace(CF_DIR, '')}`); }); console.log(` ... and ${filesToMigrate.length - 5} more\n`); console.log('Starting migration...\n'); const results = { success: [], failed: [] }; // Migrate each file for (let i = 0; i < filesToMigrate.length; i++) { const filePath = filesToMigrate[i]; const result = await migrateFile(filePath); if (result.success) { results.success.push(result); } else { results.failed.push(result); } // Progress indicator const progress = Math.round(((i + 1) / filesToMigrate.length) * 100); console.log(` Progress: ${i + 1}/${filesToMigrate.length} (${progress}%)`); // Small delay to avoid overwhelming the database await new Promise(resolve => setTimeout(resolve, 50)); } // Summary console.log('\n================================================================================'); console.log('Migration Complete'); console.log('================================================================================\n'); console.log(`✓ Successfully migrated: ${results.success.length} files`); console.log(`✗ Failed: ${results.failed.length} files`); if (results.failed.length > 0) { console.log('\nFailed files:'); results.failed.forEach(f => { console.log(` - ${f.filename}: ${f.error}`); }); } // Group by type const byType = {}; results.success.forEach(r => { byType[r.archiveType] = (byType[r.archiveType] || 0) + 1; }); console.log('\nBy archive type:'); Object.entries(byType).forEach(([type, count]) => { console.log(` - ${type}: ${count} files`); }); console.log(`\nBackup location: ${BACKUP_DIR}`); console.log('Original files can be restored if needed.'); // Calculate total size const totalSize = results.success.reduce((sum, r) => sum + (r.fileSize || 0), 0); console.log(`\nTotal data migrated: ${Math.round(totalSize / 1024 / 1024 * 100) / 100}MB`); } // Run migration main() .then(() => { console.log('\n✓ Migration script completed successfully'); process.exit(0); }) .catch(error => { console.error('\n✗ Migration script failed:', error); console.error(error.stack); process.exit(1); });