# Migrate

> Imported skill migrate from vercel

- Skill: `bitwikiorg/migrate` (Agent Skill)
- Install (CLI): `npx skillmds@latest add bitwikiorg/migrate`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bitwikiorg/migrate/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: bitwikiorg (https://skillmd.com/u/bitwikiorg)
- Updated: 2026-08-19
- Page: https://skillmd.com/skills/bitwikiorg/migrate

---


#!/usr/bin/env node
/**
 * Migration script to split RPG.md into individual rule files
 * This is a one-time script to help migrate existing content
 */

import { readFile, writeFile, mkdir } from 'fs/promises'
import { join } from 'path'
import { existsSync } from 'fs'
import { SKILL_DIR, RULES_DIR } from './config.js'

const RPG_FILE = join(SKILL_DIR, 'RPG.md')

/**
 * Extract section number and title from heading
 */
function parseSectionHeading(line: string): { number: number; title: string } | null {
  const match = line.match(/^##\s+(\d+)\.\s+(.+)$/)
  if (match) {
    return {
      number: parseInt(match[1]),
      title: match[2].trim()
    }
  }
  return null
}

/**
 * Extract rule number and title from heading
 */
function parseRuleHeading(line: string): { section: number; subsection: number; title: string } | null {
  const match = line.match(/^###\s+(\d+)\.(\d+)\s+(.+)$/)
  if (match) {
    return {
      section: parseInt(match[1]),
      subsection: parseInt(match[2]),
      title: match[3].trim()
    }
  }
  return null
}

/**
 * Extract impact from line
 */
function extractImpact(line: string): { impact: string; description?: string } | null {
  const match = line.match(/\*\*Impact:\s*(\w+(?:-\w+)?)\s*(?:\(([^)]+)\))?/i)
  if (match) {
    return {
      impact: match[1].toUpperCase().replace(/-/g, '-'),
      description: match[2]
    }
  }
  return null
}

async function migrate() {
  try {
    console.log('Migrating RPG.md to individual rule files...')
    
    if (!existsSync(RPG_FILE)) {
      console.error(`RPG.md not found at ${RPG_FILE}`)
      process.exit(1)
    }
    
    // Ensure rules directory exists
    if (!existsSync(RULES_DIR)) {
      await mkdir(RULES_DIR, { recursive: true })
    }
    
    const content = await readFile(RPG_FILE, 'utf-8')
    const lines = content.split('\n')
    
    let currentSection: { number: number; title: string; impact?: string; introduction?: string } | null = null
    let currentRule: { section: number; subsection: number; title: string; content: string[] } | null = null
    let inCodeBlock = false
    
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i]
      
      // Check for section heading
      const sectionInfo = parseSectionHeading(line)
      if (sectionInfo) {
        // Save previous section if exists
        if (currentSection) {
          const sectionFile = join(RULES_DIR, `section-${currentSection.number}.md`)
          let sectionContent = `# ${currentSection.number}. ${currentSection.title}\n\n`
          if (currentSection.impact) {
            sectionContent += `**Impact: ${currentSection.impact}**\n\n`
          }
          if (currentSection.introduction) {
            sectionContent += `## Introduction\n\n${currentSection.introduction}\n`
          }
          await writeFile(sectionFile, sectionContent, 'utf-8')
        }
        
        currentSection = sectionInfo
        currentRule = null
        
        // Look for impact on next few lines
        for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
          const impactInfo = extractImpact(lines[j])
          if (impactInfo) {
            currentSection.impact = impactInfo.impact
            break
          }
        }
        
        // Collect introduction text until first rule
        let introduction: string[] = []
        for (let j = i + 1; j < lines.length; j++) {
          if (parseRuleHeading(lines[j])) {
            break
          }
          if (!lines[j].match(/^###/)) {
            introduction.push(lines[j])
          }
        }
        currentSection.introduction = introduction.join('\n').trim()
        continue
      }
      
      // Check for rule heading
      const ruleInfo = parseRuleHeading(line)
      if (ruleInfo) {
        // Save previous rule if exists
        if (currentRule && currentSection) {
          const ruleFile = join(RULES_DIR, `section-${currentRule.section}-rule-${currentRule.subsection}.md`)
          const ruleContent = currentRule.content.join('\n')
          await writeFile(ruleFile, ruleContent, 'utf-8')
          console.log(`Created ${ruleFile}`)
        }
        
        currentRule = {
          ...ruleInfo,
          content: [line]
        }
        continue
      }
      
      // Accumulate content for current rule
      if (currentRule) {
        currentRule.content.push(line)
      }
    }
    
    // Save last rule
    if (currentRule && currentSection) {
      const ruleFile = join(RULES_DIR, `section-${currentRule.section}-rule-${currentRule.subsection}.md`)
      const ruleContent = currentRule.content.join('\n')
      await writeFile(ruleFile, ruleContent, 'utf-8')
      console.log(`Created ${ruleFile}`)
    }
    
    // Save last section
    if (currentSection) {
      const sectionFile = join(RULES_DIR, `section-${currentSection.number}.md`)
      let sectionContent = `# ${currentSection.number}. ${currentSection.title}\n\n`
      if (currentSection.impact) {
        sectionContent += `**Impact: ${currentSection.impact}**\n\n`
      }
      if (currentSection.introduction) {
        sectionContent += `## Introduction\n\n${currentSection.introduction}\n`
      }
      await writeFile(sectionFile, sectionContent, 'utf-8')
      console.log(`Created ${sectionFile}`)
    }
    
    console.log('\n✓ Migration complete!')
    console.log('Note: You may need to manually add frontmatter to rule files.')
  } catch (error) {
    console.error('Migration failed:', error)
    process.exit(1)
  }
}

migrate()

