#!/usr/bin/env node /** * migrate-skills-storage.js * * Phase 1 迁移脚本:将非核心 skills 从 repo/skills/ 移到 data/skills/ * * 用法: * node server/scripts/migrate-skills-storage.js [--dry-run] * node server/scripts/migrate-skills-storage.js --execute * * 默认 dry-run 模式,只输出计划,不做实际移动。 */ const path = require('path'); const fs = require('fs'); // 加载配置 require('dotenv').config({ path: path.join(__dirname, '../../.env') }); const config = require('../config'); const { CORE_SKILLS } = require('../services/skill-dir-manager'); const skillsCodeDir = config.skillsCodeDir; const skillsDataDir = config.skillsDataDir; const dryRun = !process.argv.includes('--execute'); console.log('═══════════════════════════════════════════'); console.log(' Phase 1: Skill Storage Migration'); console.log('═══════════════════════════════════════════'); console.log(` Code dir: ${skillsCodeDir}`); console.log(` Data dir: ${skillsDataDir}`); console.log(` Mode: ${dryRun ? '🔍 DRY RUN' : '⚡ EXECUTE'}`); console.log(` Core skills: ${CORE_SKILLS.size}`); console.log(''); // 扫描 skills/ 目录 if (!fs.existsSync(skillsCodeDir)) { console.log('❌ skillsCodeDir not found:', skillsCodeDir); process.exit(1); } const entries = fs.readdirSync(skillsCodeDir, { withFileTypes: true }) .filter(e => e.isDirectory() && !e.name.startsWith('.')); const toMove = []; const toKeep = []; for (const entry of entries) { if (CORE_SKILLS.has(entry.name)) { toKeep.push(entry.name); } else { toMove.push(entry.name); } } console.log(`📦 Total skills in repo: ${entries.length}`); console.log(`✅ Keep in repo (core): ${toKeep.length}`); console.log(`📤 Move to data/: ${toMove.length}`); console.log(''); // 显示分类 console.log('── Keep in repo ──'); for (const name of toKeep.sort()) { console.log(` ✅ ${name}`); } console.log(''); console.log('── Move to data/skills/ ──'); for (const name of toMove.sort()) { const src = path.join(skillsCodeDir, name); const dst = path.join(skillsDataDir, name); const exists = fs.existsSync(dst); console.log(` 📤 ${name}${exists ? ' (⚠️ already exists in data/)' : ''}`); } console.log(''); if (dryRun) { console.log('🔍 Dry run complete. Use --execute to perform the migration.'); process.exit(0); } // ── Execute migration ── console.log('⚡ Executing migration...'); // 确保 data/skills/ 存在 if (!fs.existsSync(skillsDataDir)) { fs.mkdirSync(skillsDataDir, { recursive: true }); } let moved = 0; let skipped = 0; let errors = 0; for (const name of toMove) { const src = path.join(skillsCodeDir, name); const dst = path.join(skillsDataDir, name); if (fs.existsSync(dst)) { console.log(` ⏭️ ${name} — already in data/, skipped`); skipped++; continue; } try { fs.renameSync(src, dst); console.log(` ✅ ${name} → data/skills/${name}`); moved++; } catch (err) { // 跨设备可能需要 copy + delete try { copyDirSync(src, dst); fs.rmSync(src, { recursive: true, force: true }); console.log(` ✅ ${name} → data/skills/${name} (copied)`); moved++; } catch (err2) { console.error(` ❌ ${name} — ${err2.message}`); errors++; } } } console.log(''); console.log('── Summary ──'); console.log(` Moved: ${moved}`); console.log(` Skipped: ${skipped}`); console.log(` Errors: ${errors}`); console.log(''); // 更新 SQLite directory 字段 if (moved > 0) { console.log('📊 Updating SQLite directory fields...'); try { const skillDb = require('../services/skill-db'); const skills = skillDb.listSkills(); let updated = 0; for (const skill of skills) { const newDir = path.join( CORE_SKILLS.has(skill.skillName) ? skillsCodeDir : skillsDataDir, skill.skillName ); if (skill.directory !== newDir && fs.existsSync(newDir)) { skillDb.updateSkill(skill.skillName, { directory: newDir }); updated++; } } console.log(` Updated ${updated} directory entries in SQLite`); } catch (err) { console.error(' ⚠️ SQLite update failed:', err.message); } } console.log(''); console.log('✅ Migration complete!'); // ── Helper ── function copyDirSync(src, dst) { fs.mkdirSync(dst, { recursive: true }); for (const entry of fs.readdirSync(src, { withFileTypes: true })) { const s = path.join(src, entry.name); const d = path.join(dst, entry.name); if (entry.isDirectory()) { copyDirSync(s, d); } else { fs.copyFileSync(s, d); } } }