Skip to main content Accueil Créateurs dicklesworthstone pi_agent_rust linear-migration-deep-dive
linear-migration-deep-dive Migrate from Jira, Asana, GitHub Issues, or other tools to Linear.
Use when planning a migration to Linear, executing data transfer,
or mapping workflows between tools.
Trigger with phrases like "migrate to linear", "jira to linear",
"asana to linear", "import to linear", "linear migration".
Aller à l'installation Skills Marketplace Découvrez et explorez les compétences IA créées par la communauté.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Copier le promptAfficher les détails du prompt Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
npx skills add https://github.com/Dicklesworthstone/pi_agent_rust --skill linear-migration-deep-diveLa commande reste sur une seule ligne. Faites défiler horizontalement pour la vérifier avant de la copier.
Vous préférez une copie locale ? Téléchargez les fichiers actuellement disponibles dans SkillsMP.
Télécharger Zip Téléchargement... Métiers associés SOC
Basé sur la classification professionnelle SOC
name linear-migration-deep-dive description Migrate from Jira, Asana, GitHub Issues, or other tools to Linear.
Use when planning a migration to Linear, executing data transfer,
or mapping workflows between tools.
Trigger with phrases like "migrate to linear", "jira to linear",
"asana to linear", "import to linear", "linear migration".
allowed-tools Read, Write, Edit, Bash(node:*), Bash(npx:*), Grep version 1.0.0 license MIT author Jeremy Longshore <jeremy@intentsolutions.io>
Linear Migration Deep Dive
Overview
Comprehensive guide for migrating from other issue trackers to Linear.
Prerequisites
Admin access to source system
Linear workspace with admin access
API access to both systems
Migration timeline and rollback plan
Migration Planning
Phase 1: Assessment
## Migration Assessment Checklist
[ ] Count total issues:
[ ] Count total projects:
[ ] Count total users:
[ ] Attachments size: GB
[ ] Custom fields count:
[ ] Document current statuses/states
[ ] Map status transitions
[ ] Identify automation rules
[ ] List integrations in use
[ ] Export user list from source
[ ] Map to Linear users
[ ] Plan for unmapped users
[ ] Migration window:
[ ] Parallel run period:
[ ] Cutover date:
[ ] Rollback deadline:
### Data Volume
-
____
-
____
-
____
-
____
-
____
### Workflow Analysis
-
-
-
-
### User Mapping
-
-
-
### Timeline
-
____
-
____
-
____
-
____
Phase 2: Workflow Mapping
const JIRA_STATUS_MAP : Record <string , string > = {
"To Do" : "Todo" ,
"In Progress" : "In Progress" ,
"In Review" : "In Review" ,
"Done" : "Done" ,
"Closed" : "Done" ,
"Backlog" : "Backlog" ,
"Blocked" : "In Progress" ,
};
const JIRA_PRIORITY_MAP : Record <string , number > = {
"Highest" : 1 ,
"High" : 2 ,
"Medium" : 3 ,
"Low" : 4 ,
"Lowest" : 4 ,
};
const JIRA_TYPE_MAP : Record <string , { labelName : string }> = {
"Bug" : { labelName : "Bug" },
"Story" : { labelName : "Feature" },
"Task" : { labelName : "Task" },
"Epic" : { labelName : "Epic" },
"Subtask" : { labelName : "Subtask" },
};
const ASANA_SECTION_MAP : Record <string , string > = {
"To Do" : "Todo" ,
"Doing" : "In Progress" ,
"Review" : "In Review" ,
"Complete" : "Done" ,
};
Instructions
Step 1: Export from Source System
import JiraClient from "jira-client" ;
const jira = new JiraClient ({
host : process.env .JIRA_HOST ,
basic_auth : {
email : process.env .JIRA_EMAIL ,
api_token : process.env .JIRA_API_TOKEN ,
},
});
interface JiraIssue {
key : string ;
fields : {
summary : string ;
description : string ;
status : { name : string };
priority : { name : string };
issuetype : { name : string };
assignee : { emailAddress : string } | null ;
reporter : { emailAddress : string };
created : string ;
updated : string ;
parent ?: { key : string };
subtasks : { key : string }[];
labels : string [];
customfield_10001 ?: number ;
};
}
export async function exportJiraProject (projectKey : string ): Promise <JiraIssue []> {
const issues : JiraIssue [] = [];
let startAt = 0 ;
const maxResults = 100 ;
while (true ) {
const result = await jira.searchJira (
`project = ${projectKey} ORDER BY created ASC` ,
{
startAt,
maxResults,
fields : [
"summary" ,
"description" ,
"status" ,
"priority" ,
"issuetype" ,
"assignee" ,
"reporter" ,
"created" ,
"updated" ,
"parent" ,
"subtasks" ,
"labels" ,
"customfield_10001" ,
],
}
);
issues.push (...result.issues );
if (issues.length >= result.total ) break ;
startAt += maxResults;
console .log (`Exported ${issues.length} /${result.total} issues...` );
}
await fs.writeFile (
`jira-export-${projectKey} -${Date .now()} .json` ,
JSON .stringify (issues, null , 2 )
);
return issues;
}
import Asana from "asana" ;
const asana = Asana .Client .create ().useAccessToken (process.env .ASANA_TOKEN );
export async function exportAsanaProject (projectGid : string ) {
const tasks = [];
const result = await asana.tasks .getTasks ({
project : projectGid,
opt_fields : [
"name" ,
"notes" ,
"assignee" ,
"due_on" ,
"completed" ,
"memberships.section.name" ,
"tags.name" ,
"parent.gid" ,
"subtasks.gid" ,
"created_at" ,
"modified_at" ,
],
});
for await (const task of result) {
tasks.push (task);
}
return tasks;
}
Step 2: Transform Data
import { LinearClient } from "@linear/sdk" ;
interface LinearIssueInput {
teamId : string ;
title : string ;
description ?: string ;
priority ?: number ;
stateId ?: string ;
assigneeId ?: string ;
labelIds ?: string [];
estimate ?: number ;
parentId ?: string ;
}
interface TransformContext {
linearClient : LinearClient ;
teamId : string ;
stateMap : Map <string , string >;
userMap : Map <string , string >;
labelMap : Map <string , string >;
issueIdMap : Map <string , string >;
}
export async function transformJiraIssue (
jiraIssue : JiraIssue ,
context : TransformContext
): Promise <LinearIssueInput > {
const linearStatus = JIRA_STATUS_MAP [jiraIssue.fields .status .name ] || "Todo" ;
const stateId = context.stateMap .get (linearStatus);
const priority = JIRA_PRIORITY_MAP [jiraIssue.fields .priority ?.name ] || 0 ;
const assigneeEmail = jiraIssue.fields .assignee ?.emailAddress ;
const assigneeId = assigneeEmail ? context.userMap .get (assigneeEmail) : undefined ;
const labelIds : string [] = [];
const typeLabel = JIRA_TYPE_MAP [jiraIssue.fields .issuetype .name ];
if (typeLabel && context.labelMap .has (typeLabel.labelName )) {
labelIds.push (context.labelMap .get (typeLabel.labelName )!);
}
for (const label of jiraIssue.fields .labels ) {
const linearLabelId = context.labelMap .get (label);
if (linearLabelId) {
labelIds.push (linearLabelId);
}
}
const description = convertJiraToMarkdown (jiraIssue.fields .description );
return {
teamId : context.teamId ,
title : `[${jiraIssue.key} ] ${jiraIssue.fields.summary} ` ,
description,
priority,
stateId,
assigneeId,
labelIds,
estimate : jiraIssue.fields .customfield_10001 ,
};
}
function convertJiraToMarkdown (jiraMarkup : string | null ): string {
if (!jiraMarkup) return "" ;
let md = jiraMarkup;
md = md.replace (/h1\. /g , "# " );
md = md.replace (/h2\. /g , "## " );
md = md.replace (/h3\. /g , "### " );
md = md.replace (/\*([^*]+)\*/g , "**$1**" );
md = md.replace (/_([^_]+)_/g , "*$1*" );
md = md.replace (/\{code(:([^}]+))?\}([\s\S]*?)\{code\}/g , "```$2\n$3\n```" );
md = md.replace (/\{noformat\}([\s\S]*?)\{noformat\}/g , "```\n$1\n```" );
md = md.replace (/^# /gm , "1. " );
md = md.replace (/^\* /gm , "- " );
md = md.replace (/\[([^\]|]+)\|([^\]]+)\]/g , "[$1]($2)" );
md = md.replace (/\[([^\]]+)\]/g , "[$1]($1)" );
return md;
}
Step 3: Import to Linear
import { LinearClient } from "@linear/sdk" ;
interface ImportStats {
total : number ;
created : number ;
skipped : number ;
errors : { sourceId : string ; error : string }[];
}
export async function importToLinear (
issues : JiraIssue [],
context : TransformContext
): Promise <ImportStats > {
const stats : ImportStats = {
total : issues.length ,
created : 0 ,
skipped : 0 ,
errors : [],
};
const sorted = sortByHierarchy (issues);
for (const jiraIssue of sorted) {
try {
if (context.issueIdMap .has (jiraIssue.key )) {
stats.skipped ++;
continue ;
}
const input = await transformJiraIssue (jiraIssue, context);
if (jiraIssue.fields .parent ) {
input.parentId = context.issueIdMap .get (jiraIssue.fields .parent .key );
}
const result = await context.linearClient .createIssue (input);
if (result.success ) {
const issue = await result.issue ;
context.issueIdMap .set (jiraIssue.key , issue!.id );
stats.created ++;
await sleep (100 );
} else {
throw new Error ("Create failed" );
}
console .log (`Imported ${stats.created} /${stats.total} : ${jiraIssue.key} ` );
} catch (error) {
stats.errors .push ({
sourceId : jiraIssue.key ,
error : error instanceof Error ? error.message : "Unknown error" ,
});
console .error (`Failed to import ${jiraIssue.key} :` , error);
}
}
return stats;
}
function sortByHierarchy (issues : JiraIssue [] ): JiraIssue [] {
const byKey = new Map (issues.map (i => [i.key , i]));
const sorted : JiraIssue [] = [];
const processed = new Set <string >();
function addWithDependencies (issue : JiraIssue ): void {
if (processed.has (issue.key )) return ;
if (issue.fields .parent ) {
const parent = byKey.get (issue.fields .parent .key );
if (parent) addWithDependencies (parent);
}
sorted.push (issue);
processed.add (issue.key );
}
for (const issue of issues) {
addWithDependencies (issue);
}
return sorted;
}
Step 4: Validation & Verification
export async function validateMigration (
sourceIssues : JiraIssue [],
context : TransformContext
): Promise <{ valid : boolean ; issues : string [] }> {
const issues : string [] = [];
for (const source of sourceIssues) {
if (!context.issueIdMap .has (source.key )) {
issues.push (`Missing: ${source.key} ` );
}
}
const sampleSize = Math .min (50 , sourceIssues.length );
const sample = sourceIssues.slice (0 , sampleSize);
for (const source of sample) {
const linearId = context.issueIdMap .get (source.key );
if (!linearId) continue ;
try {
const linearIssue = await context.linearClient .issue (linearId);
if (!linearIssue.title .includes (source.key )) {
issues.push (`Title mismatch: ${source.key} ` );
}
const expectedPriority = JIRA_PRIORITY_MAP [source.fields .priority ?.name ] || 0 ;
if (linearIssue.priority !== expectedPriority) {
issues.push (`Priority mismatch: ${source.key} (${linearIssue.priority} != ${expectedPriority} )` );
}
} catch (error) {
issues.push (`Verify failed: ${source.key} - ${error} ` );
}
}
return {
valid : issues.length === 0 ,
issues,
};
}
Step 5: Post-Migration
export async function createMigrationReport (
stats : ImportStats ,
context : TransformContext
): Promise <string > {
const report = `
# Migration Report
**Date:** ${new Date ().toISOString()}
**Source:** Jira
**Target:** Linear
## Statistics
- Total issues: ${stats.total}
- Successfully imported: ${stats.created}
- Skipped (duplicates): ${stats.skipped}
- Errors: ${stats.errors.length}
## ID Mapping
${Array .from (context.issueIdMap.entries())
.map(([source, linear]) => `- ${source} -> ${linear} ` )
.join("\n" )}
## Errors
${stats.errors.map(e => `- ${e.sourceId} : ${e.error} ` ).join("\n" ) || "None" }
## Next Steps
1. Verify critical issues manually
2. Update integrations to use Linear
3. Archive source project after parallel run
4. Train team on Linear workflows
` ;
await fs.writeFile ("migration-report.md" , report);
return report;
}
Migration Checklist ## Pre-Migration
[ ] Backup source system data
[ ] Create Linear workspace and teams
[ ] Set up workflow states and labels
[ ] Map users between systems
[ ] Create API credentials
## Migration
[ ] Export data from source
[ ] Transform to Linear format
[ ] Import in batches
[ ] Validate sample issues
[ ] Import attachments (if needed)
## Post-Migration
[ ] Run full validation
[ ] Set up redirects (if applicable)
[ ] Update integrations
[ ] Train team
[ ] Run parallel for 1-2 weeks
[ ] Archive source after cutover
Error Handling Error Cause Solution User not foundUnmapped user Add to user mapping Rate limitedToo fast import Add delays between requests State not foundUnmapped status Update state mapping Parent not foundImport order wrong Sort by hierarchy
Resources
Conclusion You have completed the Linear Flagship Skill Pack. You now have comprehensive knowledge of Linear integrations from basic setup through enterprise deployment.