[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F7WXB7DFEV1XWCSBTQZS7D":3,"skill-same:01M6F7WXB7DFEV1XWCSBTQZS7D":83},{"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":23,"publishedAt":51,"linkOnly":52,"markdownContent":53,"markdownBody":54,"frontMatter":55,"refName":60,"commitSha":61,"skillDirectory":62,"forksCount":63,"repositoryHtmlUrl":64,"repositoryHomepageUrl":6,"ownerHtmlUrl":65,"ownerBlogUrl":66,"ownerDescription":67,"updatedAt":68,"install":69,"resources":82},"01M6F7WXB7DFEV1XWCSBTQZS7D","linear-data-handling",null,"Data synchronization, backup, and consistency patterns for Linear.\nUse when implementing data sync, creating backups,\nor ensuring data consistency across systems.\nTrigger with phrases like \"linear data sync\", \"backup linear\",\n\"linear data consistency\", \"sync linear issues\", \"linear data export\".","Dicklesworthstone\u002Fpi_agent_rust","Dicklesworthstone","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F35050222?v=4","01M6CG26AR942CSVMN1M5E3DSE","Jeff Emanuel","01M6CG26EPP71PSHW1VDS71121",1986,"MIT","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Flinear-pack\u002Fskills\u002Flinear-data-handling\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50],"linear","data","linear data handling","data sync","when","data export","linear issues","implementing","consistency","with","trigger","io","systems","across","patterns","export","sync","use","edit","write","read","node","grep","synchronization","Bash","backup","issues","2026-11-10 10:12:30",false,"---\nname: linear-data-handling\ndescription: |\n  Data synchronization, backup, and consistency patterns for Linear.\n  Use when implementing data sync, creating backups,\n  or ensuring data consistency across systems.\n  Trigger with phrases like \"linear data sync\", \"backup linear\",\n  \"linear data consistency\", \"sync linear issues\", \"linear data export\".\nallowed-tools: Read, Write, Edit, Grep, Bash(node:*)\nversion: 1.0.0\nlicense: MIT\nauthor: Jeremy Longshore \u003Cjeremy@intentsolutions.io>\n---\n\n# Linear Data Handling\n\n## Overview\nImplement reliable data synchronization, backup, and consistency for Linear integrations.\n\n## Prerequisites\n- Linear API access\n- Database for local storage\n- Understanding of eventual consistency\n\n## Instructions\n\n### Step 1: Data Model Mapping\n```typescript\n\u002F\u002F models\u002Flinear-entities.ts\nimport { z } from \"zod\";\n\n\u002F\u002F Core entity schemas\nexport const LinearIssueSchema = z.object({\n  id: z.string(),\n  identifier: z.string(),\n  title: z.string(),\n  description: z.string().nullable(),\n  priority: z.number(),\n  estimate: z.number().nullable(),\n  stateId: z.string(),\n  stateName: z.string(),\n  teamId: z.string(),\n  teamKey: z.string(),\n  assigneeId: z.string().nullable(),\n  projectId: z.string().nullable(),\n  cycleId: z.string().nullable(),\n  createdAt: z.string(),\n  updatedAt: z.string(),\n  completedAt: z.string().nullable(),\n  canceledAt: z.string().nullable(),\n});\n\nexport type LinearIssue = z.infer\u003Ctypeof LinearIssueSchema>;\n\nexport const LinearProjectSchema = z.object({\n  id: z.string(),\n  name: z.string(),\n  description: z.string().nullable(),\n  state: z.string(),\n  progress: z.number(),\n  targetDate: z.string().nullable(),\n  createdAt: z.string(),\n  updatedAt: z.string(),\n});\n\nexport type LinearProject = z.infer\u003Ctypeof LinearProjectSchema>;\n```\n\n### Step 2: Full Sync Implementation\n```typescript\n\u002F\u002F sync\u002Ffull-sync.ts\nimport { LinearClient, Issue } from \"@linear\u002Fsdk\";\nimport { db } from \"..\u002Flib\u002Fdatabase\";\nimport { LinearIssueSchema } from \"..\u002Fmodels\u002Flinear-entities\";\n\ninterface SyncStats {\n  total: number;\n  created: number;\n  updated: number;\n  deleted: number;\n  errors: number;\n}\n\nexport async function fullSync(client: LinearClient): Promise\u003CSyncStats> {\n  const stats: SyncStats = { total: 0, created: 0, updated: 0, deleted: 0, errors: 0 };\n\n  console.log(\"Starting full sync...\");\n\n  \u002F\u002F Fetch all issues with pagination\n  const remoteIssues = new Map\u003Cstring, LinearIssue>();\n  let hasMore = true;\n  let cursor: string | undefined;\n\n  while (hasMore) {\n    const issues = await client.issues({\n      first: 100,\n      after: cursor,\n      includeArchived: false,\n    });\n\n    for (const issue of issues.nodes) {\n      const state = await issue.state;\n      const team = await issue.team;\n\n      const mapped: LinearIssue = {\n        id: issue.id,\n        identifier: issue.identifier,\n        title: issue.title,\n        description: issue.description,\n        priority: issue.priority,\n        estimate: issue.estimate,\n        stateId: state?.id ?? \"\",\n        stateName: state?.name ?? \"Unknown\",\n        teamId: team?.id ?? \"\",\n        teamKey: team?.key ?? \"\",\n        assigneeId: issue.assigneeId,\n        projectId: issue.projectId,\n        cycleId: issue.cycleId,\n        createdAt: issue.createdAt.toISOString(),\n        updatedAt: issue.updatedAt.toISOString(),\n        completedAt: issue.completedAt?.toISOString() ?? null,\n        canceledAt: issue.canceledAt?.toISOString() ?? null,\n      };\n\n      remoteIssues.set(issue.id, mapped);\n    }\n\n    hasMore = issues.pageInfo.hasNextPage;\n    cursor = issues.pageInfo.endCursor;\n\n    console.log(`  Fetched ${remoteIssues.size} issues...`);\n  }\n\n  stats.total = remoteIssues.size;\n\n  \u002F\u002F Get local issues\n  const localIssues = await db.select().from(issuesTable);\n  const localIssueMap = new Map(localIssues.map(i => [i.id, i]));\n\n  \u002F\u002F Process changes\n  await db.transaction(async (tx) => {\n    \u002F\u002F Upsert remote issues\n    for (const [id, issue] of remoteIssues) {\n      const existing = localIssueMap.get(id);\n\n      if (!existing) {\n        await tx.insert(issuesTable).values(issue);\n        stats.created++;\n      } else if (existing.updatedAt !== issue.updatedAt) {\n        await tx.update(issuesTable).set(issue).where(eq(issuesTable.id, id));\n        stats.updated++;\n      }\n    }\n\n    \u002F\u002F Mark deleted issues\n    for (const [id, local] of localIssueMap) {\n      if (!remoteIssues.has(id) && !local.deletedAt) {\n        await tx.update(issuesTable)\n          .set({ deletedAt: new Date().toISOString() })\n          .where(eq(issuesTable.id, id));\n        stats.deleted++;\n      }\n    }\n  });\n\n  console.log(\"Full sync complete:\", stats);\n  return stats;\n}\n```\n\n### Step 3: Incremental Sync with Webhooks\n```typescript\n\u002F\u002F sync\u002Fincremental-sync.ts\nimport { db } from \"..\u002Flib\u002Fdatabase\";\n\ninterface WebhookEvent {\n  action: \"create\" | \"update\" | \"remove\";\n  type: string;\n  data: Record\u003Cstring, unknown>;\n  createdAt: string;\n}\n\nexport async function processWebhookSync(event: WebhookEvent): Promise\u003Cvoid> {\n  const { action, type, data } = event;\n\n  if (type !== \"Issue\") return;\n\n  const issueData = data as any;\n\n  switch (action) {\n    case \"create\":\n      await db.insert(issuesTable).values({\n        id: issueData.id,\n        identifier: issueData.identifier,\n        title: issueData.title,\n        \u002F\u002F ... map all fields\n        syncedAt: new Date().toISOString(),\n      });\n      break;\n\n    case \"update\":\n      await db.update(issuesTable)\n        .set({\n          title: issueData.title,\n          \u002F\u002F ... update changed fields\n          syncedAt: new Date().toISOString(),\n        })\n        .where(eq(issuesTable.id, issueData.id));\n      break;\n\n    case \"remove\":\n      await db.update(issuesTable)\n        .set({ deletedAt: new Date().toISOString() })\n        .where(eq(issuesTable.id, issueData.id));\n      break;\n  }\n}\n```\n\n### Step 4: Data Export\u002FBackup\n```typescript\n\u002F\u002F backup\u002Fexport.ts\nimport { LinearClient } from \"@linear\u002Fsdk\";\nimport { createWriteStream } from \"fs\";\nimport { pipeline } from \"stream\u002Fpromises\";\n\ninterface BackupOptions {\n  includeComments?: boolean;\n  includeAttachments?: boolean;\n  format?: \"json\" | \"csv\";\n}\n\nexport async function createBackup(\n  client: LinearClient,\n  outputPath: string,\n  options: BackupOptions = {}\n): Promise\u003Cvoid> {\n  const { includeComments = true, format = \"json\" } = options;\n\n  const backup = {\n    exportedAt: new Date().toISOString(),\n    version: \"1.0\",\n    data: {\n      teams: [] as any[],\n      projects: [] as any[],\n      cycles: [] as any[],\n      issues: [] as any[],\n      comments: [] as any[],\n    },\n  };\n\n  console.log(\"Creating backup...\");\n\n  \u002F\u002F Export teams\n  const teams = await client.teams();\n  backup.data.teams = await Promise.all(\n    teams.nodes.map(async (team) => ({\n      id: team.id,\n      name: team.name,\n      key: team.key,\n      description: team.description,\n    }))\n  );\n  console.log(`  Exported ${backup.data.teams.length} teams`);\n\n  \u002F\u002F Export projects\n  const projects = await client.projects();\n  backup.data.projects = projects.nodes.map(p => ({\n    id: p.id,\n    name: p.name,\n    description: p.description,\n    state: p.state,\n    targetDate: p.targetDate,\n  }));\n  console.log(`  Exported ${backup.data.projects.length} projects`);\n\n  \u002F\u002F Export issues with pagination\n  let cursor: string | undefined;\n  let hasMore = true;\n\n  while (hasMore) {\n    const issues = await client.issues({\n      first: 100,\n      after: cursor,\n      includeArchived: true,\n    });\n\n    for (const issue of issues.nodes) {\n      const issueData: any = {\n        id: issue.id,\n        identifier: issue.identifier,\n        title: issue.title,\n        description: issue.description,\n        priority: issue.priority,\n        createdAt: issue.createdAt,\n        updatedAt: issue.updatedAt,\n      };\n\n      if (includeComments) {\n        const comments = await issue.comments();\n        issueData.comments = comments.nodes.map(c => ({\n          id: c.id,\n          body: c.body,\n          createdAt: c.createdAt,\n        }));\n      }\n\n      backup.data.issues.push(issueData);\n    }\n\n    hasMore = issues.pageInfo.hasNextPage;\n    cursor = issues.pageInfo.endCursor;\n  }\n  console.log(`  Exported ${backup.data.issues.length} issues`);\n\n  \u002F\u002F Write to file\n  const output = format === \"json\"\n    ? JSON.stringify(backup, null, 2)\n    : convertToCSV(backup);\n\n  await fs.writeFile(outputPath, output);\n  console.log(`Backup saved to ${outputPath}`);\n}\n```\n\n### Step 5: Data Consistency Checks\n```typescript\n\u002F\u002F sync\u002Fconsistency-check.ts\nimport { LinearClient } from \"@linear\u002Fsdk\";\nimport { db } from \"..\u002Flib\u002Fdatabase\";\n\ninterface ConsistencyReport {\n  timestamp: string;\n  issues: {\n    total: number;\n    missing: string[];\n    stale: string[];\n    orphaned: string[];\n  };\n}\n\nexport async function checkConsistency(client: LinearClient): Promise\u003CConsistencyReport> {\n  const report: ConsistencyReport = {\n    timestamp: new Date().toISOString(),\n    issues: {\n      total: 0,\n      missing: [],\n      stale: [],\n      orphaned: [],\n    },\n  };\n\n  \u002F\u002F Get sample of remote issues\n  const remoteIssues = await client.issues({ first: 100 });\n  report.issues.total = remoteIssues.nodes.length;\n\n  \u002F\u002F Check each remote issue exists locally\n  for (const remote of remoteIssues.nodes) {\n    const local = await db.query.issues.findFirst({\n      where: eq(issues.id, remote.id),\n    });\n\n    if (!local) {\n      report.issues.missing.push(remote.identifier);\n    } else if (new Date(local.updatedAt) \u003C remote.updatedAt) {\n      report.issues.stale.push(remote.identifier);\n    }\n  }\n\n  \u002F\u002F Check for orphaned local issues\n  const localIssues = await db.select({ id: issues.id, identifier: issues.identifier })\n    .from(issues)\n    .where(isNull(issues.deletedAt))\n    .limit(100);\n\n  for (const local of localIssues) {\n    try {\n      await client.issue(local.id);\n    } catch {\n      report.issues.orphaned.push(local.identifier);\n    }\n  }\n\n  return report;\n}\n\n\u002F\u002F Run periodically\nexport async function scheduleConsistencyChecks(): Promise\u003Cvoid> {\n  cron.schedule(\"0 0 * * *\", async () => {\n    const client = new LinearClient({ apiKey: process.env.LINEAR_API_KEY! });\n    const report = await checkConsistency(client);\n\n    if (report.issues.missing.length > 0 || report.issues.stale.length > 10) {\n      await alertOncall(\"Data consistency issues detected\", report);\n      await fullSync(client); \u002F\u002F Trigger resync\n    }\n  });\n}\n```\n\n### Step 6: Conflict Resolution\n```typescript\n\u002F\u002F sync\u002Fconflict-resolution.ts\ninterface ConflictStrategy {\n  strategy: \"remote-wins\" | \"local-wins\" | \"merge\" | \"manual\";\n  mergeFields?: string[];\n}\n\nexport async function resolveConflict(\n  local: LinearIssue,\n  remote: LinearIssue,\n  config: ConflictStrategy\n): Promise\u003CLinearIssue> {\n  switch (config.strategy) {\n    case \"remote-wins\":\n      return remote;\n\n    case \"local-wins\":\n      return local;\n\n    case \"merge\":\n      \u002F\u002F Merge specific fields\n      const merged = { ...remote };\n      for (const field of config.mergeFields ?? []) {\n        if (local[field as keyof LinearIssue] !== undefined) {\n          (merged as any)[field] = local[field as keyof LinearIssue];\n        }\n      }\n      return merged;\n\n    case \"manual\":\n      throw new ConflictError(local, remote);\n\n    default:\n      return remote;\n  }\n}\n```\n\n## Error Handling\n| Error | Cause | Solution |\n|-------|-------|----------|\n| `Sync timeout` | Too many records | Use smaller batches |\n| `Conflict detected` | Concurrent edits | Apply conflict resolution |\n| `Stale data` | Missed webhooks | Trigger full sync |\n| `Export failed` | API rate limit | Add delays between requests |\n\n## Resources\n- [Linear GraphQL API](https:\u002F\u002Fdevelopers.linear.app\u002Fdocs\u002Fgraphql\u002Fworking-with-the-graphql-api)\n- [Data Sync Patterns](https:\u002F\u002Fmartinfowler.com\u002Farticles\u002Fpatterns-of-distributed-systems\u002F)\n- [Eventual Consistency](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FEventual_consistency)\n\n## Next Steps\nImplement enterprise RBAC with `linear-enterprise-rbac`.\n","# Linear Data Handling\n\n## Overview\nImplement reliable data synchronization, backup, and consistency for Linear integrations.\n\n## Prerequisites\n- Linear API access\n- Database for local storage\n- Understanding of eventual consistency\n\n## Instructions\n\n### Step 1: Data Model Mapping\n```typescript\n\u002F\u002F models\u002Flinear-entities.ts\nimport { z } from \"zod\";\n\n\u002F\u002F Core entity schemas\nexport const LinearIssueSchema = z.object({\n  id: z.string(),\n  identifier: z.string(),\n  title: z.string(),\n  description: z.string().nullable(),\n  priority: z.number(),\n  estimate: z.number().nullable(),\n  stateId: z.string(),\n  stateName: z.string(),\n  teamId: z.string(),\n  teamKey: z.string(),\n  assigneeId: z.string().nullable(),\n  projectId: z.string().nullable(),\n  cycleId: z.string().nullable(),\n  createdAt: z.string(),\n  updatedAt: z.string(),\n  completedAt: z.string().nullable(),\n  canceledAt: z.string().nullable(),\n});\n\nexport type LinearIssue = z.infer\u003Ctypeof LinearIssueSchema>;\n\nexport const LinearProjectSchema = z.object({\n  id: z.string(),\n  name: z.string(),\n  description: z.string().nullable(),\n  state: z.string(),\n  progress: z.number(),\n  targetDate: z.string().nullable(),\n  createdAt: z.string(),\n  updatedAt: z.string(),\n});\n\nexport type LinearProject = z.infer\u003Ctypeof LinearProjectSchema>;\n```\n\n### Step 2: Full Sync Implementation\n```typescript\n\u002F\u002F sync\u002Ffull-sync.ts\nimport { LinearClient, Issue } from \"@linear\u002Fsdk\";\nimport { db } from \"..\u002Flib\u002Fdatabase\";\nimport { LinearIssueSchema } from \"..\u002Fmodels\u002Flinear-entities\";\n\ninterface SyncStats {\n  total: number;\n  created: number;\n  updated: number;\n  deleted: number;\n  errors: number;\n}\n\nexport async function fullSync(client: LinearClient): Promise\u003CSyncStats> {\n  const stats: SyncStats = { total: 0, created: 0, updated: 0, deleted: 0, errors: 0 };\n\n  console.log(\"Starting full sync...\");\n\n  \u002F\u002F Fetch all issues with pagination\n  const remoteIssues = new Map\u003Cstring, LinearIssue>();\n  let hasMore = true;\n  let cursor: string | undefined;\n\n  while (hasMore) {\n    const issues = await client.issues({\n      first: 100,\n      after: cursor,\n      includeArchived: false,\n    });\n\n    for (const issue of issues.nodes) {\n      const state = await issue.state;\n      const team = await issue.team;\n\n      const mapped: LinearIssue = {\n        id: issue.id,\n        identifier: issue.identifier,\n        title: issue.title,\n        description: issue.description,\n        priority: issue.priority,\n        estimate: issue.estimate,\n        stateId: state?.id ?? \"\",\n        stateName: state?.name ?? \"Unknown\",\n        teamId: team?.id ?? \"\",\n        teamKey: team?.key ?? \"\",\n        assigneeId: issue.assigneeId,\n        projectId: issue.projectId,\n        cycleId: issue.cycleId,\n        createdAt: issue.createdAt.toISOString(),\n        updatedAt: issue.updatedAt.toISOString(),\n        completedAt: issue.completedAt?.toISOString() ?? null,\n        canceledAt: issue.canceledAt?.toISOString() ?? null,\n      };\n\n      remoteIssues.set(issue.id, mapped);\n    }\n\n    hasMore = issues.pageInfo.hasNextPage;\n    cursor = issues.pageInfo.endCursor;\n\n    console.log(`  Fetched ${remoteIssues.size} issues...`);\n  }\n\n  stats.total = remoteIssues.size;\n\n  \u002F\u002F Get local issues\n  const localIssues = await db.select().from(issuesTable);\n  const localIssueMap = new Map(localIssues.map(i => [i.id, i]));\n\n  \u002F\u002F Process changes\n  await db.transaction(async (tx) => {\n    \u002F\u002F Upsert remote issues\n    for (const [id, issue] of remoteIssues) {\n      const existing = localIssueMap.get(id);\n\n      if (!existing) {\n        await tx.insert(issuesTable).values(issue);\n        stats.created++;\n      } else if (existing.updatedAt !== issue.updatedAt) {\n        await tx.update(issuesTable).set(issue).where(eq(issuesTable.id, id));\n        stats.updated++;\n      }\n    }\n\n    \u002F\u002F Mark deleted issues\n    for (const [id, local] of localIssueMap) {\n      if (!remoteIssues.has(id) && !local.deletedAt) {\n        await tx.update(issuesTable)\n          .set({ deletedAt: new Date().toISOString() })\n          .where(eq(issuesTable.id, id));\n        stats.deleted++;\n      }\n    }\n  });\n\n  console.log(\"Full sync complete:\", stats);\n  return stats;\n}\n```\n\n### Step 3: Incremental Sync with Webhooks\n```typescript\n\u002F\u002F sync\u002Fincremental-sync.ts\nimport { db } from \"..\u002Flib\u002Fdatabase\";\n\ninterface WebhookEvent {\n  action: \"create\" | \"update\" | \"remove\";\n  type: string;\n  data: Record\u003Cstring, unknown>;\n  createdAt: string;\n}\n\nexport async function processWebhookSync(event: WebhookEvent): Promise\u003Cvoid> {\n  const { action, type, data } = event;\n\n  if (type !== \"Issue\") return;\n\n  const issueData = data as any;\n\n  switch (action) {\n    case \"create\":\n      await db.insert(issuesTable).values({\n        id: issueData.id,\n        identifier: issueData.identifier,\n        title: issueData.title,\n        \u002F\u002F ... map all fields\n        syncedAt: new Date().toISOString(),\n      });\n      break;\n\n    case \"update\":\n      await db.update(issuesTable)\n        .set({\n          title: issueData.title,\n          \u002F\u002F ... update changed fields\n          syncedAt: new Date().toISOString(),\n        })\n        .where(eq(issuesTable.id, issueData.id));\n      break;\n\n    case \"remove\":\n      await db.update(issuesTable)\n        .set({ deletedAt: new Date().toISOString() })\n        .where(eq(issuesTable.id, issueData.id));\n      break;\n  }\n}\n```\n\n### Step 4: Data Export\u002FBackup\n```typescript\n\u002F\u002F backup\u002Fexport.ts\nimport { LinearClient } from \"@linear\u002Fsdk\";\nimport { createWriteStream } from \"fs\";\nimport { pipeline } from \"stream\u002Fpromises\";\n\ninterface BackupOptions {\n  includeComments?: boolean;\n  includeAttachments?: boolean;\n  format?: \"json\" | \"csv\";\n}\n\nexport async function createBackup(\n  client: LinearClient,\n  outputPath: string,\n  options: BackupOptions = {}\n): Promise\u003Cvoid> {\n  const { includeComments = true, format = \"json\" } = options;\n\n  const backup = {\n    exportedAt: new Date().toISOString(),\n    version: \"1.0\",\n    data: {\n      teams: [] as any[],\n      projects: [] as any[],\n      cycles: [] as any[],\n      issues: [] as any[],\n      comments: [] as any[],\n    },\n  };\n\n  console.log(\"Creating backup...\");\n\n  \u002F\u002F Export teams\n  const teams = await client.teams();\n  backup.data.teams = await Promise.all(\n    teams.nodes.map(async (team) => ({\n      id: team.id,\n      name: team.name,\n      key: team.key,\n      description: team.description,\n    }))\n  );\n  console.log(`  Exported ${backup.data.teams.length} teams`);\n\n  \u002F\u002F Export projects\n  const projects = await client.projects();\n  backup.data.projects = projects.nodes.map(p => ({\n    id: p.id,\n    name: p.name,\n    description: p.description,\n    state: p.state,\n    targetDate: p.targetDate,\n  }));\n  console.log(`  Exported ${backup.data.projects.length} projects`);\n\n  \u002F\u002F Export issues with pagination\n  let cursor: string | undefined;\n  let hasMore = true;\n\n  while (hasMore) {\n    const issues = await client.issues({\n      first: 100,\n      after: cursor,\n      includeArchived: true,\n    });\n\n    for (const issue of issues.nodes) {\n      const issueData: any = {\n        id: issue.id,\n        identifier: issue.identifier,\n        title: issue.title,\n        description: issue.description,\n        priority: issue.priority,\n        createdAt: issue.createdAt,\n        updatedAt: issue.updatedAt,\n      };\n\n      if (includeComments) {\n        const comments = await issue.comments();\n        issueData.comments = comments.nodes.map(c => ({\n          id: c.id,\n          body: c.body,\n          createdAt: c.createdAt,\n        }));\n      }\n\n      backup.data.issues.push(issueData);\n    }\n\n    hasMore = issues.pageInfo.hasNextPage;\n    cursor = issues.pageInfo.endCursor;\n  }\n  console.log(`  Exported ${backup.data.issues.length} issues`);\n\n  \u002F\u002F Write to file\n  const output = format === \"json\"\n    ? JSON.stringify(backup, null, 2)\n    : convertToCSV(backup);\n\n  await fs.writeFile(outputPath, output);\n  console.log(`Backup saved to ${outputPath}`);\n}\n```\n\n### Step 5: Data Consistency Checks\n```typescript\n\u002F\u002F sync\u002Fconsistency-check.ts\nimport { LinearClient } from \"@linear\u002Fsdk\";\nimport { db } from \"..\u002Flib\u002Fdatabase\";\n\ninterface ConsistencyReport {\n  timestamp: string;\n  issues: {\n    total: number;\n    missing: string[];\n    stale: string[];\n    orphaned: string[];\n  };\n}\n\nexport async function checkConsistency(client: LinearClient): Promise\u003CConsistencyReport> {\n  const report: ConsistencyReport = {\n    timestamp: new Date().toISOString(),\n    issues: {\n      total: 0,\n      missing: [],\n      stale: [],\n      orphaned: [],\n    },\n  };\n\n  \u002F\u002F Get sample of remote issues\n  const remoteIssues = await client.issues({ first: 100 });\n  report.issues.total = remoteIssues.nodes.length;\n\n  \u002F\u002F Check each remote issue exists locally\n  for (const remote of remoteIssues.nodes) {\n    const local = await db.query.issues.findFirst({\n      where: eq(issues.id, remote.id),\n    });\n\n    if (!local) {\n      report.issues.missing.push(remote.identifier);\n    } else if (new Date(local.updatedAt) \u003C remote.updatedAt) {\n      report.issues.stale.push(remote.identifier);\n    }\n  }\n\n  \u002F\u002F Check for orphaned local issues\n  const localIssues = await db.select({ id: issues.id, identifier: issues.identifier })\n    .from(issues)\n    .where(isNull(issues.deletedAt))\n    .limit(100);\n\n  for (const local of localIssues) {\n    try {\n      await client.issue(local.id);\n    } catch {\n      report.issues.orphaned.push(local.identifier);\n    }\n  }\n\n  return report;\n}\n\n\u002F\u002F Run periodically\nexport async function scheduleConsistencyChecks(): Promise\u003Cvoid> {\n  cron.schedule(\"0 0 * * *\", async () => {\n    const client = new LinearClient({ apiKey: process.env.LINEAR_API_KEY! });\n    const report = await checkConsistency(client);\n\n    if (report.issues.missing.length > 0 || report.issues.stale.length > 10) {\n      await alertOncall(\"Data consistency issues detected\", report);\n      await fullSync(client); \u002F\u002F Trigger resync\n    }\n  });\n}\n```\n\n### Step 6: Conflict Resolution\n```typescript\n\u002F\u002F sync\u002Fconflict-resolution.ts\ninterface ConflictStrategy {\n  strategy: \"remote-wins\" | \"local-wins\" | \"merge\" | \"manual\";\n  mergeFields?: string[];\n}\n\nexport async function resolveConflict(\n  local: LinearIssue,\n  remote: LinearIssue,\n  config: ConflictStrategy\n): Promise\u003CLinearIssue> {\n  switch (config.strategy) {\n    case \"remote-wins\":\n      return remote;\n\n    case \"local-wins\":\n      return local;\n\n    case \"merge\":\n      \u002F\u002F Merge specific fields\n      const merged = { ...remote };\n      for (const field of config.mergeFields ?? []) {\n        if (local[field as keyof LinearIssue] !== undefined) {\n          (merged as any)[field] = local[field as keyof LinearIssue];\n        }\n      }\n      return merged;\n\n    case \"manual\":\n      throw new ConflictError(local, remote);\n\n    default:\n      return remote;\n  }\n}\n```\n\n## Error Handling\n| Error | Cause | Solution |\n|-------|-------|----------|\n| `Sync timeout` | Too many records | Use smaller batches |\n| `Conflict detected` | Concurrent edits | Apply conflict resolution |\n| `Stale data` | Missed webhooks | Trigger full sync |\n| `Export failed` | API rate limit | Add delays between requests |\n\n## Resources\n- [Linear GraphQL API](https:\u002F\u002Fdevelopers.linear.app\u002Fdocs\u002Fgraphql\u002Fworking-with-the-graphql-api)\n- [Data Sync Patterns](https:\u002F\u002Fmartinfowler.com\u002Farticles\u002Fpatterns-of-distributed-systems\u002F)\n- [Eventual Consistency](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FEventual_consistency)\n\n## Next Steps\nImplement enterprise RBAC with `linear-enterprise-rbac`.\n",{"name":5,"author":56,"license":15,"version":57,"description":58,"allowed-tools":59},"Jeremy Longshore \u003Cjeremy@intentsolutions.io>","1.0.0","Data synchronization, backup, and consistency patterns for Linear.\nUse when implementing data sync, creating backups,\nor ensuring data consistency across systems.\nTrigger with phrases like \"linear data sync\", \"backup linear\",\n\"linear data consistency\", \"sync linear issues\", \"linear data export\".\n","Read, Write, Edit, Grep, Bash(node:*)","main","90b50ed0778745a80fd5bfb13b1843d45b9e82f7","tests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Flinear-pack\u002Fskills\u002Flinear-data-handling",233,"https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust","https:\u002F\u002Fgithub.com\u002FDicklesworthstone","https:\u002F\u002Fwww.jeffreyemanuel.com\u002F","Building in NY","2026-11-13 03:33:43",{"source":8,"skillName":5,"commands":70,"prompt":80,"zipAvailable":81},[71,74,77],{"tool":72,"command":73},"npx","npx skills add Dicklesworthstone\u002Fpi_agent_rust --skill linear-data-handling",{"tool":75,"command":76},"bunx","bunx skills add Dicklesworthstone\u002Fpi_agent_rust --skill linear-data-handling",{"tool":78,"command":79},"pnpm","pnpm dlx skills add Dicklesworthstone\u002Fpi_agent_rust --skill linear-data-handling","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Flinear-pack\u002Fskills\u002Flinear-data-handling\u002FSKILL.md",true,[],[84,124,148,173,200,222],{"skillNo":85,"name":86,"nameZh":6,"description":87,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":88,"licenseFileUrl":89,"sourceHtmlUrl":90,"categories":91,"keywords":97,"publishedAt":51},"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",[92],{"categoryNo":93,"categoryName":94,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",[98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,41,116,117,118,119,120,121,122,123],"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":125,"name":126,"nameZh":6,"description":127,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":88,"licenseFileUrl":128,"sourceHtmlUrl":129,"categories":130,"keywords":135,"publishedAt":51},"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",[131,134],{"categoryNo":132,"categoryName":133,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":93,"categoryName":94,"taxonomyType":95,"taxonomyTypeDesc":96},[136,137,138,103,139,140,99,141,142,143,113,144,145,146,147,33,121],"testing","webapp testing","webapp","screenshots","playwright","debugging","toolkit","browser","logs","applications","behavior","local",{"skillNo":149,"name":150,"nameZh":6,"description":151,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":152,"sourceHtmlUrl":153,"categories":154,"keywords":155,"publishedAt":51},"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",[],[156,157,158,159,25,160,161,162,41,163,164,165,166,167,168,169,33,170,171,172,121,28],"analysis","neuropixels analysis","spike","metrics","open","quality","user","recordings","Neural","motion","visual","quality metrics","unit","sorting","curation","recording","load",{"skillNo":174,"name":175,"nameZh":6,"description":176,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":88,"licenseFileUrl":177,"sourceHtmlUrl":178,"categories":179,"keywords":183,"publishedAt":51},"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",[180],{"categoryNo":181,"categoryName":182,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000023","Skill与智能体开发",[184,185,186,187,188,189,190,191,192,193,194,195,196,197,33,121,198,28,199],"skill creator","creator","workflows","create","update","capabilities","knowledge","tool","integrations","specialized","guide","new skill","existing skill","users","Existing","new",{"skillNo":201,"name":202,"nameZh":6,"description":203,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":204,"sourceHtmlUrl":205,"categories":206,"keywords":208,"publishedAt":51},"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",[207],{"categoryNo":181,"categoryName":182,"taxonomyType":95,"taxonomyTypeDesc":96},[202,209,210,211,212,213,214,143,215,216,41,217,218,219,220,121,221],"google","browser automation","gemini","automation","auth","code","library","management","only","document","query","Citation","source",{"skillNo":223,"name":224,"nameZh":6,"description":225,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":88,"licenseFileUrl":226,"sourceHtmlUrl":227,"categories":228,"keywords":235,"publishedAt":51},"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",[229,232],{"categoryNo":230,"categoryName":231,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000005","邮件与沟通",{"categoryNo":233,"categoryName":234,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000016","图像与多媒体",[236,237,238,185,239,114,240,241,190,41,242,197,243,244,245,121,246,28],"slack gif creator","slack","gif","validation","constraints","Animation","me","make","Utilities","concepts","request"]