migrate-skills-storage.js 4.75 KB
#!/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);
    }
  }
}