Files
gh-duongdev-ccpm/commands/spec:migrate.md
2025-11-29 18:24:24 +08:00

23 KiB

description, allowed-tools, argument-hint
description allowed-tools argument-hint
Migrate existing markdown specs from .claude/ to Linear Documents
LinearMCP
Read
Glob
Bash
AskUserQuestion
<project-path> [category]

Migrate Specs to Linear: $1

🚨 CRITICAL: Safety Rules

READ FIRST: $CCPM_COMMANDS_DIR/SAFETY_RULES.md

NEVER submit, post, or update anything to Jira, Confluence, BitBucket, or Slack without explicit user confirmation, even in bypass permission mode.


Arguments

  • $1 - Project path:
    • . - Use current directory
    • Absolute path (e.g., ~/projects/my-app)
    • Relative path (e.g., ../other-project)
  • $2 - (Optional) Category to migrate: docs, plans, enhancements, tasks, all

Default category: all

Note: Using . will scan current working directory for .claude/ folder.

Workflow

Step 1: Resolve Project Path

// Resolve project path
let projectPath = $1

if (projectPath === '.') {
  // Use current working directory
  projectPath = process.cwd()
}

// Resolve to absolute path
projectPath = path.resolve(projectPath)

// Check if .claude/ exists
const claudePath = path.join(projectPath, '.claude')
if (!fs.existsSync(claudePath)) {
  // Error: No .claude/ directory found
  // Suggest: Check path or create .claude/ first
}

Step 2: Discover Existing Specs

Scan project .claude/ directory for markdown files:

const categories = {
  docs: '.claude/docs/',
  plans: '.claude/plans/',
  enhancements: '.claude/enhancements/',
  tasks: '.claude/tasks/',
  research: '.claude/research/',
  analysis: '.claude/analysis/',
  qa: '.claude/qa/',
  security: '.claude/security/'
}

// Use Glob to find all .md files in each category
const files = {}

for (const [category, path] of Object.entries(categories)) {
  const pattern = `${projectPath}/${path}**/*.md`
  files[category] = await glob(pattern)
}

Filter by category if $2 provided.

Step 2: Categorize Files by Type

Analyze each file to determine type:

function categorizeFile(filePath, content) {
  // Check filename patterns
  const fileName = path.basename(filePath)

  // Plans with checklists → Features
  if (fileName.includes('plan') && hasChecklist(content)) {
    return { type: 'feature', confidence: 'high' }
  }

  // Enhancements → Features
  if (filePath.includes('/enhancements/')) {
    return { type: 'feature', confidence: 'high' }
  }

  // Tasks → Tasks
  if (filePath.includes('/tasks/') && hasImplementationDetails(content)) {
    return { type: 'task', confidence: 'high' }
  }

  // Docs/guides → Documentation (not migrated, or linked)
  if (filePath.includes('/docs/') || fileName.includes('guide')) {
    return { type: 'documentation', confidence: 'high' }
  }

  // Research → Link as reference
  if (filePath.includes('/research/')) {
    return { type: 'reference', confidence: 'high' }
  }

  // Large multi-section files → Epics
  if (hasSections(content) > 5 && hasFeatureBreakdown(content)) {
    return { type: 'epic', confidence: 'medium' }
  }

  // Default: Feature
  return { type: 'feature', confidence: 'low' }
}

function hasChecklist(content) {
  return /- \[ \]/.test(content)
}

function hasImplementationDetails(content) {
  return content.includes('## What Was Implemented') ||
         content.includes('## Implementation') ||
         content.includes('Status:** ✅')
}

function hasFeatureBreakdown(content) {
  return /## Features/i.test(content) ||
         /## Phases/i.test(content)
}

Step 3: Ask What to Migrate

FIRST: Let user select which categories to migrate.

Use AskUserQuestion:

{
  questions: [{
    question: "Which categories would you like to migrate?",
    header: "Select Categories",
    multiSelect: true,  // Allow multiple selections
    options: [
      {
        label: "Epics (2 files)",
        description: `${epics.length} epic specs found in plans/`
      },
      {
        label: "Features (12 files)",
        description: `${features.length} features found in enhancements/`
      },
      {
        label: "Tasks (25 files)",
        description: `${tasks.length} tasks found in tasks/`
      },
      {
        label: "Documentation (6 files)",
        description: `${docs.length} docs/guides found in docs/`
      },
      {
        label: "Research (8 files)",
        description: `${research.length} research docs found in research/`
      }
    ]
  }]
}

User can select:

  • Single category (e.g., only "Features")
  • Multiple categories (e.g., "Epics" + "Features")
  • All categories (select all)

After selection, filter files to only selected categories.

Step 4: Show Migration Preview for Selected Categories

Display categorized files to user:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📦 Migration Preview: $1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📂 Category: ${$2 || 'all'}
📁 Project Path: $1/.claude/

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 Discovered Files
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📚 EPICS (Will create Initiatives + Spec Docs):
  1. production-deployment-plan.md → Epic: "Production Deployment Plan"
  2. [...]

🎨 FEATURES (Will create Features + Design Docs):
  1. 20251031-posthog-observability-implementation.md → Feature: "PostHog Observability"
  2. 20251024-120100-search-and-filter-system.md → Feature: "Search & Filter System"
  3. [...]

✅ TASKS (Will create Tasks):
  1. 20251030-130330-implement-task-comments-phase2.md → Task: "Task Comments Phase 2"
  2. 20251107-095033-fix-posthog-provider-crash.md → Task: "Fix PostHog Provider Crash"
  3. [...]

📖 DOCUMENTATION (Will link as references):
  1. feature-flags-guide.md
  2. ota-updates-guide.md
  3. [...]

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📈 Migration Summary
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Total Files: 45
- Epics: 2
- Features: 12
- Tasks: 25
- Documentation: 6

Estimated Time: ~15 minutes

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️  Migration Rules
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

✅ Original .md files will NOT be deleted (safe migration)
✅ Files will be moved to .claude/migrated/ after successful migration
✅ Linear issues will include link to original file
✅ You can review and edit in Linear after migration

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Step 4: Show Detailed Preview (MANDATORY)

CRITICAL: ALWAYS show full preview before ANY migration.

For EACH file, display:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📄 File #1: production-deployment-plan.md
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📁 Original Path: .claude/plans/production-deployment-plan.md
📊 Type: Epic (detected)
📝 Size: 57KB

Will Create in Linear:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. Epic/Initiative:
   - Title: "Production Deployment Plan"
   - Team: Personal
   - Project: Personal Project
   - Labels: ["epic", "migrated", "spec:draft"]
   - Status: Planned

2. Linear Document:
   - Title: "Epic Spec: Production Deployment Plan"
   - Content: Full markdown (57KB)
   - Linked to Epic above

3. Extracted Metadata:
   - Created: 2025-11-01
   - Status: Planning
   - Version: v1.0.0

4. Post-Migration:
   - Original file moved to: .claude/migrated/plans/
   - Breadcrumb created: .claude/plans/production-deployment-plan.md.migrated.txt

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📄 File #2: 20251031-posthog-observability-implementation.md
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📁 Original Path: .claude/enhancements/20251031-posthog-observability-implementation.md
📊 Type: Feature (detected)
📝 Size: 35KB
📋 Checklist Found: 3 subtasks detected

Will Create in Linear:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. Feature (Parent Issue):
   - Title: "PostHog Observability Implementation"
   - Team: Personal
   - Project: Personal Project
   - Labels: ["feature", "migrated", "spec:draft"]
   - Priority: High (detected)

2. Linear Document:
   - Title: "Feature Design: PostHog Observability Implementation"
   - Content: Full markdown (35KB)
   - Linked to Feature above

3. Sub-Tasks (from checklist):
   - Task 1: "Setup PostHog Integration" (Est: 2h)
   - Task 2: "Configure Event Tracking" (Est: 4h)
   - Task 3: "Add Custom Properties" (Est: 3h)

4. Post-Migration:
   - Original file moved to: .claude/migrated/enhancements/
   - Breadcrumb created with Linear links

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[... show ALL files ...]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Step 5: Confirm Migration (REQUIRED)

NEVER migrate without explicit confirmation.

Use AskUserQuestion:

{
  questions: [{
    question: "⚠️  REVIEW COMPLETE. Proceed with creating these items in Linear?",
    header: "Confirm",
    multiSelect: false,
    options: [
      {
        label: "✅ Yes, Migrate All",
        description: "Create all items in Linear as shown above"
      },
      {
        label: "🔍 Select Specific Files",
        description: "I want to choose which files to migrate"
      },
      {
        label: "❌ Cancel",
        description: "Don't migrate anything"
      }
    ]
  }]
}

If "Select Specific Files":

  • Show numbered list of all files
  • Ask user to specify indices (e.g., "1,3,5-8,12")
  • Show preview AGAIN for selected files only
  • Ask confirmation AGAIN before migrating

Step 5: Migrate Each File

For each file:

Step 5.1: Read and Parse File

const content = await readFile(filePath)

const metadata = extractMetadata(content)
// Extracts:
// - Title (from # heading or filename)
// - Created date (from file or "Created:" field)
// - Status (from "Status:" field)
// - Related docs (from "Related:" or links)

Step 5.2: Transform Content for Linear

Keep most content as-is, but:

  1. Remove file-specific headers:

    # Task Comments Phase 2: Photo Attachments Implementation
    
    **Date:** 2025-10-30 - 2025-10-31
    **Status:** ✅ COMPLETED
    **Related Task:** `.claude/tasks/20251030-130330-implement-task-comments.md`
    

    Becomes Linear issue fields:

    • Title: "Task Comments Phase 2: Photo Attachments"
    • Status: "Done"
    • Description: (rest of content)
  2. Convert local file links to references:

    **Related Task:** `.claude/tasks/20251030-130330-implement-task-comments.md`
    

    Becomes:

    **Related Task:** [Will be migrated as WORK-XXX] or [Original file: `.claude/tasks/...`]
    
  3. Preserve all other content:

    • Code blocks → Keep as-is
    • Tables → Keep as-is
    • Images → Keep as-is (if hosted)
    • Checklists → Keep as-is
    • Headers, formatting → Keep as-is

Step 5.3: Create Linear Entities

For Epic:

// 1. Create Initiative in Linear
const epic = await createLinearInitiative({
  name: metadata.title,
  description: `Original file: \`${relativePath}\`\n\nMigrated from: ${filePath}`,
  team: detectTeam(projectPath),
  targetDate: metadata.targetDate
})

// 2. Create Linear Document for Epic Spec
const doc = await createLinearDocument({
  title: `Epic Spec: ${metadata.title}`,
  content: transformedContent,  // Full markdown content
  projectId: epic.id
})

// 3. Update Initiative description to link to spec doc
await updateLinearInitiative(epic.id, {
  description: `
## 📄 Specification

**Spec Document**: [Epic Spec: ${metadata.title}](${doc.url})

**Original File**: \`${relativePath}\`
**Migrated**: ${new Date().toISOString().split('T')[0]}

---

${epic.description}
  `
})

For Feature:

// 1. Create Feature (Parent Issue)
const feature = await createLinearIssue({
  title: metadata.title,
  team: detectTeam(projectPath),
  project: detectProject(projectPath),
  labels: ['feature', 'migrated', metadata.status ? `status:${metadata.status}` : 'spec:draft'],
  priority: metadata.priority || 0,
  description: `Original file: \`${relativePath}\`\n\nMigrated from: ${filePath}`
})

// 2. Create Linear Document for Feature Design
const doc = await createLinearDocument({
  title: `Feature Design: ${metadata.title}`,
  content: transformedContent,
  projectId: feature.projectId
})

// 3. Link doc to feature
await updateLinearIssue(feature.id, {
  description: `
## 📄 Specification

**Design Doc**: [Feature Design: ${metadata.title}](${doc.url})

**Original File**: \`${relativePath}\`
**Migrated**: ${new Date().toISOString().split('T')[0]}
**Original Status**: ${metadata.status || 'N/A'}

---

${feature.description}
  `
})

// 4. If file has checklist, extract and add as sub-tasks
if (hasChecklist(content)) {
  const tasks = extractChecklist(content)
  for (const task of tasks) {
    await createLinearIssue({
      title: task.title,
      team: feature.team,
      project: feature.project,
      parent: feature.id,  // Sub-issue
      labels: ['task', 'migrated'],
      description: task.description || ''
    })
  }
}

For Task:

// Create Task (regular issue, may or may not be sub-issue)
const task = await createLinearIssue({
  title: metadata.title,
  team: detectTeam(projectPath),
  project: detectProject(projectPath),
  labels: ['task', 'migrated', metadata.status === '✅ COMPLETED' ? 'status:done' : 'planning'],
  status: metadata.status === '✅ COMPLETED' ? 'Done' : 'Planning',
  description: `
## 📄 Original Implementation Notes

**Original File**: \`${relativePath}\`
**Migrated**: ${new Date().toISOString().split('T')[0]}
**Original Status**: ${metadata.status || 'N/A'}

---

${transformedContent}
  `
})

For Documentation:

// Don't create Linear issue, just create reference document
const doc = await createLinearDocument({
  title: `Reference: ${metadata.title}`,
  content: `
# ${metadata.title}

**Source**: \`${relativePath}\`
**Type**: Documentation/Guide

---

${content}
  `
})

// Optionally create "Documentation" parent issue to group all docs

Step 5.4: Move Original File

After successful migration:

# Create migrated directory
mkdir -p ${projectPath}/.claude/migrated/${category}

# Move original file
mv ${filePath} ${projectPath}/.claude/migrated/${category}/

# Create breadcrumb file
echo "Migrated to Linear: [WORK-123](url)" > ${filePath}.migrated.txt

Step 6: Track Progress

Show progress during migration:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔄 Migration in Progress...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[████████████░░░░░░░░] 12/45 (26%)

Current: 20251031-posthog-observability-implementation.md
Status: Creating feature... ✅
       Creating design doc... ✅
       Extracting subtasks... ✅ (3 tasks created)
       Moving to migrated/... ✅

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Step 7: Generate Migration Summary

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Migration Complete!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📊 Migration Results
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Total Files Migrated: 45
- Epics Created: 2
- Features Created: 12
- Tasks Created: 25
- Documentation: 6

Total Linear Issues Created: 39
Total Linear Documents Created: 14
Total Sub-Tasks Created: 18

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 Created Items Summary
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Epics:
✅ WORK-100: Production Deployment Plan
✅ WORK-101: Feature Roadmap V1

Features:
✅ WORK-102: PostHog Observability (+ 3 subtasks)
✅ WORK-103: Search & Filter System (+ 4 subtasks)
✅ WORK-104: Task Comment Enhancements (+ 2 subtasks)
... [show first 10, then "and X more"]

Tasks:
✅ WORK-120: Fix PostHog Provider Crash
✅ WORK-121: Implement Additional Task Flags
... [show first 10, then "and X more"]

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📁 Original Files
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Original files moved to:
${projectPath}/.claude/migrated/

Breadcrumb files created with Linear links.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💡 Next Steps
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. Review migrated items in Linear
2. Update status labels if needed
3. Link related features/tasks
4. Run /ccpm:utils:report to see project overview

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Step 8: Create Migration Log

Create a migration log file:

# Migration Log - ${new Date().toISOString().split('T')[0]}

## Summary

- Total Files: 45
- Epics: 2
- Features: 12
- Tasks: 25
- Documentation: 6

## Migrated Files

| Original File | Type | Linear ID | Linear URL | Status |
|---------------|------|-----------|------------|--------|
| production-deployment-plan.md | Epic | WORK-100 | [Link](url) | ✅ |
| 20251031-posthog-observability-implementation.md | Feature | WORK-102 | [Link](url) | ✅ |
| ... | ... | ... | ... | ... |

## Errors

[None / List any files that failed to migrate]

## Notes

- Original files moved to `.claude/migrated/`
- All Linear issues labeled with `migrated`
- Spec documents created for Epics and Features
- Subtasks extracted from checklists

Save to: ${projectPath}/.claude/migration-log-${timestamp}.md

Step 9: Interactive Next Actions

{
  questions: [{
    question: "Migration complete! What would you like to do next?",
    header: "Next Step",
    multiSelect: false,
    options: [
      {
        label: "View Project Report",
        description: "See all migrated items organized (/ccpm:utils:report)"
      },
      {
        label: "Review in Linear",
        description: "Open Linear to review migrated items"
      },
      {
        label: "View Migration Log",
        description: "Open detailed migration log file"
      },
      {
        label: "Done",
        description: "Finish migration"
      }
    ]
  }]
}

Helper Functions

function detectTeamAndProject(projectPath) {
  // Load CCPM configuration
  const config = loadCCPMConfig() // from ~/.claude/ccpm-config.yaml

  // Try auto-detection from config patterns
  for (const [projectId, projectConfig] of Object.entries(config.projects)) {
    const patterns = config.context.detection.patterns || []

    // Check if path matches any detection pattern
    for (const pattern of patterns) {
      if (pattern.project === projectId) {
        const regex = new RegExp(pattern.pattern.replace('*', '.*'))
        if (regex.test(projectPath)) {
          return {
            team: projectConfig.linear.team,
            project: projectConfig.linear.project,
            projectId: projectId
          }
        }
      }
    }

    // Also check repository URL match
    if (projectConfig.repository?.url && projectPath.includes(projectConfig.repository.url)) {
      return {
        team: projectConfig.linear.team,
        project: projectConfig.linear.project,
        projectId: projectId
      }
    }
  }

  // If no match, use active project or prompt
  const activeProject = config.context.current_project
  if (activeProject && config.projects[activeProject]) {
    return {
      team: config.projects[activeProject].linear.team,
      project: config.projects[activeProject].linear.project,
      projectId: activeProject
    }
  }

  // Last resort: prompt user to select from available projects
  return promptForProject(config.projects)
}

function loadCCPMConfig() {
  // Load from ~/.claude/ccpm-config.yaml
  const configPath = path.join(process.env.HOME, '.claude', 'ccpm-config.yaml')
  return yaml.parse(fs.readFileSync(configPath, 'utf8'))
}

function extractMetadata(content) {
  const metadata = {}

  // Extract title (first # heading)
  const titleMatch = content.match(/^#\s+(.+)$/m)
  metadata.title = titleMatch ? titleMatch[1] : 'Untitled'

  // Extract created date
  const createdMatch = content.match(/\*\*Created[:\s]+\*\*\s*(\d{4}-\d{2}-\d{2})/i)
  metadata.created = createdMatch ? createdMatch[1] : null

  // Extract status
  const statusMatch = content.match(/\*\*Status[:\s]+\*\*\s*(.+?)(?:\n|$)/i)
  metadata.status = statusMatch ? statusMatch[1].trim() : null

  // Extract priority
  const priorityMatch = content.match(/\*\*Priority[:\s]+\*\*\s*(.+?)(?:\n|$)/i)
  metadata.priority = priorityMatch ? mapPriority(priorityMatch[1].trim()) : null

  return metadata
}

function extractChecklist(content) {
  const tasks = []
  const checklistRegex = /- \[ \] \*\*(.+?)\*\*[:\s]+(.+?)(?:\(Est: (.+?)\))?$/gm

  let match
  while ((match = checklistRegex.exec(content)) !== null) {
    tasks.push({
      title: match[1].trim(),
      description: match[2].trim(),
      estimate: match[3] ? match[3].trim() : null
    })
  }

  return tasks
}

Notes

  • Safe Migration: Original files are moved, not deleted
  • Preserves History: Original file path saved in Linear description
  • Full Content: Entire markdown content migrated to Linear Documents
  • Relationships: Checklists → Sub-tasks automatically
  • Status Mapping: Original status preserved as label
  • Breadcrumbs: .migrated.txt files created with Linear links
  • Rollback: Can restore from .claude/migrated/ if needed