[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F7E9RTSBWSCH2WTM4DR5WN":3,"skill-same:01M6F7E9RTSBWSCH2WTM4DR5WN":77},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":45,"linkOnly":46,"markdownContent":47,"markdownBody":48,"frontMatter":49,"refName":54,"commitSha":55,"skillDirectory":56,"forksCount":57,"repositoryHtmlUrl":58,"repositoryHomepageUrl":6,"ownerHtmlUrl":59,"ownerBlogUrl":60,"ownerDescription":61,"updatedAt":62,"install":63,"resources":76},"01M6F7E9RTSBWSCH2WTM4DR5WN","juicebox-migration-deep-dive",null,"Advanced Juicebox data migration strategies.\nUse when migrating from other recruiting platforms, performing bulk data imports,\nor implementing complex data transformation pipelines.\nTrigger with phrases like \"juicebox data migration\", \"migrate to juicebox\",\n\"juicebox import\", \"juicebox bulk migration\".","Dicklesworthstone\u002Fpi_agent_rust","Dicklesworthstone","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F35050222?v=4","01M6CG26AR942CSVMN1M5E3DSE","Jeff Emanuel","01M6CG26EPP71PSHW1VDS71121",1981,"MIT","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Fjuicebox-pack\u002Fskills\u002Fjuicebox-migration-deep-dive\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44],"deep dive","juicebox migration deep dive","migration","deep","transformation","when","imports","implementing","advanced","with","data migration","bulk","import","trigger","io","data","strategies","use","edit","write","read","curl","migrate","Bash","Recruiting","kubectl","2026-11-10 10:12:30",false,"---\nname: juicebox-migration-deep-dive\ndescription: |\n  Advanced Juicebox data migration strategies.\n  Use when migrating from other recruiting platforms, performing bulk data imports,\n  or implementing complex data transformation pipelines.\n  Trigger with phrases like \"juicebox data migration\", \"migrate to juicebox\",\n  \"juicebox import\", \"juicebox bulk migration\".\nallowed-tools: Read, Write, Edit, Bash(kubectl:*), Bash(curl:*)\nversion: 1.0.0\nlicense: MIT\nauthor: Jeremy Longshore \u003Cjeremy@intentsolutions.io>\n---\n\n# Juicebox Migration Deep Dive\n\n## Overview\nAdvanced strategies for migrating data to Juicebox from other recruiting and people search platforms.\n\n## Prerequisites\n- Source data access and export capabilities\n- Juicebox Enterprise plan (for bulk imports)\n- Data mapping documentation\n- Testing environment\n\n## Migration Sources\n\n| Source | Complexity | Common Issues |\n|--------|------------|---------------|\n| LinkedIn Recruiter | Medium | Rate limits, field mapping |\n| Greenhouse | Low | Well-documented API |\n| Lever | Low | Standard export format |\n| Custom ATS | High | Custom transformation needed |\n| CSV\u002FExcel | Low | Data quality issues |\n\n## Instructions\n\n### Step 1: Data Assessment\n```typescript\n\u002F\u002F scripts\u002Fassess-source-data.ts\ninterface DataAssessment {\n  totalRecords: number;\n  uniqueProfiles: number;\n  duplicates: number;\n  fieldCoverage: Record\u003Cstring, number>;\n  dataQualityScore: number;\n  estimatedMigrationTime: string;\n}\n\nexport async function assessSourceData(\n  source: string,\n  sampleSize: number = 1000\n): Promise\u003CDataAssessment> {\n  const sample = await loadSampleData(source, sampleSize);\n\n  const assessment: DataAssessment = {\n    totalRecords: sample.total,\n    uniqueProfiles: new Set(sample.records.map(r => r.email)).size,\n    duplicates: sample.total - new Set(sample.records.map(r => r.email)).size,\n    fieldCoverage: calculateFieldCoverage(sample.records),\n    dataQualityScore: calculateQualityScore(sample.records),\n    estimatedMigrationTime: estimateMigrationTime(sample.total)\n  };\n\n  return assessment;\n}\n\nfunction calculateFieldCoverage(records: any[]): Record\u003Cstring, number> {\n  const fields = ['name', 'email', 'title', 'company', 'location', 'phone'];\n  const coverage: Record\u003Cstring, number> = {};\n\n  for (const field of fields) {\n    const count = records.filter(r => r[field] && r[field].trim()).length;\n    coverage[field] = (count \u002F records.length) * 100;\n  }\n\n  return coverage;\n}\n```\n\n### Step 2: Schema Mapping\n```typescript\n\u002F\u002F lib\u002Fmigration\u002Fschema-mapper.ts\nexport interface FieldMapping {\n  sourceField: string;\n  targetField: string;\n  transform?: (value: any) => any;\n  required: boolean;\n}\n\nexport const linkedInMapping: FieldMapping[] = [\n  { sourceField: 'firstName', targetField: 'first_name', required: true },\n  { sourceField: 'lastName', targetField: 'last_name', required: true },\n  {\n    sourceField: 'fullName',\n    targetField: 'name',\n    transform: (v) => v || undefined,\n    required: false\n  },\n  { sourceField: 'headline', targetField: 'title', required: false },\n  { sourceField: 'companyName', targetField: 'company', required: false },\n  {\n    sourceField: 'location',\n    targetField: 'location',\n    transform: normalizeLocation,\n    required: false\n  },\n  {\n    sourceField: 'profileUrl',\n    targetField: 'linkedin_url',\n    transform: normalizeLinkedInUrl,\n    required: false\n  },\n  {\n    sourceField: 'connectionDegree',\n    targetField: 'metadata.connection_degree',\n    required: false\n  }\n];\n\nexport class SchemaMapper {\n  constructor(private mappings: FieldMapping[]) {}\n\n  mapRecord(source: Record\u003Cstring, any>): Record\u003Cstring, any> {\n    const target: Record\u003Cstring, any> = {};\n\n    for (const mapping of this.mappings) {\n      let value = this.getNestedValue(source, mapping.sourceField);\n\n      if (mapping.transform) {\n        value = mapping.transform(value);\n      }\n\n      if (value !== undefined && value !== null && value !== '') {\n        this.setNestedValue(target, mapping.targetField, value);\n      } else if (mapping.required) {\n        throw new Error(`Required field missing: ${mapping.sourceField}`);\n      }\n    }\n\n    return target;\n  }\n}\n```\n\n### Step 3: Data Transformation Pipeline\n```typescript\n\u002F\u002F lib\u002Fmigration\u002Fpipeline.ts\nimport { Transform, pipeline } from 'stream';\nimport { promisify } from 'util';\n\nconst pipelineAsync = promisify(pipeline);\n\nexport class MigrationPipeline {\n  private stages: Transform[] = [];\n\n  addStage(name: string, transform: (record: any) => any): this {\n    this.stages.push(new Transform({\n      objectMode: true,\n      transform(record, encoding, callback) {\n        try {\n          const result = transform(record);\n          if (result) {\n            this.push(result);\n          }\n          callback();\n        } catch (error) {\n          callback(error as Error);\n        }\n      }\n    }));\n    return this;\n  }\n\n  async run(source: Readable, destination: Writable): Promise\u003CMigrationStats> {\n    const stats = new MigrationStats();\n\n    const statsTracker = new Transform({\n      objectMode: true,\n      transform(record, encoding, callback) {\n        stats.increment();\n        this.push(record);\n        callback();\n      }\n    });\n\n    await pipelineAsync(\n      source,\n      ...this.stages,\n      statsTracker,\n      destination\n    );\n\n    return stats;\n  }\n}\n\n\u002F\u002F Usage\nconst pipeline = new MigrationPipeline()\n  .addStage('parse', parseCSVRecord)\n  .addStage('validate', validateRecord)\n  .addStage('deduplicate', deduplicateRecord)\n  .addStage('transform', transformToJuiceboxSchema)\n  .addStage('enrich', enrichWithMetadata);\n```\n\n### Step 4: Bulk Import with Rate Limiting\n```typescript\n\u002F\u002F lib\u002Fmigration\u002Fbulk-importer.ts\nexport class BulkImporter {\n  private rateLimiter: RateLimiter;\n  private batchSize: number;\n  private maxConcurrent: number;\n\n  constructor(options: {\n    requestsPerSecond: number;\n    batchSize: number;\n    maxConcurrent: number;\n  }) {\n    this.rateLimiter = new RateLimiter(options.requestsPerSecond);\n    this.batchSize = options.batchSize;\n    this.maxConcurrent = options.maxConcurrent;\n  }\n\n  async import(records: Profile[]): Promise\u003CImportResult> {\n    const result: ImportResult = {\n      total: records.length,\n      successful: 0,\n      failed: 0,\n      errors: []\n    };\n\n    \u002F\u002F Split into batches\n    const batches = chunk(records, this.batchSize);\n\n    \u002F\u002F Process batches with concurrency limit\n    const semaphore = new Semaphore(this.maxConcurrent);\n\n    await Promise.all(batches.map(async (batch, index) => {\n      await semaphore.acquire();\n      try {\n        await this.rateLimiter.wait();\n        const batchResult = await this.importBatch(batch);\n\n        result.successful += batchResult.successful;\n        result.failed += batchResult.failed;\n        result.errors.push(...batchResult.errors);\n\n        logger.info(`Batch ${index + 1}\u002F${batches.length} complete`, {\n          successful: batchResult.successful,\n          failed: batchResult.failed\n        });\n      } finally {\n        semaphore.release();\n      }\n    }));\n\n    return result;\n  }\n\n  private async importBatch(batch: Profile[]): Promise\u003CBatchResult> {\n    try {\n      const response = await juiceboxClient.profiles.bulkImport(batch);\n      return {\n        successful: response.created + response.updated,\n        failed: response.failed,\n        errors: response.errors\n      };\n    } catch (error) {\n      return {\n        successful: 0,\n        failed: batch.length,\n        errors: [{ message: (error as Error).message, records: batch }]\n      };\n    }\n  }\n}\n```\n\n### Step 5: Validation and Reconciliation\n```typescript\n\u002F\u002F lib\u002Fmigration\u002Fvalidator.ts\nexport class MigrationValidator {\n  async validateMigration(\n    sourceCount: number,\n    destinationQuery: string\n  ): Promise\u003CValidationReport> {\n    const report: ValidationReport = {\n      sourceCount,\n      destinationCount: 0,\n      matchRate: 0,\n      missingRecords: [],\n      dataIntegrityIssues: []\n    };\n\n    \u002F\u002F Count destination records\n    const destResult = await juiceboxClient.search.people({\n      query: destinationQuery,\n      limit: 0\n    });\n    report.destinationCount = destResult.total;\n    report.matchRate = (report.destinationCount \u002F sourceCount) * 100;\n\n    \u002F\u002F Sample validation\n    const sampleSize = Math.min(100, sourceCount);\n    const sample = await this.getSampleFromSource(sampleSize);\n\n    for (const record of sample) {\n      const match = await this.findInDestination(record);\n      if (!match) {\n        report.missingRecords.push(record.id);\n      } else {\n        const issues = this.compareRecords(record, match);\n        if (issues.length > 0) {\n          report.dataIntegrityIssues.push({\n            recordId: record.id,\n            issues\n          });\n        }\n      }\n    }\n\n    return report;\n  }\n\n  private compareRecords(source: any, dest: any): string[] {\n    const issues: string[] = [];\n    const criticalFields = ['name', 'email', 'company'];\n\n    for (const field of criticalFields) {\n      if (source[field] !== dest[field]) {\n        issues.push(`${field} mismatch: \"${source[field]}\" vs \"${dest[field]}\"`);\n      }\n    }\n\n    return issues;\n  }\n}\n```\n\n### Step 6: Rollback Strategy\n```typescript\n\u002F\u002F lib\u002Fmigration\u002Frollback.ts\nexport class MigrationRollback {\n  private checkpointFile: string;\n\n  constructor(migrationId: string) {\n    this.checkpointFile = `.\u002Fcheckpoints\u002F${migrationId}.json`;\n  }\n\n  async saveCheckpoint(state: MigrationState): Promise\u003Cvoid> {\n    await fs.writeFile(this.checkpointFile, JSON.stringify(state, null, 2));\n  }\n\n  async loadCheckpoint(): Promise\u003CMigrationState | null> {\n    try {\n      const data = await fs.readFile(this.checkpointFile, 'utf-8');\n      return JSON.parse(data);\n    } catch {\n      return null;\n    }\n  }\n\n  async rollback(migrationId: string): Promise\u003CRollbackResult> {\n    const checkpoint = await this.loadCheckpoint();\n    if (!checkpoint) {\n      throw new Error('No checkpoint found for rollback');\n    }\n\n    \u002F\u002F Delete imported records\n    const deleted = await juiceboxClient.profiles.bulkDelete({\n      filter: { migrationId }\n    });\n\n    return {\n      recordsRolledBack: deleted.count,\n      checkpoint: checkpoint.lastProcessedId\n    };\n  }\n}\n```\n\n## Migration Checklist\n\n```markdown\n## Pre-Migration\n- [ ] Source data exported and validated\n- [ ] Field mapping documented\n- [ ] Test migration on sample data\n- [ ] Rollback plan documented\n- [ ] Stakeholder sign-off\n\n## During Migration\n- [ ] Monitoring dashboards active\n- [ ] Progress tracking enabled\n- [ ] Error logging configured\n- [ ] Checkpoint saves working\n\n## Post-Migration\n- [ ] Reconciliation complete\n- [ ] Data integrity verified\n- [ ] Source system archived\n- [ ] Documentation updated\n- [ ] Team training complete\n```\n\n## Output\n- Data assessment tools\n- Schema mapping configuration\n- Transformation pipeline\n- Bulk import with rate limiting\n- Validation and reconciliation\n\n## Resources\n- [Bulk Import Guide](https:\u002F\u002Fjuicebox.ai\u002Fdocs\u002Fmigration)\n- [Data Format Specifications](https:\u002F\u002Fjuicebox.ai\u002Fdocs\u002Fdata-formats)\n\n## Summary\nThis skill pack completes the enterprise-grade Juicebox integration toolkit.\n","# Juicebox Migration Deep Dive\n\n## Overview\nAdvanced strategies for migrating data to Juicebox from other recruiting and people search platforms.\n\n## Prerequisites\n- Source data access and export capabilities\n- Juicebox Enterprise plan (for bulk imports)\n- Data mapping documentation\n- Testing environment\n\n## Migration Sources\n\n| Source | Complexity | Common Issues |\n|--------|------------|---------------|\n| LinkedIn Recruiter | Medium | Rate limits, field mapping |\n| Greenhouse | Low | Well-documented API |\n| Lever | Low | Standard export format |\n| Custom ATS | High | Custom transformation needed |\n| CSV\u002FExcel | Low | Data quality issues |\n\n## Instructions\n\n### Step 1: Data Assessment\n```typescript\n\u002F\u002F scripts\u002Fassess-source-data.ts\ninterface DataAssessment {\n  totalRecords: number;\n  uniqueProfiles: number;\n  duplicates: number;\n  fieldCoverage: Record\u003Cstring, number>;\n  dataQualityScore: number;\n  estimatedMigrationTime: string;\n}\n\nexport async function assessSourceData(\n  source: string,\n  sampleSize: number = 1000\n): Promise\u003CDataAssessment> {\n  const sample = await loadSampleData(source, sampleSize);\n\n  const assessment: DataAssessment = {\n    totalRecords: sample.total,\n    uniqueProfiles: new Set(sample.records.map(r => r.email)).size,\n    duplicates: sample.total - new Set(sample.records.map(r => r.email)).size,\n    fieldCoverage: calculateFieldCoverage(sample.records),\n    dataQualityScore: calculateQualityScore(sample.records),\n    estimatedMigrationTime: estimateMigrationTime(sample.total)\n  };\n\n  return assessment;\n}\n\nfunction calculateFieldCoverage(records: any[]): Record\u003Cstring, number> {\n  const fields = ['name', 'email', 'title', 'company', 'location', 'phone'];\n  const coverage: Record\u003Cstring, number> = {};\n\n  for (const field of fields) {\n    const count = records.filter(r => r[field] && r[field].trim()).length;\n    coverage[field] = (count \u002F records.length) * 100;\n  }\n\n  return coverage;\n}\n```\n\n### Step 2: Schema Mapping\n```typescript\n\u002F\u002F lib\u002Fmigration\u002Fschema-mapper.ts\nexport interface FieldMapping {\n  sourceField: string;\n  targetField: string;\n  transform?: (value: any) => any;\n  required: boolean;\n}\n\nexport const linkedInMapping: FieldMapping[] = [\n  { sourceField: 'firstName', targetField: 'first_name', required: true },\n  { sourceField: 'lastName', targetField: 'last_name', required: true },\n  {\n    sourceField: 'fullName',\n    targetField: 'name',\n    transform: (v) => v || undefined,\n    required: false\n  },\n  { sourceField: 'headline', targetField: 'title', required: false },\n  { sourceField: 'companyName', targetField: 'company', required: false },\n  {\n    sourceField: 'location',\n    targetField: 'location',\n    transform: normalizeLocation,\n    required: false\n  },\n  {\n    sourceField: 'profileUrl',\n    targetField: 'linkedin_url',\n    transform: normalizeLinkedInUrl,\n    required: false\n  },\n  {\n    sourceField: 'connectionDegree',\n    targetField: 'metadata.connection_degree',\n    required: false\n  }\n];\n\nexport class SchemaMapper {\n  constructor(private mappings: FieldMapping[]) {}\n\n  mapRecord(source: Record\u003Cstring, any>): Record\u003Cstring, any> {\n    const target: Record\u003Cstring, any> = {};\n\n    for (const mapping of this.mappings) {\n      let value = this.getNestedValue(source, mapping.sourceField);\n\n      if (mapping.transform) {\n        value = mapping.transform(value);\n      }\n\n      if (value !== undefined && value !== null && value !== '') {\n        this.setNestedValue(target, mapping.targetField, value);\n      } else if (mapping.required) {\n        throw new Error(`Required field missing: ${mapping.sourceField}`);\n      }\n    }\n\n    return target;\n  }\n}\n```\n\n### Step 3: Data Transformation Pipeline\n```typescript\n\u002F\u002F lib\u002Fmigration\u002Fpipeline.ts\nimport { Transform, pipeline } from 'stream';\nimport { promisify } from 'util';\n\nconst pipelineAsync = promisify(pipeline);\n\nexport class MigrationPipeline {\n  private stages: Transform[] = [];\n\n  addStage(name: string, transform: (record: any) => any): this {\n    this.stages.push(new Transform({\n      objectMode: true,\n      transform(record, encoding, callback) {\n        try {\n          const result = transform(record);\n          if (result) {\n            this.push(result);\n          }\n          callback();\n        } catch (error) {\n          callback(error as Error);\n        }\n      }\n    }));\n    return this;\n  }\n\n  async run(source: Readable, destination: Writable): Promise\u003CMigrationStats> {\n    const stats = new MigrationStats();\n\n    const statsTracker = new Transform({\n      objectMode: true,\n      transform(record, encoding, callback) {\n        stats.increment();\n        this.push(record);\n        callback();\n      }\n    });\n\n    await pipelineAsync(\n      source,\n      ...this.stages,\n      statsTracker,\n      destination\n    );\n\n    return stats;\n  }\n}\n\n\u002F\u002F Usage\nconst pipeline = new MigrationPipeline()\n  .addStage('parse', parseCSVRecord)\n  .addStage('validate', validateRecord)\n  .addStage('deduplicate', deduplicateRecord)\n  .addStage('transform', transformToJuiceboxSchema)\n  .addStage('enrich', enrichWithMetadata);\n```\n\n### Step 4: Bulk Import with Rate Limiting\n```typescript\n\u002F\u002F lib\u002Fmigration\u002Fbulk-importer.ts\nexport class BulkImporter {\n  private rateLimiter: RateLimiter;\n  private batchSize: number;\n  private maxConcurrent: number;\n\n  constructor(options: {\n    requestsPerSecond: number;\n    batchSize: number;\n    maxConcurrent: number;\n  }) {\n    this.rateLimiter = new RateLimiter(options.requestsPerSecond);\n    this.batchSize = options.batchSize;\n    this.maxConcurrent = options.maxConcurrent;\n  }\n\n  async import(records: Profile[]): Promise\u003CImportResult> {\n    const result: ImportResult = {\n      total: records.length,\n      successful: 0,\n      failed: 0,\n      errors: []\n    };\n\n    \u002F\u002F Split into batches\n    const batches = chunk(records, this.batchSize);\n\n    \u002F\u002F Process batches with concurrency limit\n    const semaphore = new Semaphore(this.maxConcurrent);\n\n    await Promise.all(batches.map(async (batch, index) => {\n      await semaphore.acquire();\n      try {\n        await this.rateLimiter.wait();\n        const batchResult = await this.importBatch(batch);\n\n        result.successful += batchResult.successful;\n        result.failed += batchResult.failed;\n        result.errors.push(...batchResult.errors);\n\n        logger.info(`Batch ${index + 1}\u002F${batches.length} complete`, {\n          successful: batchResult.successful,\n          failed: batchResult.failed\n        });\n      } finally {\n        semaphore.release();\n      }\n    }));\n\n    return result;\n  }\n\n  private async importBatch(batch: Profile[]): Promise\u003CBatchResult> {\n    try {\n      const response = await juiceboxClient.profiles.bulkImport(batch);\n      return {\n        successful: response.created + response.updated,\n        failed: response.failed,\n        errors: response.errors\n      };\n    } catch (error) {\n      return {\n        successful: 0,\n        failed: batch.length,\n        errors: [{ message: (error as Error).message, records: batch }]\n      };\n    }\n  }\n}\n```\n\n### Step 5: Validation and Reconciliation\n```typescript\n\u002F\u002F lib\u002Fmigration\u002Fvalidator.ts\nexport class MigrationValidator {\n  async validateMigration(\n    sourceCount: number,\n    destinationQuery: string\n  ): Promise\u003CValidationReport> {\n    const report: ValidationReport = {\n      sourceCount,\n      destinationCount: 0,\n      matchRate: 0,\n      missingRecords: [],\n      dataIntegrityIssues: []\n    };\n\n    \u002F\u002F Count destination records\n    const destResult = await juiceboxClient.search.people({\n      query: destinationQuery,\n      limit: 0\n    });\n    report.destinationCount = destResult.total;\n    report.matchRate = (report.destinationCount \u002F sourceCount) * 100;\n\n    \u002F\u002F Sample validation\n    const sampleSize = Math.min(100, sourceCount);\n    const sample = await this.getSampleFromSource(sampleSize);\n\n    for (const record of sample) {\n      const match = await this.findInDestination(record);\n      if (!match) {\n        report.missingRecords.push(record.id);\n      } else {\n        const issues = this.compareRecords(record, match);\n        if (issues.length > 0) {\n          report.dataIntegrityIssues.push({\n            recordId: record.id,\n            issues\n          });\n        }\n      }\n    }\n\n    return report;\n  }\n\n  private compareRecords(source: any, dest: any): string[] {\n    const issues: string[] = [];\n    const criticalFields = ['name', 'email', 'company'];\n\n    for (const field of criticalFields) {\n      if (source[field] !== dest[field]) {\n        issues.push(`${field} mismatch: \"${source[field]}\" vs \"${dest[field]}\"`);\n      }\n    }\n\n    return issues;\n  }\n}\n```\n\n### Step 6: Rollback Strategy\n```typescript\n\u002F\u002F lib\u002Fmigration\u002Frollback.ts\nexport class MigrationRollback {\n  private checkpointFile: string;\n\n  constructor(migrationId: string) {\n    this.checkpointFile = `.\u002Fcheckpoints\u002F${migrationId}.json`;\n  }\n\n  async saveCheckpoint(state: MigrationState): Promise\u003Cvoid> {\n    await fs.writeFile(this.checkpointFile, JSON.stringify(state, null, 2));\n  }\n\n  async loadCheckpoint(): Promise\u003CMigrationState | null> {\n    try {\n      const data = await fs.readFile(this.checkpointFile, 'utf-8');\n      return JSON.parse(data);\n    } catch {\n      return null;\n    }\n  }\n\n  async rollback(migrationId: string): Promise\u003CRollbackResult> {\n    const checkpoint = await this.loadCheckpoint();\n    if (!checkpoint) {\n      throw new Error('No checkpoint found for rollback');\n    }\n\n    \u002F\u002F Delete imported records\n    const deleted = await juiceboxClient.profiles.bulkDelete({\n      filter: { migrationId }\n    });\n\n    return {\n      recordsRolledBack: deleted.count,\n      checkpoint: checkpoint.lastProcessedId\n    };\n  }\n}\n```\n\n## Migration Checklist\n\n```markdown\n## Pre-Migration\n- [ ] Source data exported and validated\n- [ ] Field mapping documented\n- [ ] Test migration on sample data\n- [ ] Rollback plan documented\n- [ ] Stakeholder sign-off\n\n## During Migration\n- [ ] Monitoring dashboards active\n- [ ] Progress tracking enabled\n- [ ] Error logging configured\n- [ ] Checkpoint saves working\n\n## Post-Migration\n- [ ] Reconciliation complete\n- [ ] Data integrity verified\n- [ ] Source system archived\n- [ ] Documentation updated\n- [ ] Team training complete\n```\n\n## Output\n- Data assessment tools\n- Schema mapping configuration\n- Transformation pipeline\n- Bulk import with rate limiting\n- Validation and reconciliation\n\n## Resources\n- [Bulk Import Guide](https:\u002F\u002Fjuicebox.ai\u002Fdocs\u002Fmigration)\n- [Data Format Specifications](https:\u002F\u002Fjuicebox.ai\u002Fdocs\u002Fdata-formats)\n\n## Summary\nThis skill pack completes the enterprise-grade Juicebox integration toolkit.\n",{"name":5,"author":50,"license":15,"version":51,"description":52,"allowed-tools":53},"Jeremy Longshore \u003Cjeremy@intentsolutions.io>","1.0.0","Advanced Juicebox data migration strategies.\nUse when migrating from other recruiting platforms, performing bulk data imports,\nor implementing complex data transformation pipelines.\nTrigger with phrases like \"juicebox data migration\", \"migrate to juicebox\",\n\"juicebox import\", \"juicebox bulk migration\".\n","Read, Write, Edit, Bash(kubectl:*), Bash(curl:*)","main","90b50ed0778745a80fd5bfb13b1843d45b9e82f7","tests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Fjuicebox-pack\u002Fskills\u002Fjuicebox-migration-deep-dive",231,"https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust","https:\u002F\u002Fgithub.com\u002FDicklesworthstone","https:\u002F\u002Fwww.jeffreyemanuel.com\u002F","Building in NY","2026-11-12 03:15:57",{"source":8,"skillName":5,"commands":64,"prompt":74,"zipAvailable":75},[65,68,71],{"tool":66,"command":67},"npx","npx skills add Dicklesworthstone\u002Fpi_agent_rust --skill juicebox-migration-deep-dive",{"tool":69,"command":70},"bunx","bunx skills add Dicklesworthstone\u002Fpi_agent_rust --skill juicebox-migration-deep-dive",{"tool":72,"command":73},"pnpm","pnpm dlx skills add Dicklesworthstone\u002Fpi_agent_rust --skill juicebox-migration-deep-dive","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Fjuicebox-pack\u002Fskills\u002Fjuicebox-migration-deep-dive\u002FSKILL.md",true,[],[78,118,142,167,194,216],{"skillNo":79,"name":80,"nameZh":6,"description":81,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":82,"licenseFileUrl":83,"sourceHtmlUrl":84,"categories":85,"keywords":91,"publishedAt":45},"01M6FEGPPK0ZTKCCM0R3E3KS33","web-artifacts-builder","Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn\u002Fui). Use for complex artifacts requiring state management, routing, or shadcn\u002Fui components - not for simple single-file HTML\u002FJSX artifacts.","Apache-2.0","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Futilities\u002Fweb-artifacts-builder\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Futilities\u002Fweb-artifacts-builder\u002FSKILL.md",[86],{"categoryNo":87,"categoryName":88,"taxonomyType":89,"taxonomyTypeDesc":90},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",[92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,36,110,111,112,113,114,115,116,117],"web artifacts builder","web","artifacts","artifacts builder","builder","frontend","css","components","react","component","routing","state management","state","shadcn","file","ui","Tools","Tailwind","html","single-file","simple","ui components","multi","for","jsx","suite",{"skillNo":119,"name":120,"nameZh":6,"description":121,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":82,"licenseFileUrl":122,"sourceHtmlUrl":123,"categories":124,"keywords":129,"publishedAt":45},"01M6FEE9VTK1W4GY21AKREJXRG","webapp-testing","Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fsecurity\u002Fwebapp-testing\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fsecurity\u002Fwebapp-testing\u002FSKILL.md",[125,128],{"categoryNo":126,"categoryName":127,"taxonomyType":89,"taxonomyTypeDesc":90},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":87,"categoryName":88,"taxonomyType":89,"taxonomyTypeDesc":90},[130,131,132,97,133,134,93,135,136,137,107,138,139,140,141,28,115],"testing","webapp testing","webapp","screenshots","playwright","debugging","toolkit","browser","logs","applications","behavior","local",{"skillNo":143,"name":144,"nameZh":6,"description":145,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":146,"sourceHtmlUrl":147,"categories":148,"keywords":149,"publishedAt":45},"01M6FE20F7ZTQBFWKF4MPJ2SBX","neuropixels-analysis","Neuropixels neural recording analysis. Load SpikeGLX\u002FOpenEphys data, preprocess, motion correction, Kilosort4 spike sorting, quality metrics, Allen\u002FIBL curation, AI-assisted visual analysis, for Neuropixels 1.0\u002F2.0 extracellular electrophysiology. Use when working with neural recordings, spike sorting, extracellular electrophysiology, or when the user mentions Neuropixels, SpikeGLX, Open Ephys, Kilosort, quality metrics, or unit curation.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fscientific\u002Fneuropixels-analysis\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fscientific\u002Fneuropixels-analysis\u002FSKILL.md",[],[150,151,152,153,34,154,155,156,36,157,158,159,160,161,162,163,28,164,165,166,115,24],"analysis","neuropixels analysis","spike","metrics","open","quality","user","recordings","Neural","motion","visual","quality metrics","unit","sorting","curation","recording","load",{"skillNo":168,"name":169,"nameZh":6,"description":170,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":82,"licenseFileUrl":171,"sourceHtmlUrl":172,"categories":173,"keywords":177,"publishedAt":45},"01M6FDP0PDK6F1GGX395C7625Z","skill-creator","Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fskill-creator\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fskill-creator\u002FSKILL.md",[174],{"categoryNo":175,"categoryName":176,"taxonomyType":89,"taxonomyTypeDesc":90},"01JBSEEDCAT000000000000023","Skill与智能体开发",[178,179,180,181,182,183,184,185,186,187,188,189,190,191,28,115,192,24,193],"skill creator","creator","workflows","create","update","capabilities","knowledge","tool","integrations","specialized","guide","new skill","existing skill","users","Existing","new",{"skillNo":195,"name":196,"nameZh":6,"description":197,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":198,"sourceHtmlUrl":199,"categories":200,"keywords":202,"publishedAt":45},"01M6FDM8Y7VK7NYG9SM7QPA3PW","notebooklm","Use this skill to query your Google NotebookLM notebooks directly from Claude Code for source-grounded, citation-backed answers from Gemini. Browser automation, library management, persistent auth. Drastically reduced hallucinations through document-only responses.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fnotebooklm\u002FLICENSE","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fnotebooklm\u002FSKILL.md",[201],{"categoryNo":175,"categoryName":176,"taxonomyType":89,"taxonomyTypeDesc":90},[196,203,204,205,206,207,208,137,209,210,36,211,212,213,214,115,215],"google","browser automation","gemini","automation","auth","code","library","management","only","document","query","Citation","source",{"skillNo":217,"name":218,"nameZh":6,"description":219,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":82,"licenseFileUrl":220,"sourceHtmlUrl":221,"categories":222,"keywords":229,"publishedAt":45},"01M6FDHPXE10J3XGTCH5HBHS1M","slack-gif-creator","Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like \"make me a GIF of X doing Y for Slack.\"","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fenterprise-communication\u002Fslack-gif-creator\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fenterprise-communication\u002Fslack-gif-creator\u002FSKILL.md",[223,226],{"categoryNo":224,"categoryName":225,"taxonomyType":89,"taxonomyTypeDesc":90},"01JBSEEDCAT000000000000005","邮件与沟通",{"categoryNo":227,"categoryName":228,"taxonomyType":89,"taxonomyTypeDesc":90},"01JBSEEDCAT000000000000016","图像与多媒体",[230,231,232,179,233,108,234,235,184,36,236,191,237,238,239,115,240,24],"slack gif creator","slack","gif","validation","constraints","Animation","me","make","Utilities","concepts","request"]