@stackmemoryai/stackmemory 0.5.29 → 0.5.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -32
- package/dist/core/database/batch-operations.js +29 -4
- package/dist/core/database/batch-operations.js.map +2 -2
- package/dist/core/database/connection-pool.js +13 -2
- package/dist/core/database/connection-pool.js.map +2 -2
- package/dist/core/database/migration-manager.js +130 -34
- package/dist/core/database/migration-manager.js.map +2 -2
- package/dist/core/database/paradedb-adapter.js +23 -7
- package/dist/core/database/paradedb-adapter.js.map +2 -2
- package/dist/core/database/query-router.js +8 -3
- package/dist/core/database/query-router.js.map +2 -2
- package/dist/core/database/sqlite-adapter.js +152 -33
- package/dist/core/database/sqlite-adapter.js.map +2 -2
- package/dist/integrations/linear/auth.js +34 -20
- package/dist/integrations/linear/auth.js.map +2 -2
- package/dist/integrations/linear/auto-sync.js +18 -8
- package/dist/integrations/linear/auto-sync.js.map +2 -2
- package/dist/integrations/linear/client.js +42 -9
- package/dist/integrations/linear/client.js.map +2 -2
- package/dist/integrations/linear/migration.js +94 -36
- package/dist/integrations/linear/migration.js.map +2 -2
- package/dist/integrations/linear/oauth-server.js +77 -34
- package/dist/integrations/linear/oauth-server.js.map +2 -2
- package/dist/integrations/linear/rest-client.js +13 -3
- package/dist/integrations/linear/rest-client.js.map +2 -2
- package/dist/integrations/linear/sync-service.js +18 -15
- package/dist/integrations/linear/sync-service.js.map +2 -2
- package/dist/integrations/linear/sync.js +12 -4
- package/dist/integrations/linear/sync.js.map +2 -2
- package/dist/integrations/linear/unified-sync.js +33 -8
- package/dist/integrations/linear/unified-sync.js.map +2 -2
- package/dist/integrations/linear/webhook-handler.js +5 -1
- package/dist/integrations/linear/webhook-handler.js.map +2 -2
- package/dist/integrations/linear/webhook-server.js +7 -7
- package/dist/integrations/linear/webhook-server.js.map +2 -2
- package/dist/integrations/linear/webhook.js +9 -2
- package/dist/integrations/linear/webhook.js.map +2 -2
- package/dist/integrations/mcp/schemas.js +147 -0
- package/dist/integrations/mcp/schemas.js.map +7 -0
- package/dist/integrations/mcp/server.js +19 -3
- package/dist/integrations/mcp/server.js.map +2 -2
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/integrations/linear/sync.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Linear Bi-directional Sync Engine\n * Handles syncing tasks between StackMemory and Linear\n */\n\nimport { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport { logger } from '../../core/monitoring/logger.js';\nimport {\n PebblesTask,\n LinearTaskManager,\n TaskStatus,\n TaskPriority,\n} from '../../features/tasks/linear-task-manager.js';\nimport { LinearClient, LinearIssue, LinearCreateIssueInput } from './client.js';\nimport { LinearAuthManager } from './auth.js';\nimport { getEnv, getOptionalEnv } from '../../utils/env.js';\n\n\nexport interface SyncConfig {\n enabled: boolean;\n direction: 'bidirectional' | 'to_linear' | 'from_linear';\n defaultTeamId?: string;\n autoSync: boolean;\n conflictResolution:\n | 'linear_wins'\n | 'stackmemory_wins'\n | 'manual'\n | 'newest_wins';\n syncInterval?: number; // minutes\n maxBatchSize?: number; // max tasks to sync per batch\n rateLimitDelay?: number; // ms delay between API calls\n}\n\nexport interface SyncResult {\n success: boolean;\n synced: {\n toLinear: number;\n fromLinear: number;\n updated: number;\n };\n conflicts: Array<{\n taskId: string;\n linearId: string;\n reason: string;\n }>;\n errors: string[];\n}\n\nexport interface TaskMapping {\n stackmemoryId: string;\n linearId: string;\n linearIdentifier: string;\n lastSyncTimestamp: number;\n lastLinearUpdate: string;\n lastStackMemoryUpdate: number;\n}\n\nexport class LinearSyncEngine {\n private taskStore: LinearTaskManager;\n private linearClient: LinearClient;\n private authManager: LinearAuthManager;\n private config: SyncConfig;\n private mappings: Map<string, TaskMapping> = new Map();\n private projectRoot: string;\n private mappingsPath: string;\n\n constructor(\n taskStore: LinearTaskManager,\n authManager: LinearAuthManager,\n config: SyncConfig,\n projectRoot?: string\n ) {\n this.taskStore = taskStore;\n this.authManager = authManager;\n this.config = config;\n this.projectRoot = projectRoot || process.cwd();\n this.mappingsPath = join(\n this.projectRoot,\n '.stackmemory',\n 'linear-mappings.json'\n );\n\n // Check for API key from environment variable first\n const apiKey = process.env['LINEAR_API_KEY'];\n\n if (apiKey) {\n // Use API key from environment\n this.linearClient = new LinearClient({\n apiKey: apiKey,\n });\n } else {\n // Fall back to OAuth tokens\n const tokens = this.authManager.loadTokens();\n if (!tokens) {\n throw new Error(\n 'Linear API key or authentication tokens not found. Set LINEAR_API_KEY environment variable or run \"stackmemory linear setup\" first.'\n );\n }\n\n this.linearClient = new LinearClient({\n apiKey: tokens.accessToken,\n useBearer: true,\n onUnauthorized: async () => {\n const refreshed = await this.authManager.refreshAccessToken();\n return refreshed.accessToken;\n },\n });\n }\n\n this.loadMappings();\n }\n\n /**\n * Update sync configuration\n */\n updateConfig(newConfig: Partial<SyncConfig>): void {\n this.config = { ...this.config, ...newConfig };\n }\n\n /**\n * Perform bi-directional sync\n */\n async sync(): Promise<SyncResult> {\n if (!this.config.enabled) {\n return {\n success: false,\n synced: { toLinear: 0, fromLinear: 0, updated: 0 },\n conflicts: [],\n errors: ['Sync is disabled'],\n };\n }\n\n const result: SyncResult = {\n success: true,\n synced: { toLinear: 0, fromLinear: 0, updated: 0 },\n conflicts: [],\n errors: [],\n };\n\n try {\n // Update client with valid token if not using environment API key\n const apiKey = process.env['LINEAR_API_KEY'];\n if (!apiKey) {\n const token = await this.authManager.getValidToken();\n this.linearClient = new LinearClient({\n apiKey: token,\n useBearer: true,\n onUnauthorized: async () => {\n const refreshed = await this.authManager.refreshAccessToken();\n return refreshed.accessToken;\n },\n });\n }\n\n // Get team info if not configured\n if (!this.config.defaultTeamId) {\n const team = await this.linearClient.getTeam();\n this.config.defaultTeamId = team.id;\n logger.info(`Using Linear team: ${team.name} (${team.key})`);\n }\n\n // Sync in both directions based on configuration\n if (\n this.config.direction === 'bidirectional' ||\n this.config.direction === 'to_linear'\n ) {\n const toLinearResult = await this.syncToLinear();\n result.synced.toLinear = toLinearResult.created;\n result.synced.updated += toLinearResult.updated;\n result.errors.push(...toLinearResult.errors);\n }\n\n if (\n this.config.direction === 'bidirectional' ||\n this.config.direction === 'from_linear'\n ) {\n const fromLinearResult = await this.syncFromLinear();\n result.synced.fromLinear = fromLinearResult.created;\n result.synced.updated += fromLinearResult.updated;\n result.conflicts.push(...fromLinearResult.conflicts);\n result.errors.push(...fromLinearResult.errors);\n }\n\n this.saveMappings();\n } catch (error: unknown) {\n result.success = false;\n result.errors.push(`Sync failed: ${String(error)}`);\n logger.error('Linear sync failed:', error as Error);\n }\n\n return result;\n }\n\n /**\n * Delay helper for rate limiting\n */\n private async delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Sync tasks from StackMemory to Linear\n */\n private async syncToLinear(): Promise<{\n created: number;\n updated: number;\n errors: string[];\n }> {\n const result = { created: 0, updated: 0, errors: [] as string[] };\n const maxBatchSize = this.config.maxBatchSize || 10;\n const rateLimitDelay = this.config.rateLimitDelay || 500;\n\n // Initialize duplicate detector\n const duplicateDetector = new LinearDuplicateDetector(this.linearClient);\n\n // Get unsynced tasks from StackMemory\n const unsyncedTasks = this.getUnsyncedTasks();\n\n // Limit batch size to avoid rate limits\n const tasksToSync = unsyncedTasks.slice(0, maxBatchSize);\n\n if (unsyncedTasks.length > maxBatchSize) {\n logger.info(\n `Syncing ${tasksToSync.length} of ${unsyncedTasks.length} unsynced tasks (batch limit)`\n );\n }\n\n for (const task of tasksToSync) {\n try {\n // Check for duplicates before creating\n const duplicateCheck = await duplicateDetector.checkForDuplicate(\n task.title,\n this.config.defaultTeamId\n );\n\n let linearIssue: LinearIssue;\n \n if (duplicateCheck.isDuplicate && duplicateCheck.existingIssue) {\n // Found duplicate - merge instead of creating\n logger.info(\n `Found existing Linear issue for \"${task.title}\": ${duplicateCheck.existingIssue.identifier} (${Math.round((duplicateCheck.similarity || 0) * 100)}% match)`\n );\n \n // Merge task content into existing issue\n linearIssue = await duplicateDetector.mergeIntoExisting(\n duplicateCheck.existingIssue,\n task.title,\n this.formatDescriptionForLinear(task),\n `StackMemory Task ID: ${task.id}\\nFrame: ${task.frame_id}`\n );\n } else {\n // No duplicate found, create new issue\n linearIssue = await this.createLinearIssueFromTask(task);\n }\n\n // Create mapping\n const mapping: TaskMapping = {\n stackmemoryId: task.id,\n linearId: linearIssue.id,\n linearIdentifier: linearIssue.identifier,\n lastSyncTimestamp: Date.now(),\n lastLinearUpdate: linearIssue.updatedAt,\n lastStackMemoryUpdate: task.timestamp * 1000,\n };\n\n this.mappings.set(task.id, mapping);\n\n // Update task with Linear reference\n this.updateTaskWithLinearRef(task.id, linearIssue);\n\n result.created++;\n logger.info(\n `Synced task to Linear: ${task.title} \u2192 ${linearIssue.identifier}`\n );\n\n // Rate limit delay between creates\n await this.delay(rateLimitDelay);\n } catch (error: unknown) {\n const errorMsg = String(error);\n // Stop syncing on rate limit errors\n if (\n errorMsg.includes('rate limit') ||\n errorMsg.includes('usage limit')\n ) {\n logger.warn('Rate limit hit, stopping sync batch');\n result.errors.push('Rate limit reached - sync paused');\n break;\n }\n result.errors.push(`Failed to sync task ${task.id}: ${errorMsg}`);\n logger.error(\n `Failed to sync task ${task.id} to Linear:`,\n error as Error\n );\n }\n }\n\n // Update existing Linear issues for modified StackMemory tasks\n const modifiedTasks = this.getModifiedTasks();\n\n for (const task of modifiedTasks) {\n try {\n const mapping = this.mappings.get(task.id);\n if (!mapping) continue;\n\n await this.updateLinearIssueFromTask(task, mapping);\n\n mapping.lastSyncTimestamp = Date.now();\n mapping.lastStackMemoryUpdate = task.timestamp * 1000;\n\n result.updated++;\n logger.info(`Updated Linear issue: ${mapping.linearIdentifier}`);\n } catch (error: unknown) {\n result.errors.push(\n `Failed to update Linear issue for task ${task.id}: ${String(error)}`\n );\n logger.error(\n `Failed to update Linear issue for task ${task.id}:`,\n error as Error\n );\n }\n }\n\n return result;\n }\n\n /**\n * Sync tasks from Linear to StackMemory\n */\n private async syncFromLinear(): Promise<{\n created: number;\n updated: number;\n conflicts: Array<{ taskId: string; linearId: string; reason: string }>;\n errors: string[];\n }> {\n const result = {\n created: 0,\n updated: 0,\n conflicts: [] as Array<{\n taskId: string;\n linearId: string;\n reason: string;\n }>,\n errors: [] as string[],\n };\n\n // First, import any new issues from Linear that aren't mapped yet\n const importResult = await this.importFromLinear();\n result.created = importResult.imported;\n result.errors.push(...importResult.errors);\n\n // Then update existing mapped tasks\n for (const [taskId, mapping] of this.mappings) {\n try {\n const linearIssue = await this.linearClient.getIssue(mapping.linearId);\n\n if (!linearIssue) {\n result.errors.push(`Linear issue ${mapping.linearId} not found`);\n continue;\n }\n\n // Check if Linear issue was updated since last sync\n const linearUpdateTime = new Date(linearIssue.updatedAt).getTime();\n if (linearUpdateTime <= mapping.lastSyncTimestamp) {\n continue; // No changes in Linear\n }\n\n // Check for conflicts\n const task = this.taskStore.getTask(taskId);\n if (!task) {\n result.errors.push(`StackMemory task ${taskId} not found`);\n continue;\n }\n\n const stackMemoryUpdateTime = task.timestamp * 1000;\n\n if (\n stackMemoryUpdateTime > mapping.lastSyncTimestamp &&\n linearUpdateTime > mapping.lastSyncTimestamp\n ) {\n // Conflict: both sides updated since last sync\n result.conflicts.push({\n taskId,\n linearId: mapping.linearId,\n reason: 'Both StackMemory and Linear were updated since last sync',\n });\n\n if (this.config.conflictResolution === 'manual') {\n continue; // Skip, let user resolve manually\n }\n }\n\n // Apply conflict resolution\n const shouldUpdateFromLinear = this.shouldUpdateFromLinear(\n task,\n linearIssue,\n mapping,\n stackMemoryUpdateTime,\n linearUpdateTime\n );\n\n if (shouldUpdateFromLinear) {\n this.updateTaskFromLinearIssue(task, linearIssue);\n\n mapping.lastSyncTimestamp = Date.now();\n mapping.lastLinearUpdate = linearIssue.updatedAt;\n\n result.updated++;\n logger.info(`Updated StackMemory task from Linear: ${task.title}`);\n }\n } catch (error: unknown) {\n result.errors.push(\n `Failed to sync from Linear for task ${taskId}: ${String(error)}`\n );\n logger.error(\n `Failed to sync from Linear for task ${taskId}:`,\n error as Error\n );\n }\n }\n\n return result;\n }\n\n /**\n * Create Linear issue from StackMemory task\n */\n private async createLinearIssueFromTask(\n task: PebblesTask\n ): Promise<LinearIssue> {\n const input: LinearCreateIssueInput = {\n title: task.title,\n description: this.formatDescriptionForLinear(task),\n teamId: this.config.defaultTeamId!,\n priority: this.mapPriorityToLinear(task.priority),\n estimate: task.estimated_effort\n ? Math.ceil(task.estimated_effort / 60)\n : undefined, // Convert minutes to hours\n labelIds: this.mapTagsToLinear(task.tags),\n };\n\n return await this.linearClient.createIssue(input);\n }\n\n /**\n * Update Linear issue from StackMemory task\n */\n private async updateLinearIssueFromTask(\n task: PebblesTask,\n mapping: TaskMapping\n ): Promise<void> {\n const updates: Partial<LinearCreateIssueInput> & { stateId?: string } = {\n title: task.title,\n description: this.formatDescriptionForLinear(task),\n priority: this.mapPriorityToLinear(task.priority),\n estimate: task.estimated_effort\n ? Math.ceil(task.estimated_effort / 60)\n : undefined,\n stateId: await this.mapStatusToLinearState(task.status),\n };\n\n await this.linearClient.updateIssue(mapping.linearId, updates);\n }\n\n /**\n * Update StackMemory task from Linear issue\n */\n private updateTaskFromLinearIssue(\n task: PebblesTask,\n linearIssue: LinearIssue\n ): void {\n // Map Linear state to StackMemory status\n const newStatus = this.mapLinearStateToStatus(linearIssue.state.type);\n\n if (newStatus !== task.status) {\n this.taskStore.updateTaskStatus(\n task.id,\n newStatus,\n 'Updated from Linear'\n );\n }\n\n // Note: Other fields like title, description could be updated here\n // but require careful consideration of conflict resolution\n }\n\n /**\n * Check if task should be updated from Linear based on conflict resolution strategy\n */\n private shouldUpdateFromLinear(\n task: PebblesTask,\n linearIssue: LinearIssue,\n mapping: TaskMapping,\n stackMemoryUpdateTime: number,\n linearUpdateTime: number\n ): boolean {\n switch (this.config.conflictResolution) {\n case 'linear_wins':\n return true;\n case 'stackmemory_wins':\n return false;\n case 'newest_wins':\n return linearUpdateTime > stackMemoryUpdateTime;\n case 'manual':\n return false;\n default:\n return false;\n }\n }\n\n /**\n * Get tasks that haven't been synced to Linear yet\n */\n private getUnsyncedTasks(): PebblesTask[] {\n const activeTasks = this.taskStore.getActiveTasks();\n return activeTasks.filter(\n (task) => !this.mappings.has(task.id) && !task.external_refs?.linear\n );\n }\n\n /**\n * Get tasks that have been modified since last sync\n */\n private getModifiedTasks(): PebblesTask[] {\n const tasks: PebblesTask[] = [];\n\n for (const [taskId, mapping] of this.mappings) {\n const task = this.taskStore.getTask(taskId);\n if (task && task.timestamp * 1000 > mapping.lastSyncTimestamp) {\n tasks.push(task);\n }\n }\n\n return tasks;\n }\n\n /**\n * Update task with Linear reference\n */\n private updateTaskWithLinearRef(\n taskId: string,\n linearIssue: LinearIssue\n ): void {\n const task = this.taskStore.getTask(taskId);\n if (!task) return;\n\n // This would need a method in LinearTaskManager to update external_refs\n // For now, we'll track this in our mappings\n logger.info(`Task ${taskId} mapped to Linear ${linearIssue.identifier}`);\n }\n\n // Mapping utilities\n\n private formatDescriptionForLinear(task: PebblesTask): string {\n let description = task.description || '';\n\n description += `\\n\\n---\\n**StackMemory Context:**\\n`;\n description += `- Task ID: ${task.id}\\n`;\n description += `- Frame: ${task.frame_id}\\n`;\n description += `- Created: ${new Date(task.created_at * 1000).toISOString()}\\n`;\n\n if (task.tags.length > 0) {\n description += `- Tags: ${task.tags.join(', ')}\\n`;\n }\n\n if (task.depends_on.length > 0) {\n description += `- Dependencies: ${task.depends_on.join(', ')}\\n`;\n }\n\n return description;\n }\n\n private mapPriorityToLinear(priority: TaskPriority): number {\n const map: Record<TaskPriority, number> = {\n low: 1, // Low priority in Linear\n medium: 2, // Medium priority in Linear\n high: 3, // High priority in Linear\n urgent: 4, // Urgent priority in Linear\n };\n return map[priority] || 2;\n }\n\n private mapTagsToLinear(_tags: string[]): string[] | undefined {\n // In a full implementation, this would map StackMemory tags to Linear label IDs\n // For now, return undefined to skip label assignment\n return undefined;\n }\n\n private mapLinearStateToStatus(linearStateType: string): TaskStatus {\n switch (linearStateType) {\n case 'backlog':\n case 'unstarted':\n return 'pending';\n case 'started':\n return 'in_progress';\n case 'completed':\n return 'completed';\n case 'cancelled':\n return 'cancelled';\n default:\n return 'pending';\n }\n }\n\n private async mapStatusToLinearState(\n status: TaskStatus\n ): Promise<string | undefined> {\n // Get available states for the team\n try {\n const team = await this.linearClient.getTeam();\n const states = await this.linearClient.getWorkflowStates(team.id);\n\n // Map StackMemory status to Linear state types\n const targetStateType = this.getLinearStateTypeFromStatus(status);\n\n // Find the first state that matches the target type\n const matchingState = states.find(\n (state) => state.type === targetStateType\n );\n return matchingState?.id;\n } catch (error: unknown) {\n logger.warn(\n 'Failed to map status to Linear state:',\n error instanceof Error ? { error } : undefined\n );\n return undefined;\n }\n }\n\n private getLinearStateTypeFromStatus(status: TaskStatus): string {\n switch (status) {\n case 'pending':\n return 'unstarted';\n case 'in_progress':\n return 'started';\n case 'completed':\n return 'completed';\n case 'cancelled':\n return 'cancelled';\n case 'blocked':\n return 'unstarted'; // Map blocked to unstarted in Linear\n default:\n return 'unstarted';\n }\n }\n\n // Persistence for mappings\n\n private loadMappings(): void {\n this.mappings.clear();\n\n if (existsSync(this.mappingsPath)) {\n try {\n const data = readFileSync(this.mappingsPath, 'utf8');\n const mappingsArray: TaskMapping[] = JSON.parse(data);\n for (const mapping of mappingsArray) {\n this.mappings.set(mapping.stackmemoryId, mapping);\n }\n logger.info(`Loaded ${this.mappings.size} task mappings from disk`);\n } catch (error: unknown) {\n logger.warn('Failed to load mappings, starting fresh');\n }\n }\n }\n\n private saveMappings(): void {\n try {\n const mappingsArray = Array.from(this.mappings.values());\n writeFileSync(this.mappingsPath, JSON.stringify(mappingsArray, null, 2));\n logger.info(`Saved ${this.mappings.size} task mappings to disk`);\n } catch (error: unknown) {\n logger.error('Failed to save mappings:', error as Error);\n }\n }\n\n /**\n * Import all issues from Linear to local task store\n */\n async importFromLinear(): Promise<{\n imported: number;\n skipped: number;\n errors: string[];\n }> {\n const result = { imported: 0, skipped: 0, errors: [] as string[] };\n\n try {\n // Get team info\n if (!this.config.defaultTeamId) {\n const team = await this.linearClient.getTeam();\n this.config.defaultTeamId = team.id;\n logger.info(`Using Linear team: ${team.name} (${team.key})`);\n }\n\n // Fetch all issues from Linear (excluding completed/cancelled)\n const issues = await this.linearClient.getIssues({\n teamId: this.config.defaultTeamId,\n limit: 100,\n });\n\n logger.info(`Found ${issues.length} issues in Linear`);\n\n // Build reverse mapping (linearId -> stackmemoryId)\n const linearIdToTaskId = new Map<string, string>();\n for (const [taskId, mapping] of this.mappings) {\n linearIdToTaskId.set(mapping.linearId, taskId);\n }\n\n for (const issue of issues) {\n try {\n // Skip if already mapped\n if (linearIdToTaskId.has(issue.id)) {\n result.skipped++;\n continue;\n }\n\n // Create local task from Linear issue\n const taskId = await this.createTaskFromLinearIssue(issue);\n\n if (taskId) {\n // Create mapping\n const mapping: TaskMapping = {\n stackmemoryId: taskId,\n linearId: issue.id,\n linearIdentifier: issue.identifier,\n lastSyncTimestamp: Date.now(),\n lastLinearUpdate: issue.updatedAt,\n lastStackMemoryUpdate: Date.now(),\n };\n this.mappings.set(taskId, mapping);\n result.imported++;\n logger.info(`Imported ${issue.identifier}: ${issue.title}`);\n }\n } catch (error: unknown) {\n result.errors.push(\n `Failed to import ${issue.identifier}: ${String(error)}`\n );\n logger.error(`Failed to import ${issue.identifier}:`, error as Error);\n }\n }\n\n this.saveMappings();\n } catch (error: unknown) {\n result.errors.push(`Import failed: ${String(error)}`);\n logger.error('Linear import failed:', error as Error);\n }\n\n return result;\n }\n\n /**\n * Create a local task from a Linear issue\n */\n private async createTaskFromLinearIssue(\n issue: LinearIssue\n ): Promise<string | null> {\n try {\n const priority = this.mapLinearPriorityToLocal(issue.priority);\n\n // Build description with Linear context\n let description = issue.description || '';\n description += `\\n\\n---\\n**Linear:** ${issue.identifier} | ${issue.url}`;\n\n // Extract labels (handle both array and {nodes: [...]} formats)\n const labels = Array.isArray(issue.labels)\n ? issue.labels\n : (issue.labels as unknown as { nodes: Array<{ name: string }> })\n ?.nodes || [];\n const tags = labels.map((l) => l.name);\n if (tags.length === 0) tags.push('linear');\n\n // Create the task via the task store\n const taskId = this.taskStore.createTask({\n title: `[${issue.identifier}] ${issue.title}`,\n description,\n priority,\n frameId: 'linear-import',\n tags,\n estimatedEffort: issue.estimate ? issue.estimate * 60 : undefined,\n });\n\n // Update status if not pending\n const status = this.mapLinearStateToStatus(issue.state.type);\n if (status !== 'pending') {\n this.taskStore.updateTaskStatus(\n taskId,\n status,\n `Imported from Linear as ${status}`\n );\n }\n\n return taskId;\n } catch (error: unknown) {\n logger.error(\n `Failed to create task from Linear issue ${issue.identifier}: ${String(error)}`\n );\n return null;\n }\n }\n\n /**\n * Map Linear priority (0-4) to local TaskPriority\n */\n private mapLinearPriorityToLocal(priority: number): TaskPriority {\n switch (priority) {\n case 1:\n return 'urgent';\n case 2:\n return 'high';\n case 3:\n return 'medium';\n case 4:\n return 'low';\n default:\n return 'medium';\n }\n }\n}\n\n/**\n * Default sync configuration\n */\nexport const DEFAULT_SYNC_CONFIG: SyncConfig = {\n enabled: false,\n direction: 'bidirectional',\n autoSync: true,\n conflictResolution: 'newest_wins',\n syncInterval: 15, // minutes\n maxBatchSize: 10, // max tasks per sync batch\n rateLimitDelay: 500, // 500ms between API calls\n};\n\n/**\n * Duplicate Detection for Linear Issues\n */\nexport interface DuplicateCheckResult {\n isDuplicate: boolean;\n existingIssue?: LinearIssue;\n similarity?: number;\n}\n\nexport class LinearDuplicateDetector {\n private linearClient: LinearClient;\n private titleCache: Map<string, LinearIssue[]> = new Map();\n private cacheExpiry: number = 5 * 60 * 1000; // 5 minutes\n private lastCacheRefresh: number = 0;\n\n constructor(linearClient: LinearClient) {\n this.linearClient = linearClient;\n }\n\n /**\n * Search for existing Linear issues with similar titles\n */\n async searchByTitle(title: string, teamId?: string): Promise<LinearIssue[]> {\n const normalizedTitle = this.normalizeTitle(title);\n \n // Check cache first\n if (this.isCacheValid()) {\n const cached = this.titleCache.get(normalizedTitle);\n if (cached) return cached;\n }\n\n try {\n // Get all issues from the team (Linear API limit is 250)\n const allIssues = await this.linearClient.getIssues({\n teamId,\n limit: 100, // Use smaller limit to avoid API errors\n });\n\n // Filter for matching titles (exact and fuzzy)\n const matchingIssues = allIssues.filter(issue => {\n const issueNormalized = this.normalizeTitle(issue.title);\n \n // Exact match\n if (issueNormalized === normalizedTitle) return true;\n \n // Fuzzy match - check if titles are very similar\n const similarity = this.calculateSimilarity(normalizedTitle, issueNormalized);\n return similarity > 0.85; // 85% similarity threshold\n });\n\n // Update cache\n this.titleCache.set(normalizedTitle, matchingIssues);\n this.lastCacheRefresh = Date.now();\n\n return matchingIssues;\n } catch (error) {\n logger.error('Failed to search Linear issues by title:', error as Error);\n return [];\n }\n }\n\n /**\n * Check if a task title would create a duplicate in Linear\n */\n async checkForDuplicate(\n title: string,\n teamId?: string\n ): Promise<DuplicateCheckResult> {\n const existingIssues = await this.searchByTitle(title, teamId);\n \n if (existingIssues.length === 0) {\n return { isDuplicate: false };\n }\n\n // Find the best match\n let bestMatch: LinearIssue | undefined;\n let bestSimilarity = 0;\n\n for (const issue of existingIssues) {\n const similarity = this.calculateSimilarity(\n this.normalizeTitle(title),\n this.normalizeTitle(issue.title)\n );\n \n if (similarity > bestSimilarity) {\n bestSimilarity = similarity;\n bestMatch = issue;\n }\n }\n\n return {\n isDuplicate: true,\n existingIssue: bestMatch,\n similarity: bestSimilarity,\n };\n }\n\n /**\n * Merge task content into existing Linear issue\n */\n async mergeIntoExisting(\n existingIssue: LinearIssue,\n newTitle: string,\n newDescription?: string,\n additionalContext?: string\n ): Promise<LinearIssue> {\n try {\n // Build merged description\n let mergedDescription = existingIssue.description || '';\n \n if (newDescription && !mergedDescription.includes(newDescription)) {\n mergedDescription += `\\n\\n## Additional Context (${new Date().toISOString()})\\n`;\n mergedDescription += newDescription;\n }\n\n if (additionalContext) {\n mergedDescription += `\\n\\n---\\n${additionalContext}`;\n }\n\n // Update the existing issue\n const updateQuery = `\n mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {\n issueUpdate(id: $id, input: $input) {\n issue {\n id\n identifier\n title\n description\n updatedAt\n }\n }\n }\n `;\n\n const variables = {\n id: existingIssue.id,\n input: {\n description: mergedDescription,\n },\n };\n\n const response = await this.linearClient.graphql(updateQuery, variables);\n const updatedIssue = response.issueUpdate?.issue;\n\n if (updatedIssue) {\n logger.info(\n `Merged content into existing Linear issue ${existingIssue.identifier}: ${existingIssue.title}`\n );\n return updatedIssue;\n }\n\n return existingIssue;\n } catch (error) {\n logger.error('Failed to merge into existing Linear issue:', error as Error);\n return existingIssue;\n }\n }\n\n /**\n * Normalize title for comparison\n */\n private normalizeTitle(title: string): string {\n return title\n .toLowerCase()\n .trim()\n .replace(/\\s+/g, ' ') // Normalize whitespace\n .replace(/[^\\w\\s-]/g, '') // Remove special characters except hyphens\n .replace(/^(sta|eng|bug|feat|task|tsk)[-\\s]\\d+[-\\s:]*/, '') // Remove issue prefixes\n .trim();\n }\n\n /**\n * Calculate similarity between two strings (Levenshtein distance based)\n */\n private calculateSimilarity(str1: string, str2: string): number {\n if (str1 === str2) return 1;\n if (str1.length === 0 || str2.length === 0) return 0;\n\n // Use Levenshtein distance for similarity calculation\n const distance = this.levenshteinDistance(str1, str2);\n const maxLength = Math.max(str1.length, str2.length);\n \n return 1 - (distance / maxLength);\n }\n\n /**\n * Calculate Levenshtein distance between two strings\n */\n private levenshteinDistance(str1: string, str2: string): number {\n const m = str1.length;\n const n = str2.length;\n const dp: number[][] = Array(m + 1)\n .fill(null)\n .map(() => Array(n + 1).fill(0));\n\n for (let i = 0; i <= m; i++) dp[i][0] = i;\n for (let j = 0; j <= n; j++) dp[0][j] = j;\n\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n if (str1[i - 1] === str2[j - 1]) {\n dp[i][j] = dp[i - 1][j - 1];\n } else {\n dp[i][j] = 1 + Math.min(\n dp[i - 1][j], // deletion\n dp[i][j - 1], // insertion\n dp[i - 1][j - 1] // substitution\n );\n }\n }\n }\n\n return dp[m][n];\n }\n\n /**\n * Check if cache is still valid\n */\n private isCacheValid(): boolean {\n return Date.now() - this.lastCacheRefresh < this.cacheExpiry;\n }\n\n /**\n * Clear the title cache\n */\n clearCache(): void {\n this.titleCache.clear();\n this.lastCacheRefresh = 0;\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Linear Bi-directional Sync Engine\n * Handles syncing tasks between StackMemory and Linear\n */\n\nimport { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport { logger } from '../../core/monitoring/logger.js';\nimport { IntegrationError, ErrorCode } from '../../core/errors/index.js';\nimport {\n PebblesTask,\n LinearTaskManager,\n TaskStatus,\n TaskPriority,\n} from '../../features/tasks/linear-task-manager.js';\nimport { LinearClient, LinearIssue, LinearCreateIssueInput } from './client.js';\nimport { LinearAuthManager } from './auth.js';\nimport { getEnv, getOptionalEnv } from '../../utils/env.js';\n\nexport interface SyncConfig {\n enabled: boolean;\n direction: 'bidirectional' | 'to_linear' | 'from_linear';\n defaultTeamId?: string;\n autoSync: boolean;\n conflictResolution:\n | 'linear_wins'\n | 'stackmemory_wins'\n | 'manual'\n | 'newest_wins';\n syncInterval?: number; // minutes\n maxBatchSize?: number; // max tasks to sync per batch\n rateLimitDelay?: number; // ms delay between API calls\n}\n\nexport interface SyncResult {\n success: boolean;\n synced: {\n toLinear: number;\n fromLinear: number;\n updated: number;\n };\n conflicts: Array<{\n taskId: string;\n linearId: string;\n reason: string;\n }>;\n errors: string[];\n}\n\nexport interface TaskMapping {\n stackmemoryId: string;\n linearId: string;\n linearIdentifier: string;\n lastSyncTimestamp: number;\n lastLinearUpdate: string;\n lastStackMemoryUpdate: number;\n}\n\nexport class LinearSyncEngine {\n private taskStore: LinearTaskManager;\n private linearClient: LinearClient;\n private authManager: LinearAuthManager;\n private config: SyncConfig;\n private mappings: Map<string, TaskMapping> = new Map();\n private projectRoot: string;\n private mappingsPath: string;\n\n constructor(\n taskStore: LinearTaskManager,\n authManager: LinearAuthManager,\n config: SyncConfig,\n projectRoot?: string\n ) {\n this.taskStore = taskStore;\n this.authManager = authManager;\n this.config = config;\n this.projectRoot = projectRoot || process.cwd();\n this.mappingsPath = join(\n this.projectRoot,\n '.stackmemory',\n 'linear-mappings.json'\n );\n\n // Check for API key from environment variable first\n const apiKey = process.env['LINEAR_API_KEY'];\n\n if (apiKey) {\n // Use API key from environment\n this.linearClient = new LinearClient({\n apiKey: apiKey,\n });\n } else {\n // Fall back to OAuth tokens\n const tokens = this.authManager.loadTokens();\n if (!tokens) {\n throw new IntegrationError(\n 'Linear API key or authentication tokens not found. Set LINEAR_API_KEY environment variable or run \"stackmemory linear setup\" first.',\n ErrorCode.LINEAR_SYNC_FAILED\n );\n }\n\n this.linearClient = new LinearClient({\n apiKey: tokens.accessToken,\n useBearer: true,\n onUnauthorized: async () => {\n const refreshed = await this.authManager.refreshAccessToken();\n return refreshed.accessToken;\n },\n });\n }\n\n this.loadMappings();\n }\n\n /**\n * Update sync configuration\n */\n updateConfig(newConfig: Partial<SyncConfig>): void {\n this.config = { ...this.config, ...newConfig };\n }\n\n /**\n * Perform bi-directional sync\n */\n async sync(): Promise<SyncResult> {\n if (!this.config.enabled) {\n return {\n success: false,\n synced: { toLinear: 0, fromLinear: 0, updated: 0 },\n conflicts: [],\n errors: ['Sync is disabled'],\n };\n }\n\n const result: SyncResult = {\n success: true,\n synced: { toLinear: 0, fromLinear: 0, updated: 0 },\n conflicts: [],\n errors: [],\n };\n\n try {\n // Update client with valid token if not using environment API key\n const apiKey = process.env['LINEAR_API_KEY'];\n if (!apiKey) {\n const token = await this.authManager.getValidToken();\n this.linearClient = new LinearClient({\n apiKey: token,\n useBearer: true,\n onUnauthorized: async () => {\n const refreshed = await this.authManager.refreshAccessToken();\n return refreshed.accessToken;\n },\n });\n }\n\n // Get team info if not configured\n if (!this.config.defaultTeamId) {\n const team = await this.linearClient.getTeam();\n this.config.defaultTeamId = team.id;\n logger.info(`Using Linear team: ${team.name} (${team.key})`);\n }\n\n // Sync in both directions based on configuration\n if (\n this.config.direction === 'bidirectional' ||\n this.config.direction === 'to_linear'\n ) {\n const toLinearResult = await this.syncToLinear();\n result.synced.toLinear = toLinearResult.created;\n result.synced.updated += toLinearResult.updated;\n result.errors.push(...toLinearResult.errors);\n }\n\n if (\n this.config.direction === 'bidirectional' ||\n this.config.direction === 'from_linear'\n ) {\n const fromLinearResult = await this.syncFromLinear();\n result.synced.fromLinear = fromLinearResult.created;\n result.synced.updated += fromLinearResult.updated;\n result.conflicts.push(...fromLinearResult.conflicts);\n result.errors.push(...fromLinearResult.errors);\n }\n\n this.saveMappings();\n } catch (error: unknown) {\n result.success = false;\n result.errors.push(`Sync failed: ${String(error)}`);\n logger.error('Linear sync failed:', error as Error);\n }\n\n return result;\n }\n\n /**\n * Delay helper for rate limiting\n */\n private async delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Sync tasks from StackMemory to Linear\n */\n private async syncToLinear(): Promise<{\n created: number;\n updated: number;\n errors: string[];\n }> {\n const result = { created: 0, updated: 0, errors: [] as string[] };\n const maxBatchSize = this.config.maxBatchSize || 10;\n const rateLimitDelay = this.config.rateLimitDelay || 500;\n\n // Initialize duplicate detector\n const duplicateDetector = new LinearDuplicateDetector(this.linearClient);\n\n // Get unsynced tasks from StackMemory\n const unsyncedTasks = this.getUnsyncedTasks();\n\n // Limit batch size to avoid rate limits\n const tasksToSync = unsyncedTasks.slice(0, maxBatchSize);\n\n if (unsyncedTasks.length > maxBatchSize) {\n logger.info(\n `Syncing ${tasksToSync.length} of ${unsyncedTasks.length} unsynced tasks (batch limit)`\n );\n }\n\n for (const task of tasksToSync) {\n try {\n // Check for duplicates before creating\n const duplicateCheck = await duplicateDetector.checkForDuplicate(\n task.title,\n this.config.defaultTeamId\n );\n\n let linearIssue: LinearIssue;\n\n if (duplicateCheck.isDuplicate && duplicateCheck.existingIssue) {\n // Found duplicate - merge instead of creating\n logger.info(\n `Found existing Linear issue for \"${task.title}\": ${duplicateCheck.existingIssue.identifier} (${Math.round((duplicateCheck.similarity || 0) * 100)}% match)`\n );\n\n // Merge task content into existing issue\n linearIssue = await duplicateDetector.mergeIntoExisting(\n duplicateCheck.existingIssue,\n task.title,\n this.formatDescriptionForLinear(task),\n `StackMemory Task ID: ${task.id}\\nFrame: ${task.frame_id}`\n );\n } else {\n // No duplicate found, create new issue\n linearIssue = await this.createLinearIssueFromTask(task);\n }\n\n // Create mapping\n const mapping: TaskMapping = {\n stackmemoryId: task.id,\n linearId: linearIssue.id,\n linearIdentifier: linearIssue.identifier,\n lastSyncTimestamp: Date.now(),\n lastLinearUpdate: linearIssue.updatedAt,\n lastStackMemoryUpdate: task.timestamp * 1000,\n };\n\n this.mappings.set(task.id, mapping);\n\n // Update task with Linear reference\n this.updateTaskWithLinearRef(task.id, linearIssue);\n\n result.created++;\n logger.info(\n `Synced task to Linear: ${task.title} \u2192 ${linearIssue.identifier}`\n );\n\n // Rate limit delay between creates\n await this.delay(rateLimitDelay);\n } catch (error: unknown) {\n const errorMsg = String(error);\n // Stop syncing on rate limit errors\n if (\n errorMsg.includes('rate limit') ||\n errorMsg.includes('usage limit')\n ) {\n logger.warn('Rate limit hit, stopping sync batch');\n result.errors.push('Rate limit reached - sync paused');\n break;\n }\n result.errors.push(`Failed to sync task ${task.id}: ${errorMsg}`);\n logger.error(\n `Failed to sync task ${task.id} to Linear:`,\n error as Error\n );\n }\n }\n\n // Update existing Linear issues for modified StackMemory tasks\n const modifiedTasks = this.getModifiedTasks();\n\n for (const task of modifiedTasks) {\n try {\n const mapping = this.mappings.get(task.id);\n if (!mapping) continue;\n\n await this.updateLinearIssueFromTask(task, mapping);\n\n mapping.lastSyncTimestamp = Date.now();\n mapping.lastStackMemoryUpdate = task.timestamp * 1000;\n\n result.updated++;\n logger.info(`Updated Linear issue: ${mapping.linearIdentifier}`);\n } catch (error: unknown) {\n result.errors.push(\n `Failed to update Linear issue for task ${task.id}: ${String(error)}`\n );\n logger.error(\n `Failed to update Linear issue for task ${task.id}:`,\n error as Error\n );\n }\n }\n\n return result;\n }\n\n /**\n * Sync tasks from Linear to StackMemory\n */\n private async syncFromLinear(): Promise<{\n created: number;\n updated: number;\n conflicts: Array<{ taskId: string; linearId: string; reason: string }>;\n errors: string[];\n }> {\n const result = {\n created: 0,\n updated: 0,\n conflicts: [] as Array<{\n taskId: string;\n linearId: string;\n reason: string;\n }>,\n errors: [] as string[],\n };\n\n // First, import any new issues from Linear that aren't mapped yet\n const importResult = await this.importFromLinear();\n result.created = importResult.imported;\n result.errors.push(...importResult.errors);\n\n // Then update existing mapped tasks\n for (const [taskId, mapping] of this.mappings) {\n try {\n const linearIssue = await this.linearClient.getIssue(mapping.linearId);\n\n if (!linearIssue) {\n result.errors.push(`Linear issue ${mapping.linearId} not found`);\n continue;\n }\n\n // Check if Linear issue was updated since last sync\n const linearUpdateTime = new Date(linearIssue.updatedAt).getTime();\n if (linearUpdateTime <= mapping.lastSyncTimestamp) {\n continue; // No changes in Linear\n }\n\n // Check for conflicts\n const task = this.taskStore.getTask(taskId);\n if (!task) {\n result.errors.push(`StackMemory task ${taskId} not found`);\n continue;\n }\n\n const stackMemoryUpdateTime = task.timestamp * 1000;\n\n if (\n stackMemoryUpdateTime > mapping.lastSyncTimestamp &&\n linearUpdateTime > mapping.lastSyncTimestamp\n ) {\n // Conflict: both sides updated since last sync\n result.conflicts.push({\n taskId,\n linearId: mapping.linearId,\n reason: 'Both StackMemory and Linear were updated since last sync',\n });\n\n if (this.config.conflictResolution === 'manual') {\n continue; // Skip, let user resolve manually\n }\n }\n\n // Apply conflict resolution\n const shouldUpdateFromLinear = this.shouldUpdateFromLinear(\n task,\n linearIssue,\n mapping,\n stackMemoryUpdateTime,\n linearUpdateTime\n );\n\n if (shouldUpdateFromLinear) {\n this.updateTaskFromLinearIssue(task, linearIssue);\n\n mapping.lastSyncTimestamp = Date.now();\n mapping.lastLinearUpdate = linearIssue.updatedAt;\n\n result.updated++;\n logger.info(`Updated StackMemory task from Linear: ${task.title}`);\n }\n } catch (error: unknown) {\n result.errors.push(\n `Failed to sync from Linear for task ${taskId}: ${String(error)}`\n );\n logger.error(\n `Failed to sync from Linear for task ${taskId}:`,\n error as Error\n );\n }\n }\n\n return result;\n }\n\n /**\n * Create Linear issue from StackMemory task\n */\n private async createLinearIssueFromTask(\n task: PebblesTask\n ): Promise<LinearIssue> {\n const input: LinearCreateIssueInput = {\n title: task.title,\n description: this.formatDescriptionForLinear(task),\n teamId: this.config.defaultTeamId!,\n priority: this.mapPriorityToLinear(task.priority),\n estimate: task.estimated_effort\n ? Math.ceil(task.estimated_effort / 60)\n : undefined, // Convert minutes to hours\n labelIds: this.mapTagsToLinear(task.tags),\n };\n\n return await this.linearClient.createIssue(input);\n }\n\n /**\n * Update Linear issue from StackMemory task\n */\n private async updateLinearIssueFromTask(\n task: PebblesTask,\n mapping: TaskMapping\n ): Promise<void> {\n const updates: Partial<LinearCreateIssueInput> & { stateId?: string } = {\n title: task.title,\n description: this.formatDescriptionForLinear(task),\n priority: this.mapPriorityToLinear(task.priority),\n estimate: task.estimated_effort\n ? Math.ceil(task.estimated_effort / 60)\n : undefined,\n stateId: await this.mapStatusToLinearState(task.status),\n };\n\n await this.linearClient.updateIssue(mapping.linearId, updates);\n }\n\n /**\n * Update StackMemory task from Linear issue\n */\n private updateTaskFromLinearIssue(\n task: PebblesTask,\n linearIssue: LinearIssue\n ): void {\n // Map Linear state to StackMemory status\n const newStatus = this.mapLinearStateToStatus(linearIssue.state.type);\n\n if (newStatus !== task.status) {\n this.taskStore.updateTaskStatus(\n task.id,\n newStatus,\n 'Updated from Linear'\n );\n }\n\n // Note: Other fields like title, description could be updated here\n // but require careful consideration of conflict resolution\n }\n\n /**\n * Check if task should be updated from Linear based on conflict resolution strategy\n */\n private shouldUpdateFromLinear(\n task: PebblesTask,\n linearIssue: LinearIssue,\n mapping: TaskMapping,\n stackMemoryUpdateTime: number,\n linearUpdateTime: number\n ): boolean {\n switch (this.config.conflictResolution) {\n case 'linear_wins':\n return true;\n case 'stackmemory_wins':\n return false;\n case 'newest_wins':\n return linearUpdateTime > stackMemoryUpdateTime;\n case 'manual':\n return false;\n default:\n return false;\n }\n }\n\n /**\n * Get tasks that haven't been synced to Linear yet\n */\n private getUnsyncedTasks(): PebblesTask[] {\n const activeTasks = this.taskStore.getActiveTasks();\n return activeTasks.filter(\n (task) => !this.mappings.has(task.id) && !task.external_refs?.linear\n );\n }\n\n /**\n * Get tasks that have been modified since last sync\n */\n private getModifiedTasks(): PebblesTask[] {\n const tasks: PebblesTask[] = [];\n\n for (const [taskId, mapping] of this.mappings) {\n const task = this.taskStore.getTask(taskId);\n if (task && task.timestamp * 1000 > mapping.lastSyncTimestamp) {\n tasks.push(task);\n }\n }\n\n return tasks;\n }\n\n /**\n * Update task with Linear reference\n */\n private updateTaskWithLinearRef(\n taskId: string,\n linearIssue: LinearIssue\n ): void {\n const task = this.taskStore.getTask(taskId);\n if (!task) return;\n\n // This would need a method in LinearTaskManager to update external_refs\n // For now, we'll track this in our mappings\n logger.info(`Task ${taskId} mapped to Linear ${linearIssue.identifier}`);\n }\n\n // Mapping utilities\n\n private formatDescriptionForLinear(task: PebblesTask): string {\n let description = task.description || '';\n\n description += `\\n\\n---\\n**StackMemory Context:**\\n`;\n description += `- Task ID: ${task.id}\\n`;\n description += `- Frame: ${task.frame_id}\\n`;\n description += `- Created: ${new Date(task.created_at * 1000).toISOString()}\\n`;\n\n if (task.tags.length > 0) {\n description += `- Tags: ${task.tags.join(', ')}\\n`;\n }\n\n if (task.depends_on.length > 0) {\n description += `- Dependencies: ${task.depends_on.join(', ')}\\n`;\n }\n\n return description;\n }\n\n private mapPriorityToLinear(priority: TaskPriority): number {\n const map: Record<TaskPriority, number> = {\n low: 1, // Low priority in Linear\n medium: 2, // Medium priority in Linear\n high: 3, // High priority in Linear\n urgent: 4, // Urgent priority in Linear\n };\n return map[priority] || 2;\n }\n\n private mapTagsToLinear(_tags: string[]): string[] | undefined {\n // In a full implementation, this would map StackMemory tags to Linear label IDs\n // For now, return undefined to skip label assignment\n return undefined;\n }\n\n private mapLinearStateToStatus(linearStateType: string): TaskStatus {\n switch (linearStateType) {\n case 'backlog':\n case 'unstarted':\n return 'pending';\n case 'started':\n return 'in_progress';\n case 'completed':\n return 'completed';\n case 'cancelled':\n return 'cancelled';\n default:\n return 'pending';\n }\n }\n\n private async mapStatusToLinearState(\n status: TaskStatus\n ): Promise<string | undefined> {\n // Get available states for the team\n try {\n const team = await this.linearClient.getTeam();\n const states = await this.linearClient.getWorkflowStates(team.id);\n\n // Map StackMemory status to Linear state types\n const targetStateType = this.getLinearStateTypeFromStatus(status);\n\n // Find the first state that matches the target type\n const matchingState = states.find(\n (state) => state.type === targetStateType\n );\n return matchingState?.id;\n } catch (error: unknown) {\n logger.warn(\n 'Failed to map status to Linear state:',\n error instanceof Error ? { error } : undefined\n );\n return undefined;\n }\n }\n\n private getLinearStateTypeFromStatus(status: TaskStatus): string {\n switch (status) {\n case 'pending':\n return 'unstarted';\n case 'in_progress':\n return 'started';\n case 'completed':\n return 'completed';\n case 'cancelled':\n return 'cancelled';\n case 'blocked':\n return 'unstarted'; // Map blocked to unstarted in Linear\n default:\n return 'unstarted';\n }\n }\n\n // Persistence for mappings\n\n private loadMappings(): void {\n this.mappings.clear();\n\n if (existsSync(this.mappingsPath)) {\n try {\n const data = readFileSync(this.mappingsPath, 'utf8');\n const mappingsArray: TaskMapping[] = JSON.parse(data);\n for (const mapping of mappingsArray) {\n this.mappings.set(mapping.stackmemoryId, mapping);\n }\n logger.info(`Loaded ${this.mappings.size} task mappings from disk`);\n } catch (error: unknown) {\n logger.warn('Failed to load mappings, starting fresh');\n }\n }\n }\n\n private saveMappings(): void {\n try {\n const mappingsArray = Array.from(this.mappings.values());\n writeFileSync(this.mappingsPath, JSON.stringify(mappingsArray, null, 2));\n logger.info(`Saved ${this.mappings.size} task mappings to disk`);\n } catch (error: unknown) {\n logger.error('Failed to save mappings:', error as Error);\n }\n }\n\n /**\n * Import all issues from Linear to local task store\n */\n async importFromLinear(): Promise<{\n imported: number;\n skipped: number;\n errors: string[];\n }> {\n const result = { imported: 0, skipped: 0, errors: [] as string[] };\n\n try {\n // Get team info\n if (!this.config.defaultTeamId) {\n const team = await this.linearClient.getTeam();\n this.config.defaultTeamId = team.id;\n logger.info(`Using Linear team: ${team.name} (${team.key})`);\n }\n\n // Fetch all issues from Linear (excluding completed/cancelled)\n const issues = await this.linearClient.getIssues({\n teamId: this.config.defaultTeamId,\n limit: 100,\n });\n\n logger.info(`Found ${issues.length} issues in Linear`);\n\n // Build reverse mapping (linearId -> stackmemoryId)\n const linearIdToTaskId = new Map<string, string>();\n for (const [taskId, mapping] of this.mappings) {\n linearIdToTaskId.set(mapping.linearId, taskId);\n }\n\n for (const issue of issues) {\n try {\n // Skip if already mapped\n if (linearIdToTaskId.has(issue.id)) {\n result.skipped++;\n continue;\n }\n\n // Create local task from Linear issue\n const taskId = await this.createTaskFromLinearIssue(issue);\n\n if (taskId) {\n // Create mapping\n const mapping: TaskMapping = {\n stackmemoryId: taskId,\n linearId: issue.id,\n linearIdentifier: issue.identifier,\n lastSyncTimestamp: Date.now(),\n lastLinearUpdate: issue.updatedAt,\n lastStackMemoryUpdate: Date.now(),\n };\n this.mappings.set(taskId, mapping);\n result.imported++;\n logger.info(`Imported ${issue.identifier}: ${issue.title}`);\n }\n } catch (error: unknown) {\n result.errors.push(\n `Failed to import ${issue.identifier}: ${String(error)}`\n );\n logger.error(`Failed to import ${issue.identifier}:`, error as Error);\n }\n }\n\n this.saveMappings();\n } catch (error: unknown) {\n result.errors.push(`Import failed: ${String(error)}`);\n logger.error('Linear import failed:', error as Error);\n }\n\n return result;\n }\n\n /**\n * Create a local task from a Linear issue\n */\n private async createTaskFromLinearIssue(\n issue: LinearIssue\n ): Promise<string | null> {\n try {\n const priority = this.mapLinearPriorityToLocal(issue.priority);\n\n // Build description with Linear context\n let description = issue.description || '';\n description += `\\n\\n---\\n**Linear:** ${issue.identifier} | ${issue.url}`;\n\n // Extract labels (handle both array and {nodes: [...]} formats)\n const labels = Array.isArray(issue.labels)\n ? issue.labels\n : (issue.labels as unknown as { nodes: Array<{ name: string }> })\n ?.nodes || [];\n const tags = labels.map((l) => l.name);\n if (tags.length === 0) tags.push('linear');\n\n // Create the task via the task store\n const taskId = this.taskStore.createTask({\n title: `[${issue.identifier}] ${issue.title}`,\n description,\n priority,\n frameId: 'linear-import',\n tags,\n estimatedEffort: issue.estimate ? issue.estimate * 60 : undefined,\n });\n\n // Update status if not pending\n const status = this.mapLinearStateToStatus(issue.state.type);\n if (status !== 'pending') {\n this.taskStore.updateTaskStatus(\n taskId,\n status,\n `Imported from Linear as ${status}`\n );\n }\n\n return taskId;\n } catch (error: unknown) {\n logger.error(\n `Failed to create task from Linear issue ${issue.identifier}: ${String(error)}`\n );\n return null;\n }\n }\n\n /**\n * Map Linear priority (0-4) to local TaskPriority\n */\n private mapLinearPriorityToLocal(priority: number): TaskPriority {\n switch (priority) {\n case 1:\n return 'urgent';\n case 2:\n return 'high';\n case 3:\n return 'medium';\n case 4:\n return 'low';\n default:\n return 'medium';\n }\n }\n}\n\n/**\n * Default sync configuration\n */\nexport const DEFAULT_SYNC_CONFIG: SyncConfig = {\n enabled: false,\n direction: 'bidirectional',\n autoSync: true,\n conflictResolution: 'newest_wins',\n syncInterval: 15, // minutes\n maxBatchSize: 10, // max tasks per sync batch\n rateLimitDelay: 500, // 500ms between API calls\n};\n\n/**\n * Duplicate Detection for Linear Issues\n */\nexport interface DuplicateCheckResult {\n isDuplicate: boolean;\n existingIssue?: LinearIssue;\n similarity?: number;\n}\n\nexport class LinearDuplicateDetector {\n private linearClient: LinearClient;\n private titleCache: Map<string, LinearIssue[]> = new Map();\n private cacheExpiry: number = 5 * 60 * 1000; // 5 minutes\n private lastCacheRefresh: number = 0;\n\n constructor(linearClient: LinearClient) {\n this.linearClient = linearClient;\n }\n\n /**\n * Search for existing Linear issues with similar titles\n */\n async searchByTitle(title: string, teamId?: string): Promise<LinearIssue[]> {\n const normalizedTitle = this.normalizeTitle(title);\n\n // Check cache first\n if (this.isCacheValid()) {\n const cached = this.titleCache.get(normalizedTitle);\n if (cached) return cached;\n }\n\n try {\n // Get all issues from the team (Linear API limit is 250)\n const allIssues = await this.linearClient.getIssues({\n teamId,\n limit: 100, // Use smaller limit to avoid API errors\n });\n\n // Filter for matching titles (exact and fuzzy)\n const matchingIssues = allIssues.filter((issue) => {\n const issueNormalized = this.normalizeTitle(issue.title);\n\n // Exact match\n if (issueNormalized === normalizedTitle) return true;\n\n // Fuzzy match - check if titles are very similar\n const similarity = this.calculateSimilarity(\n normalizedTitle,\n issueNormalized\n );\n return similarity > 0.85; // 85% similarity threshold\n });\n\n // Update cache\n this.titleCache.set(normalizedTitle, matchingIssues);\n this.lastCacheRefresh = Date.now();\n\n return matchingIssues;\n } catch (error) {\n logger.error('Failed to search Linear issues by title:', error as Error);\n return [];\n }\n }\n\n /**\n * Check if a task title would create a duplicate in Linear\n */\n async checkForDuplicate(\n title: string,\n teamId?: string\n ): Promise<DuplicateCheckResult> {\n const existingIssues = await this.searchByTitle(title, teamId);\n\n if (existingIssues.length === 0) {\n return { isDuplicate: false };\n }\n\n // Find the best match\n let bestMatch: LinearIssue | undefined;\n let bestSimilarity = 0;\n\n for (const issue of existingIssues) {\n const similarity = this.calculateSimilarity(\n this.normalizeTitle(title),\n this.normalizeTitle(issue.title)\n );\n\n if (similarity > bestSimilarity) {\n bestSimilarity = similarity;\n bestMatch = issue;\n }\n }\n\n return {\n isDuplicate: true,\n existingIssue: bestMatch,\n similarity: bestSimilarity,\n };\n }\n\n /**\n * Merge task content into existing Linear issue\n */\n async mergeIntoExisting(\n existingIssue: LinearIssue,\n newTitle: string,\n newDescription?: string,\n additionalContext?: string\n ): Promise<LinearIssue> {\n try {\n // Build merged description\n let mergedDescription = existingIssue.description || '';\n\n if (newDescription && !mergedDescription.includes(newDescription)) {\n mergedDescription += `\\n\\n## Additional Context (${new Date().toISOString()})\\n`;\n mergedDescription += newDescription;\n }\n\n if (additionalContext) {\n mergedDescription += `\\n\\n---\\n${additionalContext}`;\n }\n\n // Update the existing issue\n const updateQuery = `\n mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {\n issueUpdate(id: $id, input: $input) {\n issue {\n id\n identifier\n title\n description\n updatedAt\n }\n }\n }\n `;\n\n const variables = {\n id: existingIssue.id,\n input: {\n description: mergedDescription,\n },\n };\n\n const response = await this.linearClient.graphql(updateQuery, variables);\n const updatedIssue = response.issueUpdate?.issue;\n\n if (updatedIssue) {\n logger.info(\n `Merged content into existing Linear issue ${existingIssue.identifier}: ${existingIssue.title}`\n );\n return updatedIssue;\n }\n\n return existingIssue;\n } catch (error) {\n logger.error(\n 'Failed to merge into existing Linear issue:',\n error as Error\n );\n return existingIssue;\n }\n }\n\n /**\n * Normalize title for comparison\n */\n private normalizeTitle(title: string): string {\n return title\n .toLowerCase()\n .trim()\n .replace(/\\s+/g, ' ') // Normalize whitespace\n .replace(/[^\\w\\s-]/g, '') // Remove special characters except hyphens\n .replace(/^(sta|eng|bug|feat|task|tsk)[-\\s]\\d+[-\\s:]*/, '') // Remove issue prefixes\n .trim();\n }\n\n /**\n * Calculate similarity between two strings (Levenshtein distance based)\n */\n private calculateSimilarity(str1: string, str2: string): number {\n if (str1 === str2) return 1;\n if (str1.length === 0 || str2.length === 0) return 0;\n\n // Use Levenshtein distance for similarity calculation\n const distance = this.levenshteinDistance(str1, str2);\n const maxLength = Math.max(str1.length, str2.length);\n\n return 1 - distance / maxLength;\n }\n\n /**\n * Calculate Levenshtein distance between two strings\n */\n private levenshteinDistance(str1: string, str2: string): number {\n const m = str1.length;\n const n = str2.length;\n const dp: number[][] = Array(m + 1)\n .fill(null)\n .map(() => Array(n + 1).fill(0));\n\n for (let i = 0; i <= m; i++) dp[i][0] = i;\n for (let j = 0; j <= n; j++) dp[0][j] = j;\n\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n if (str1[i - 1] === str2[j - 1]) {\n dp[i][j] = dp[i - 1][j - 1];\n } else {\n dp[i][j] =\n 1 +\n Math.min(\n dp[i - 1][j], // deletion\n dp[i][j - 1], // insertion\n dp[i - 1][j - 1] // substitution\n );\n }\n }\n }\n\n return dp[m][n];\n }\n\n /**\n * Check if cache is still valid\n */\n private isCacheValid(): boolean {\n return Date.now() - this.lastCacheRefresh < this.cacheExpiry;\n }\n\n /**\n * Clear the title cache\n */\n clearCache(): void {\n this.titleCache.clear();\n this.lastCacheRefresh = 0;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,6 +5,7 @@ const __dirname = __pathDirname(__filename);
|
|
|
5
5
|
import { LinearClient } from "./client.js";
|
|
6
6
|
import { LinearDuplicateDetector } from "./sync.js";
|
|
7
7
|
import { logger } from "../../core/monitoring/logger.js";
|
|
8
|
+
import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
|
|
8
9
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
9
10
|
import { join, dirname } from "path";
|
|
10
11
|
import { EventEmitter } from "events";
|
|
@@ -52,7 +53,10 @@ class UnifiedLinearSync extends EventEmitter {
|
|
|
52
53
|
try {
|
|
53
54
|
const token = await this.authManager.getValidToken();
|
|
54
55
|
if (!token) {
|
|
55
|
-
throw new
|
|
56
|
+
throw new IntegrationError(
|
|
57
|
+
'Linear authentication required. Run "stackmemory linear auth" first.',
|
|
58
|
+
ErrorCode.LINEAR_AUTH_FAILED
|
|
59
|
+
);
|
|
56
60
|
}
|
|
57
61
|
const isOAuth = this.authManager.isOAuth();
|
|
58
62
|
this.linearClient = new LinearClient({
|
|
@@ -85,7 +89,10 @@ class UnifiedLinearSync extends EventEmitter {
|
|
|
85
89
|
*/
|
|
86
90
|
async sync() {
|
|
87
91
|
if (this.syncInProgress) {
|
|
88
|
-
throw new
|
|
92
|
+
throw new IntegrationError(
|
|
93
|
+
"Sync already in progress",
|
|
94
|
+
ErrorCode.LINEAR_SYNC_FAILED
|
|
95
|
+
);
|
|
89
96
|
}
|
|
90
97
|
this.syncInProgress = true;
|
|
91
98
|
const startTime = Date.now();
|
|
@@ -163,7 +170,9 @@ class UnifiedLinearSync extends EventEmitter {
|
|
|
163
170
|
stats.fromLinear.created++;
|
|
164
171
|
}
|
|
165
172
|
} catch (error) {
|
|
166
|
-
stats.errors.push(
|
|
173
|
+
stats.errors.push(
|
|
174
|
+
`Failed to sync issue ${issue.identifier}: ${error.message}`
|
|
175
|
+
);
|
|
167
176
|
}
|
|
168
177
|
}
|
|
169
178
|
} catch (error) {
|
|
@@ -199,7 +208,10 @@ class UnifiedLinearSync extends EventEmitter {
|
|
|
199
208
|
);
|
|
200
209
|
if (duplicateCheck.isDuplicate && duplicateCheck.existingIssue) {
|
|
201
210
|
if (this.config.mergeStrategy === "merge_content") {
|
|
202
|
-
await this.mergeTaskIntoLinear(
|
|
211
|
+
await this.mergeTaskIntoLinear(
|
|
212
|
+
task,
|
|
213
|
+
duplicateCheck.existingIssue
|
|
214
|
+
);
|
|
203
215
|
this.mappings.set(task.id, duplicateCheck.existingIssue.id);
|
|
204
216
|
stats.toLinear.duplicatesMerged++;
|
|
205
217
|
} else if (this.config.mergeStrategy === "skip") {
|
|
@@ -216,7 +228,9 @@ class UnifiedLinearSync extends EventEmitter {
|
|
|
216
228
|
}
|
|
217
229
|
}
|
|
218
230
|
} catch (error) {
|
|
219
|
-
stats.errors.push(
|
|
231
|
+
stats.errors.push(
|
|
232
|
+
`Failed to sync task ${task.id}: ${error.message}`
|
|
233
|
+
);
|
|
220
234
|
}
|
|
221
235
|
}
|
|
222
236
|
} catch (error) {
|
|
@@ -385,7 +399,10 @@ class UnifiedLinearSync extends EventEmitter {
|
|
|
385
399
|
async getDefaultTeamId() {
|
|
386
400
|
const teams = await this.linearClient.getTeams();
|
|
387
401
|
if (teams.length === 0) {
|
|
388
|
-
throw new
|
|
402
|
+
throw new IntegrationError(
|
|
403
|
+
"No Linear teams found",
|
|
404
|
+
ErrorCode.LINEAR_API_ERROR
|
|
405
|
+
);
|
|
389
406
|
}
|
|
390
407
|
return teams[0].id;
|
|
391
408
|
}
|
|
@@ -505,7 +522,11 @@ Merged: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
|
505
522
|
}
|
|
506
523
|
}
|
|
507
524
|
loadMappings() {
|
|
508
|
-
const mappingFile = join(
|
|
525
|
+
const mappingFile = join(
|
|
526
|
+
this.projectRoot,
|
|
527
|
+
".stackmemory",
|
|
528
|
+
"linear-mappings.json"
|
|
529
|
+
);
|
|
509
530
|
if (existsSync(mappingFile)) {
|
|
510
531
|
try {
|
|
511
532
|
const data = JSON.parse(readFileSync(mappingFile, "utf8"));
|
|
@@ -516,7 +537,11 @@ Merged: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
|
516
537
|
}
|
|
517
538
|
}
|
|
518
539
|
saveMappings() {
|
|
519
|
-
const mappingFile = join(
|
|
540
|
+
const mappingFile = join(
|
|
541
|
+
this.projectRoot,
|
|
542
|
+
".stackmemory",
|
|
543
|
+
"linear-mappings.json"
|
|
544
|
+
);
|
|
520
545
|
const data = Object.fromEntries(this.mappings);
|
|
521
546
|
writeFileSync(mappingFile, JSON.stringify(data, null, 2));
|
|
522
547
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/integrations/linear/unified-sync.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Unified Linear Sync System\n * Consolidates all sync functionality with duplicate detection,\n * bidirectional sync, and task planning integration\n */\n\nimport { LinearClient, LinearIssue, LinearCreateIssueInput } from './client.js';\nimport { LinearDuplicateDetector, DuplicateCheckResult } from './sync.js';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport { LinearAuthManager } from './auth.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport { Task, TaskStatus, TaskPriority } from '../../types/task.js';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { EventEmitter } from 'events';\n\n// Unified sync configuration\nexport interface UnifiedSyncConfig {\n // Core settings\n enabled: boolean;\n direction: 'bidirectional' | 'to_linear' | 'from_linear';\n defaultTeamId?: string;\n \n // Duplicate detection\n duplicateDetection: boolean;\n duplicateSimilarityThreshold: number; // 0-1, default 0.85\n mergeStrategy: 'merge_content' | 'skip' | 'create_anyway';\n \n // Conflict resolution\n conflictResolution: 'newest_wins' | 'linear_wins' | 'local_wins' | 'manual';\n \n // Task planning\n taskPlanningEnabled: boolean;\n taskPlanFile?: string; // Default: .stackmemory/task-plan.md\n autoCreateTaskPlan: boolean;\n \n // Performance\n maxBatchSize: number;\n rateLimitDelay: number; // ms between requests\n maxRetries: number;\n \n // Auto-sync\n autoSync: boolean;\n autoSyncInterval?: number; // minutes\n quietHours?: {\n start: number; // hour 0-23\n end: number;\n };\n}\n\nexport const DEFAULT_UNIFIED_CONFIG: UnifiedSyncConfig = {\n enabled: true,\n direction: 'bidirectional',\n duplicateDetection: true,\n duplicateSimilarityThreshold: 0.85,\n mergeStrategy: 'merge_content',\n conflictResolution: 'newest_wins',\n taskPlanningEnabled: true,\n taskPlanFile: '.stackmemory/task-plan.md',\n autoCreateTaskPlan: true,\n maxBatchSize: 50,\n rateLimitDelay: 100,\n maxRetries: 3,\n autoSync: false,\n autoSyncInterval: 15,\n};\n\n// Sync statistics\nexport interface SyncStats {\n toLinear: {\n created: number;\n updated: number;\n skipped: number;\n duplicatesMerged: number;\n };\n fromLinear: {\n created: number;\n updated: number;\n skipped: number;\n };\n conflicts: Array<{\n taskId: string;\n reason: string;\n resolution: string;\n }>;\n errors: string[];\n duration: number;\n timestamp: number;\n}\n\n// Task planning integration\ninterface TaskPlan {\n version: string;\n lastUpdated: Date;\n phases: Array<{\n name: string;\n description: string;\n tasks: Array<{\n id: string;\n title: string;\n priority: TaskPriority;\n status: TaskStatus;\n linearId?: string;\n dependencies?: string[];\n }>;\n }>;\n}\n\nexport class UnifiedLinearSync extends EventEmitter {\n private config: UnifiedSyncConfig;\n private linearClient: LinearClient;\n private taskStore: LinearTaskManager;\n private authManager: LinearAuthManager;\n private duplicateDetector: LinearDuplicateDetector;\n private projectRoot: string;\n private mappings: Map<string, string> = new Map(); // task.id -> linear.id\n private lastSyncStats?: SyncStats;\n private syncInProgress = false;\n\n constructor(\n taskStore: LinearTaskManager,\n authManager: LinearAuthManager,\n projectRoot: string,\n config?: Partial<UnifiedSyncConfig>\n ) {\n super();\n this.taskStore = taskStore;\n this.authManager = authManager;\n this.projectRoot = projectRoot;\n this.config = { ...DEFAULT_UNIFIED_CONFIG, ...config };\n \n // Initialize Linear client - will be set up in initialize()\n this.linearClient = null as any;\n this.duplicateDetector = null as any;\n \n // Load existing mappings\n this.loadMappings();\n }\n\n /**\n * Initialize the sync system\n */\n async initialize(): Promise<void> {\n try {\n // Get Linear authentication\n const token = await this.authManager.getValidToken();\n if (!token) {\n throw new Error('Linear authentication required. Run \"stackmemory linear auth\" first.');\n }\n\n // Initialize Linear client with proper auth\n const isOAuth = this.authManager.isOAuth();\n this.linearClient = new LinearClient({\n apiKey: token,\n useBearer: isOAuth,\n teamId: this.config.defaultTeamId,\n onUnauthorized: isOAuth\n ? async () => {\n const refreshed = await this.authManager.refreshAccessToken();\n return refreshed.accessToken;\n }\n : undefined,\n });\n\n // Initialize duplicate detector\n if (this.config.duplicateDetection) {\n this.duplicateDetector = new LinearDuplicateDetector(this.linearClient);\n }\n\n // Initialize task planning if enabled\n if (this.config.taskPlanningEnabled) {\n await this.initializeTaskPlanning();\n }\n\n logger.info('Unified Linear sync initialized', {\n direction: this.config.direction,\n duplicateDetection: this.config.duplicateDetection,\n taskPlanning: this.config.taskPlanningEnabled,\n });\n } catch (error: unknown) {\n logger.error('Failed to initialize Linear sync:', error as Error);\n throw error;\n }\n }\n\n /**\n * Main sync method - orchestrates bidirectional sync\n */\n async sync(): Promise<SyncStats> {\n if (this.syncInProgress) {\n throw new Error('Sync already in progress');\n }\n\n this.syncInProgress = true;\n const startTime = Date.now();\n \n const stats: SyncStats = {\n toLinear: { created: 0, updated: 0, skipped: 0, duplicatesMerged: 0 },\n fromLinear: { created: 0, updated: 0, skipped: 0 },\n conflicts: [],\n errors: [],\n duration: 0,\n timestamp: Date.now(),\n };\n\n try {\n this.emit('sync:started', { config: this.config });\n\n // Determine sync direction and execute\n switch (this.config.direction) {\n case 'bidirectional':\n await this.syncFromLinear(stats);\n await this.syncToLinear(stats);\n break;\n case 'from_linear':\n await this.syncFromLinear(stats);\n break;\n case 'to_linear':\n await this.syncToLinear(stats);\n break;\n }\n\n // Update task plan if enabled\n if (this.config.taskPlanningEnabled) {\n await this.updateTaskPlan(stats);\n }\n\n // Save mappings\n this.saveMappings();\n\n stats.duration = Date.now() - startTime;\n this.lastSyncStats = stats;\n\n this.emit('sync:completed', { stats });\n logger.info('Unified sync completed', {\n duration: `${stats.duration}ms`,\n toLinear: stats.toLinear,\n fromLinear: stats.fromLinear,\n conflicts: stats.conflicts.length,\n });\n\n return stats;\n } catch (error: unknown) {\n stats.errors.push((error as Error).message);\n stats.duration = Date.now() - startTime;\n \n this.emit('sync:failed', { stats, error });\n logger.error('Unified sync failed:', error as Error);\n \n throw error;\n } finally {\n this.syncInProgress = false;\n }\n }\n\n /**\n * Sync from Linear to local tasks\n */\n private async syncFromLinear(stats: SyncStats): Promise<void> {\n try {\n logger.debug('Syncing from Linear...');\n\n // Get team ID\n const teamId = this.config.defaultTeamId || (await this.getDefaultTeamId());\n \n // Fetch Linear issues\n const issues = await this.linearClient.getIssues({\n teamId,\n limit: this.config.maxBatchSize,\n });\n\n for (const issue of issues) {\n try {\n await this.delay(this.config.rateLimitDelay);\n \n // Check if we have this issue mapped\n const localTaskId = this.findLocalTaskByLinearId(issue.id);\n \n if (localTaskId) {\n // Update existing task\n const localTask = await this.taskStore.getTask(localTaskId);\n if (localTask && this.hasChanges(localTask, issue)) {\n await this.updateLocalTask(localTask, issue);\n stats.fromLinear.updated++;\n } else {\n stats.fromLinear.skipped++;\n }\n } else {\n // Create new local task\n await this.createLocalTask(issue);\n stats.fromLinear.created++;\n }\n } catch (error: unknown) {\n stats.errors.push(`Failed to sync issue ${issue.identifier}: ${(error as Error).message}`);\n }\n }\n } catch (error: unknown) {\n logger.error('Failed to sync from Linear:', error as Error);\n throw error;\n }\n }\n\n /**\n * Sync local tasks to Linear\n */\n private async syncToLinear(stats: SyncStats): Promise<void> {\n try {\n logger.debug('Syncing to Linear...');\n\n // Get all local tasks\n const tasks = await this.taskStore.getAllTasks();\n const teamId = this.config.defaultTeamId || (await this.getDefaultTeamId());\n\n for (const task of tasks) {\n try {\n await this.delay(this.config.rateLimitDelay);\n \n // Skip if already mapped to Linear\n const linearId = this.mappings.get(task.id);\n \n if (linearId) {\n // Update existing Linear issue\n const linearIssue = await this.linearClient.getIssue(linearId);\n if (linearIssue && this.taskNeedsUpdate(task, linearIssue)) {\n await this.updateLinearIssue(linearIssue, task);\n stats.toLinear.updated++;\n } else {\n stats.toLinear.skipped++;\n }\n } else {\n // Check for duplicates before creating\n if (this.config.duplicateDetection) {\n const duplicateCheck = await this.duplicateDetector.checkForDuplicate(\n task.title,\n teamId\n );\n \n if (duplicateCheck.isDuplicate && duplicateCheck.existingIssue) {\n if (this.config.mergeStrategy === 'merge_content') {\n // Merge into existing\n await this.mergeTaskIntoLinear(task, duplicateCheck.existingIssue);\n this.mappings.set(task.id, duplicateCheck.existingIssue.id);\n stats.toLinear.duplicatesMerged++;\n } else if (this.config.mergeStrategy === 'skip') {\n stats.toLinear.skipped++;\n continue;\n }\n } else {\n // Create new Linear issue\n await this.createLinearIssue(task, teamId);\n stats.toLinear.created++;\n }\n } else {\n // Create without duplicate check\n await this.createLinearIssue(task, teamId);\n stats.toLinear.created++;\n }\n }\n } catch (error: unknown) {\n stats.errors.push(`Failed to sync task ${task.id}: ${(error as Error).message}`);\n }\n }\n } catch (error: unknown) {\n logger.error('Failed to sync to Linear:', error as Error);\n throw error;\n }\n }\n\n /**\n * Initialize task planning system\n */\n private async initializeTaskPlanning(): Promise<void> {\n const planFile = join(this.projectRoot, this.config.taskPlanFile!);\n const planDir = dirname(planFile);\n\n // Ensure directory exists\n if (!existsSync(planDir)) {\n mkdirSync(planDir, { recursive: true });\n }\n\n // Create default task plan if it doesn't exist\n if (!existsSync(planFile) && this.config.autoCreateTaskPlan) {\n const defaultPlan: TaskPlan = {\n version: '1.0.0',\n lastUpdated: new Date(),\n phases: [\n {\n name: 'Backlog',\n description: 'Tasks to be prioritized',\n tasks: [],\n },\n {\n name: 'Current Sprint',\n description: 'Active work items',\n tasks: [],\n },\n {\n name: 'Completed',\n description: 'Finished tasks',\n tasks: [],\n },\n ],\n };\n\n this.saveTaskPlan(defaultPlan);\n logger.info('Created default task plan', { path: planFile });\n }\n }\n\n /**\n * Update task plan with sync results\n */\n private async updateTaskPlan(stats: SyncStats): Promise<void> {\n if (!this.config.taskPlanningEnabled) return;\n\n try {\n const plan = this.loadTaskPlan();\n const tasks = await this.taskStore.getAllTasks();\n\n // Reorganize tasks by status\n plan.phases = [\n {\n name: 'Backlog',\n description: 'Tasks to be prioritized',\n tasks: tasks\n .filter((t) => t.status === 'todo')\n .map((t) => ({\n id: t.id,\n title: t.title,\n priority: t.priority || 'medium',\n status: t.status,\n linearId: this.mappings.get(t.id),\n })),\n },\n {\n name: 'In Progress',\n description: 'Active work items',\n tasks: tasks\n .filter((t) => t.status === 'in_progress')\n .map((t) => ({\n id: t.id,\n title: t.title,\n priority: t.priority || 'medium',\n status: t.status,\n linearId: this.mappings.get(t.id),\n })),\n },\n {\n name: 'Completed',\n description: 'Finished tasks',\n tasks: tasks\n .filter((t) => t.status === 'done')\n .slice(-20) // Keep last 20 completed\n .map((t) => ({\n id: t.id,\n title: t.title,\n priority: t.priority || 'medium',\n status: t.status,\n linearId: this.mappings.get(t.id),\n })),\n },\n ];\n\n plan.lastUpdated = new Date();\n this.saveTaskPlan(plan);\n\n // Also generate markdown report\n this.generateTaskReport(plan, stats);\n } catch (error: unknown) {\n logger.error('Failed to update task plan:', error as Error);\n }\n }\n\n /**\n * Generate markdown task report\n */\n private generateTaskReport(plan: TaskPlan, stats: SyncStats): void {\n const reportFile = join(this.projectRoot, '.stackmemory', 'task-report.md');\n \n let content = `# Task Sync Report\\n\\n`;\n content += `**Last Updated:** ${plan.lastUpdated.toLocaleString()}\\n`;\n content += `**Sync Duration:** ${stats.duration}ms\\n\\n`;\n\n content += `## Sync Statistics\\n\\n`;\n content += `### To Linear\\n`;\n content += `- Created: ${stats.toLinear.created}\\n`;\n content += `- Updated: ${stats.toLinear.updated}\\n`;\n content += `- Duplicates Merged: ${stats.toLinear.duplicatesMerged}\\n`;\n content += `- Skipped: ${stats.toLinear.skipped}\\n\\n`;\n\n content += `### From Linear\\n`;\n content += `- Created: ${stats.fromLinear.created}\\n`;\n content += `- Updated: ${stats.fromLinear.updated}\\n`;\n content += `- Skipped: ${stats.fromLinear.skipped}\\n\\n`;\n\n if (stats.conflicts.length > 0) {\n content += `### Conflicts\\n`;\n stats.conflicts.forEach((c) => {\n content += `- **${c.taskId}**: ${c.reason} (${c.resolution})\\n`;\n });\n content += '\\n';\n }\n\n content += `## Task Overview\\n\\n`;\n plan.phases.forEach((phase) => {\n content += `### ${phase.name} (${phase.tasks.length})\\n`;\n content += `> ${phase.description}\\n\\n`;\n \n if (phase.tasks.length > 0) {\n phase.tasks.slice(0, 10).forEach((task) => {\n const linearLink = task.linearId ? ` [Linear]` : '';\n content += `- **${task.title}**${linearLink}\\n`;\n });\n \n if (phase.tasks.length > 10) {\n content += `- _...and ${phase.tasks.length - 10} more_\\n`;\n }\n }\n content += '\\n';\n });\n\n writeFileSync(reportFile, content);\n logger.debug('Task report generated', { path: reportFile });\n }\n\n /**\n * Helper methods\n */\n \n private async getDefaultTeamId(): Promise<string> {\n const teams = await this.linearClient.getTeams();\n if (teams.length === 0) {\n throw new Error('No Linear teams found');\n }\n return teams[0]!.id;\n }\n\n private findLocalTaskByLinearId(linearId: string): string | undefined {\n for (const [taskId, linId] of this.mappings) {\n if (linId === linearId) return taskId;\n }\n return undefined;\n }\n\n private hasChanges(localTask: Task, linearIssue: LinearIssue): boolean {\n return (\n localTask.title !== linearIssue.title ||\n localTask.description !== (linearIssue.description || '') ||\n this.mapLinearStateToStatus(linearIssue.state.type) !== localTask.status\n );\n }\n\n private taskNeedsUpdate(task: Task, linearIssue: LinearIssue): boolean {\n return (\n task.title !== linearIssue.title ||\n task.description !== (linearIssue.description || '') ||\n task.status !== this.mapLinearStateToStatus(linearIssue.state.type)\n );\n }\n\n private async createLocalTask(issue: LinearIssue): Promise<void> {\n const task = await this.taskStore.createTask({\n title: issue.title,\n description: issue.description || '',\n status: this.mapLinearStateToStatus(issue.state.type),\n priority: this.mapLinearPriorityToPriority(issue.priority),\n metadata: {\n linear: {\n id: issue.id,\n identifier: issue.identifier,\n url: issue.url,\n },\n },\n });\n\n this.mappings.set(task.id, issue.id);\n }\n\n private async updateLocalTask(task: Task, issue: LinearIssue): Promise<void> {\n await this.taskStore.updateTask(task.id, {\n title: issue.title,\n description: issue.description || '',\n status: this.mapLinearStateToStatus(issue.state.type),\n priority: this.mapLinearPriorityToPriority(issue.priority),\n });\n }\n\n private async createLinearIssue(task: Task, teamId: string): Promise<void> {\n const input: LinearCreateIssueInput = {\n title: task.title,\n description: task.description || '',\n teamId,\n priority: this.mapPriorityToLinearPriority(task.priority),\n };\n\n const issue = await this.linearClient.createIssue(input);\n this.mappings.set(task.id, issue.id);\n\n // Update task with Linear metadata\n await this.taskStore.updateTask(task.id, {\n metadata: {\n ...task.metadata,\n linear: {\n id: issue.id,\n identifier: issue.identifier,\n url: issue.url,\n },\n },\n });\n }\n\n private async updateLinearIssue(issue: LinearIssue, task: Task): Promise<void> {\n await this.linearClient.updateIssue(issue.id, {\n title: task.title,\n description: task.description,\n priority: this.mapPriorityToLinearPriority(task.priority),\n });\n }\n\n private async mergeTaskIntoLinear(task: Task, existingIssue: LinearIssue): Promise<void> {\n await this.duplicateDetector.mergeIntoExisting(\n existingIssue,\n task.title,\n task.description,\n `StackMemory Task: ${task.id}\\nMerged: ${new Date().toISOString()}`\n );\n }\n\n private mapLinearStateToStatus(state: string): TaskStatus {\n switch (state.toLowerCase()) {\n case 'backlog':\n case 'unstarted':\n return 'todo';\n case 'started':\n return 'in_progress';\n case 'completed':\n return 'done';\n case 'cancelled':\n return 'cancelled';\n default:\n return 'todo';\n }\n }\n\n private mapLinearPriorityToPriority(priority?: number): TaskPriority | undefined {\n switch (priority) {\n case 1:\n return 'urgent';\n case 2:\n return 'high';\n case 3:\n return 'medium';\n case 4:\n return 'low';\n default:\n return undefined;\n }\n }\n\n private mapPriorityToLinearPriority(priority?: TaskPriority): number {\n switch (priority) {\n case 'urgent':\n return 1;\n case 'high':\n return 2;\n case 'medium':\n return 3;\n case 'low':\n return 4;\n default:\n return 0;\n }\n }\n\n private loadMappings(): void {\n const mappingFile = join(this.projectRoot, '.stackmemory', 'linear-mappings.json');\n if (existsSync(mappingFile)) {\n try {\n const data = JSON.parse(readFileSync(mappingFile, 'utf8'));\n this.mappings = new Map(Object.entries(data));\n } catch (error: unknown) {\n logger.error('Failed to load mappings:', error as Error);\n }\n }\n }\n\n private saveMappings(): void {\n const mappingFile = join(this.projectRoot, '.stackmemory', 'linear-mappings.json');\n const data = Object.fromEntries(this.mappings);\n writeFileSync(mappingFile, JSON.stringify(data, null, 2));\n }\n\n private loadTaskPlan(): TaskPlan {\n const planFile = join(this.projectRoot, this.config.taskPlanFile!);\n if (existsSync(planFile)) {\n try {\n return JSON.parse(readFileSync(planFile, 'utf8'));\n } catch (error: unknown) {\n logger.error('Failed to load task plan:', error as Error);\n }\n }\n \n return {\n version: '1.0.0',\n lastUpdated: new Date(),\n phases: [],\n };\n }\n\n private saveTaskPlan(plan: TaskPlan): void {\n const planFile = join(this.projectRoot, this.config.taskPlanFile!);\n writeFileSync(planFile, JSON.stringify(plan, null, 2));\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Get last sync statistics\n */\n getLastSyncStats(): SyncStats | undefined {\n return this.lastSyncStats;\n }\n\n /**\n * Clear duplicate detector cache\n */\n clearCache(): void {\n if (this.duplicateDetector) {\n this.duplicateDetector.clearCache();\n }\n }\n}"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Unified Linear Sync System\n * Consolidates all sync functionality with duplicate detection,\n * bidirectional sync, and task planning integration\n */\n\nimport { LinearClient, LinearIssue, LinearCreateIssueInput } from './client.js';\nimport { LinearDuplicateDetector } from './sync.js';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport { LinearAuthManager } from './auth.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport { IntegrationError, ErrorCode } from '../../core/errors/index.js';\nimport { Task, TaskStatus, TaskPriority } from '../../types/task.js';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { EventEmitter } from 'events';\n\n// Unified sync configuration\nexport interface UnifiedSyncConfig {\n // Core settings\n enabled: boolean;\n direction: 'bidirectional' | 'to_linear' | 'from_linear';\n defaultTeamId?: string;\n\n // Duplicate detection\n duplicateDetection: boolean;\n duplicateSimilarityThreshold: number; // 0-1, default 0.85\n mergeStrategy: 'merge_content' | 'skip' | 'create_anyway';\n\n // Conflict resolution\n conflictResolution: 'newest_wins' | 'linear_wins' | 'local_wins' | 'manual';\n\n // Task planning\n taskPlanningEnabled: boolean;\n taskPlanFile?: string; // Default: .stackmemory/task-plan.md\n autoCreateTaskPlan: boolean;\n\n // Performance\n maxBatchSize: number;\n rateLimitDelay: number; // ms between requests\n maxRetries: number;\n\n // Auto-sync\n autoSync: boolean;\n autoSyncInterval?: number; // minutes\n quietHours?: {\n start: number; // hour 0-23\n end: number;\n };\n}\n\nexport const DEFAULT_UNIFIED_CONFIG: UnifiedSyncConfig = {\n enabled: true,\n direction: 'bidirectional',\n duplicateDetection: true,\n duplicateSimilarityThreshold: 0.85,\n mergeStrategy: 'merge_content',\n conflictResolution: 'newest_wins',\n taskPlanningEnabled: true,\n taskPlanFile: '.stackmemory/task-plan.md',\n autoCreateTaskPlan: true,\n maxBatchSize: 50,\n rateLimitDelay: 100,\n maxRetries: 3,\n autoSync: false,\n autoSyncInterval: 15,\n};\n\n// Sync statistics\nexport interface SyncStats {\n toLinear: {\n created: number;\n updated: number;\n skipped: number;\n duplicatesMerged: number;\n };\n fromLinear: {\n created: number;\n updated: number;\n skipped: number;\n };\n conflicts: Array<{\n taskId: string;\n reason: string;\n resolution: string;\n }>;\n errors: string[];\n duration: number;\n timestamp: number;\n}\n\n// Task planning integration\ninterface TaskPlan {\n version: string;\n lastUpdated: Date;\n phases: Array<{\n name: string;\n description: string;\n tasks: Array<{\n id: string;\n title: string;\n priority: TaskPriority;\n status: TaskStatus;\n linearId?: string;\n dependencies?: string[];\n }>;\n }>;\n}\n\nexport class UnifiedLinearSync extends EventEmitter {\n private config: UnifiedSyncConfig;\n private linearClient: LinearClient;\n private taskStore: LinearTaskManager;\n private authManager: LinearAuthManager;\n private duplicateDetector: LinearDuplicateDetector;\n private projectRoot: string;\n private mappings: Map<string, string> = new Map(); // task.id -> linear.id\n private lastSyncStats?: SyncStats;\n private syncInProgress = false;\n\n constructor(\n taskStore: LinearTaskManager,\n authManager: LinearAuthManager,\n projectRoot: string,\n config?: Partial<UnifiedSyncConfig>\n ) {\n super();\n this.taskStore = taskStore;\n this.authManager = authManager;\n this.projectRoot = projectRoot;\n this.config = { ...DEFAULT_UNIFIED_CONFIG, ...config };\n\n // Initialize Linear client - will be set up in initialize()\n this.linearClient = null as any;\n this.duplicateDetector = null as any;\n\n // Load existing mappings\n this.loadMappings();\n }\n\n /**\n * Initialize the sync system\n */\n async initialize(): Promise<void> {\n try {\n // Get Linear authentication\n const token = await this.authManager.getValidToken();\n if (!token) {\n throw new IntegrationError(\n 'Linear authentication required. Run \"stackmemory linear auth\" first.',\n ErrorCode.LINEAR_AUTH_FAILED\n );\n }\n\n // Initialize Linear client with proper auth\n const isOAuth = this.authManager.isOAuth();\n this.linearClient = new LinearClient({\n apiKey: token,\n useBearer: isOAuth,\n teamId: this.config.defaultTeamId,\n onUnauthorized: isOAuth\n ? async () => {\n const refreshed = await this.authManager.refreshAccessToken();\n return refreshed.accessToken;\n }\n : undefined,\n });\n\n // Initialize duplicate detector\n if (this.config.duplicateDetection) {\n this.duplicateDetector = new LinearDuplicateDetector(this.linearClient);\n }\n\n // Initialize task planning if enabled\n if (this.config.taskPlanningEnabled) {\n await this.initializeTaskPlanning();\n }\n\n logger.info('Unified Linear sync initialized', {\n direction: this.config.direction,\n duplicateDetection: this.config.duplicateDetection,\n taskPlanning: this.config.taskPlanningEnabled,\n });\n } catch (error: unknown) {\n logger.error('Failed to initialize Linear sync:', error as Error);\n throw error;\n }\n }\n\n /**\n * Main sync method - orchestrates bidirectional sync\n */\n async sync(): Promise<SyncStats> {\n if (this.syncInProgress) {\n throw new IntegrationError(\n 'Sync already in progress',\n ErrorCode.LINEAR_SYNC_FAILED\n );\n }\n\n this.syncInProgress = true;\n const startTime = Date.now();\n\n const stats: SyncStats = {\n toLinear: { created: 0, updated: 0, skipped: 0, duplicatesMerged: 0 },\n fromLinear: { created: 0, updated: 0, skipped: 0 },\n conflicts: [],\n errors: [],\n duration: 0,\n timestamp: Date.now(),\n };\n\n try {\n this.emit('sync:started', { config: this.config });\n\n // Determine sync direction and execute\n switch (this.config.direction) {\n case 'bidirectional':\n await this.syncFromLinear(stats);\n await this.syncToLinear(stats);\n break;\n case 'from_linear':\n await this.syncFromLinear(stats);\n break;\n case 'to_linear':\n await this.syncToLinear(stats);\n break;\n }\n\n // Update task plan if enabled\n if (this.config.taskPlanningEnabled) {\n await this.updateTaskPlan(stats);\n }\n\n // Save mappings\n this.saveMappings();\n\n stats.duration = Date.now() - startTime;\n this.lastSyncStats = stats;\n\n this.emit('sync:completed', { stats });\n logger.info('Unified sync completed', {\n duration: `${stats.duration}ms`,\n toLinear: stats.toLinear,\n fromLinear: stats.fromLinear,\n conflicts: stats.conflicts.length,\n });\n\n return stats;\n } catch (error: unknown) {\n stats.errors.push((error as Error).message);\n stats.duration = Date.now() - startTime;\n\n this.emit('sync:failed', { stats, error });\n logger.error('Unified sync failed:', error as Error);\n\n throw error;\n } finally {\n this.syncInProgress = false;\n }\n }\n\n /**\n * Sync from Linear to local tasks\n */\n private async syncFromLinear(stats: SyncStats): Promise<void> {\n try {\n logger.debug('Syncing from Linear...');\n\n // Get team ID\n const teamId =\n this.config.defaultTeamId || (await this.getDefaultTeamId());\n\n // Fetch Linear issues\n const issues = await this.linearClient.getIssues({\n teamId,\n limit: this.config.maxBatchSize,\n });\n\n for (const issue of issues) {\n try {\n await this.delay(this.config.rateLimitDelay);\n\n // Check if we have this issue mapped\n const localTaskId = this.findLocalTaskByLinearId(issue.id);\n\n if (localTaskId) {\n // Update existing task\n const localTask = await this.taskStore.getTask(localTaskId);\n if (localTask && this.hasChanges(localTask, issue)) {\n await this.updateLocalTask(localTask, issue);\n stats.fromLinear.updated++;\n } else {\n stats.fromLinear.skipped++;\n }\n } else {\n // Create new local task\n await this.createLocalTask(issue);\n stats.fromLinear.created++;\n }\n } catch (error: unknown) {\n stats.errors.push(\n `Failed to sync issue ${issue.identifier}: ${(error as Error).message}`\n );\n }\n }\n } catch (error: unknown) {\n logger.error('Failed to sync from Linear:', error as Error);\n throw error;\n }\n }\n\n /**\n * Sync local tasks to Linear\n */\n private async syncToLinear(stats: SyncStats): Promise<void> {\n try {\n logger.debug('Syncing to Linear...');\n\n // Get all local tasks\n const tasks = await this.taskStore.getAllTasks();\n const teamId =\n this.config.defaultTeamId || (await this.getDefaultTeamId());\n\n for (const task of tasks) {\n try {\n await this.delay(this.config.rateLimitDelay);\n\n // Skip if already mapped to Linear\n const linearId = this.mappings.get(task.id);\n\n if (linearId) {\n // Update existing Linear issue\n const linearIssue = await this.linearClient.getIssue(linearId);\n if (linearIssue && this.taskNeedsUpdate(task, linearIssue)) {\n await this.updateLinearIssue(linearIssue, task);\n stats.toLinear.updated++;\n } else {\n stats.toLinear.skipped++;\n }\n } else {\n // Check for duplicates before creating\n if (this.config.duplicateDetection) {\n const duplicateCheck =\n await this.duplicateDetector.checkForDuplicate(\n task.title,\n teamId\n );\n\n if (duplicateCheck.isDuplicate && duplicateCheck.existingIssue) {\n if (this.config.mergeStrategy === 'merge_content') {\n // Merge into existing\n await this.mergeTaskIntoLinear(\n task,\n duplicateCheck.existingIssue\n );\n this.mappings.set(task.id, duplicateCheck.existingIssue.id);\n stats.toLinear.duplicatesMerged++;\n } else if (this.config.mergeStrategy === 'skip') {\n stats.toLinear.skipped++;\n continue;\n }\n } else {\n // Create new Linear issue\n await this.createLinearIssue(task, teamId);\n stats.toLinear.created++;\n }\n } else {\n // Create without duplicate check\n await this.createLinearIssue(task, teamId);\n stats.toLinear.created++;\n }\n }\n } catch (error: unknown) {\n stats.errors.push(\n `Failed to sync task ${task.id}: ${(error as Error).message}`\n );\n }\n }\n } catch (error: unknown) {\n logger.error('Failed to sync to Linear:', error as Error);\n throw error;\n }\n }\n\n /**\n * Initialize task planning system\n */\n private async initializeTaskPlanning(): Promise<void> {\n const planFile = join(this.projectRoot, this.config.taskPlanFile!);\n const planDir = dirname(planFile);\n\n // Ensure directory exists\n if (!existsSync(planDir)) {\n mkdirSync(planDir, { recursive: true });\n }\n\n // Create default task plan if it doesn't exist\n if (!existsSync(planFile) && this.config.autoCreateTaskPlan) {\n const defaultPlan: TaskPlan = {\n version: '1.0.0',\n lastUpdated: new Date(),\n phases: [\n {\n name: 'Backlog',\n description: 'Tasks to be prioritized',\n tasks: [],\n },\n {\n name: 'Current Sprint',\n description: 'Active work items',\n tasks: [],\n },\n {\n name: 'Completed',\n description: 'Finished tasks',\n tasks: [],\n },\n ],\n };\n\n this.saveTaskPlan(defaultPlan);\n logger.info('Created default task plan', { path: planFile });\n }\n }\n\n /**\n * Update task plan with sync results\n */\n private async updateTaskPlan(stats: SyncStats): Promise<void> {\n if (!this.config.taskPlanningEnabled) return;\n\n try {\n const plan = this.loadTaskPlan();\n const tasks = await this.taskStore.getAllTasks();\n\n // Reorganize tasks by status\n plan.phases = [\n {\n name: 'Backlog',\n description: 'Tasks to be prioritized',\n tasks: tasks\n .filter((t) => t.status === 'todo')\n .map((t) => ({\n id: t.id,\n title: t.title,\n priority: t.priority || 'medium',\n status: t.status,\n linearId: this.mappings.get(t.id),\n })),\n },\n {\n name: 'In Progress',\n description: 'Active work items',\n tasks: tasks\n .filter((t) => t.status === 'in_progress')\n .map((t) => ({\n id: t.id,\n title: t.title,\n priority: t.priority || 'medium',\n status: t.status,\n linearId: this.mappings.get(t.id),\n })),\n },\n {\n name: 'Completed',\n description: 'Finished tasks',\n tasks: tasks\n .filter((t) => t.status === 'done')\n .slice(-20) // Keep last 20 completed\n .map((t) => ({\n id: t.id,\n title: t.title,\n priority: t.priority || 'medium',\n status: t.status,\n linearId: this.mappings.get(t.id),\n })),\n },\n ];\n\n plan.lastUpdated = new Date();\n this.saveTaskPlan(plan);\n\n // Also generate markdown report\n this.generateTaskReport(plan, stats);\n } catch (error: unknown) {\n logger.error('Failed to update task plan:', error as Error);\n }\n }\n\n /**\n * Generate markdown task report\n */\n private generateTaskReport(plan: TaskPlan, stats: SyncStats): void {\n const reportFile = join(this.projectRoot, '.stackmemory', 'task-report.md');\n\n let content = `# Task Sync Report\\n\\n`;\n content += `**Last Updated:** ${plan.lastUpdated.toLocaleString()}\\n`;\n content += `**Sync Duration:** ${stats.duration}ms\\n\\n`;\n\n content += `## Sync Statistics\\n\\n`;\n content += `### To Linear\\n`;\n content += `- Created: ${stats.toLinear.created}\\n`;\n content += `- Updated: ${stats.toLinear.updated}\\n`;\n content += `- Duplicates Merged: ${stats.toLinear.duplicatesMerged}\\n`;\n content += `- Skipped: ${stats.toLinear.skipped}\\n\\n`;\n\n content += `### From Linear\\n`;\n content += `- Created: ${stats.fromLinear.created}\\n`;\n content += `- Updated: ${stats.fromLinear.updated}\\n`;\n content += `- Skipped: ${stats.fromLinear.skipped}\\n\\n`;\n\n if (stats.conflicts.length > 0) {\n content += `### Conflicts\\n`;\n stats.conflicts.forEach((c) => {\n content += `- **${c.taskId}**: ${c.reason} (${c.resolution})\\n`;\n });\n content += '\\n';\n }\n\n content += `## Task Overview\\n\\n`;\n plan.phases.forEach((phase) => {\n content += `### ${phase.name} (${phase.tasks.length})\\n`;\n content += `> ${phase.description}\\n\\n`;\n\n if (phase.tasks.length > 0) {\n phase.tasks.slice(0, 10).forEach((task) => {\n const linearLink = task.linearId ? ` [Linear]` : '';\n content += `- **${task.title}**${linearLink}\\n`;\n });\n\n if (phase.tasks.length > 10) {\n content += `- _...and ${phase.tasks.length - 10} more_\\n`;\n }\n }\n content += '\\n';\n });\n\n writeFileSync(reportFile, content);\n logger.debug('Task report generated', { path: reportFile });\n }\n\n /**\n * Helper methods\n */\n\n private async getDefaultTeamId(): Promise<string> {\n const teams = await this.linearClient.getTeams();\n if (teams.length === 0) {\n throw new IntegrationError(\n 'No Linear teams found',\n ErrorCode.LINEAR_API_ERROR\n );\n }\n return teams[0]!.id;\n }\n\n private findLocalTaskByLinearId(linearId: string): string | undefined {\n for (const [taskId, linId] of this.mappings) {\n if (linId === linearId) return taskId;\n }\n return undefined;\n }\n\n private hasChanges(localTask: Task, linearIssue: LinearIssue): boolean {\n return (\n localTask.title !== linearIssue.title ||\n localTask.description !== (linearIssue.description || '') ||\n this.mapLinearStateToStatus(linearIssue.state.type) !== localTask.status\n );\n }\n\n private taskNeedsUpdate(task: Task, linearIssue: LinearIssue): boolean {\n return (\n task.title !== linearIssue.title ||\n task.description !== (linearIssue.description || '') ||\n task.status !== this.mapLinearStateToStatus(linearIssue.state.type)\n );\n }\n\n private async createLocalTask(issue: LinearIssue): Promise<void> {\n const task = await this.taskStore.createTask({\n title: issue.title,\n description: issue.description || '',\n status: this.mapLinearStateToStatus(issue.state.type),\n priority: this.mapLinearPriorityToPriority(issue.priority),\n metadata: {\n linear: {\n id: issue.id,\n identifier: issue.identifier,\n url: issue.url,\n },\n },\n });\n\n this.mappings.set(task.id, issue.id);\n }\n\n private async updateLocalTask(task: Task, issue: LinearIssue): Promise<void> {\n await this.taskStore.updateTask(task.id, {\n title: issue.title,\n description: issue.description || '',\n status: this.mapLinearStateToStatus(issue.state.type),\n priority: this.mapLinearPriorityToPriority(issue.priority),\n });\n }\n\n private async createLinearIssue(task: Task, teamId: string): Promise<void> {\n const input: LinearCreateIssueInput = {\n title: task.title,\n description: task.description || '',\n teamId,\n priority: this.mapPriorityToLinearPriority(task.priority),\n };\n\n const issue = await this.linearClient.createIssue(input);\n this.mappings.set(task.id, issue.id);\n\n // Update task with Linear metadata\n await this.taskStore.updateTask(task.id, {\n metadata: {\n ...task.metadata,\n linear: {\n id: issue.id,\n identifier: issue.identifier,\n url: issue.url,\n },\n },\n });\n }\n\n private async updateLinearIssue(\n issue: LinearIssue,\n task: Task\n ): Promise<void> {\n await this.linearClient.updateIssue(issue.id, {\n title: task.title,\n description: task.description,\n priority: this.mapPriorityToLinearPriority(task.priority),\n });\n }\n\n private async mergeTaskIntoLinear(\n task: Task,\n existingIssue: LinearIssue\n ): Promise<void> {\n await this.duplicateDetector.mergeIntoExisting(\n existingIssue,\n task.title,\n task.description,\n `StackMemory Task: ${task.id}\\nMerged: ${new Date().toISOString()}`\n );\n }\n\n private mapLinearStateToStatus(state: string): TaskStatus {\n switch (state.toLowerCase()) {\n case 'backlog':\n case 'unstarted':\n return 'todo';\n case 'started':\n return 'in_progress';\n case 'completed':\n return 'done';\n case 'cancelled':\n return 'cancelled';\n default:\n return 'todo';\n }\n }\n\n private mapLinearPriorityToPriority(\n priority?: number\n ): TaskPriority | undefined {\n switch (priority) {\n case 1:\n return 'urgent';\n case 2:\n return 'high';\n case 3:\n return 'medium';\n case 4:\n return 'low';\n default:\n return undefined;\n }\n }\n\n private mapPriorityToLinearPriority(priority?: TaskPriority): number {\n switch (priority) {\n case 'urgent':\n return 1;\n case 'high':\n return 2;\n case 'medium':\n return 3;\n case 'low':\n return 4;\n default:\n return 0;\n }\n }\n\n private loadMappings(): void {\n const mappingFile = join(\n this.projectRoot,\n '.stackmemory',\n 'linear-mappings.json'\n );\n if (existsSync(mappingFile)) {\n try {\n const data = JSON.parse(readFileSync(mappingFile, 'utf8'));\n this.mappings = new Map(Object.entries(data));\n } catch (error: unknown) {\n logger.error('Failed to load mappings:', error as Error);\n }\n }\n }\n\n private saveMappings(): void {\n const mappingFile = join(\n this.projectRoot,\n '.stackmemory',\n 'linear-mappings.json'\n );\n const data = Object.fromEntries(this.mappings);\n writeFileSync(mappingFile, JSON.stringify(data, null, 2));\n }\n\n private loadTaskPlan(): TaskPlan {\n const planFile = join(this.projectRoot, this.config.taskPlanFile!);\n if (existsSync(planFile)) {\n try {\n return JSON.parse(readFileSync(planFile, 'utf8'));\n } catch (error: unknown) {\n logger.error('Failed to load task plan:', error as Error);\n }\n }\n\n return {\n version: '1.0.0',\n lastUpdated: new Date(),\n phases: [],\n };\n }\n\n private saveTaskPlan(plan: TaskPlan): void {\n const planFile = join(this.projectRoot, this.config.taskPlanFile!);\n writeFileSync(planFile, JSON.stringify(plan, null, 2));\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Get last sync statistics\n */\n getLastSyncStats(): SyncStats | undefined {\n return this.lastSyncStats;\n }\n\n /**\n * Clear duplicate detector cache\n */\n clearCache(): void {\n if (this.duplicateDetector) {\n this.duplicateDetector.clearCache();\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,12 +5,16 @@ const __dirname = __pathDirname(__filename);
|
|
|
5
5
|
import { createHmac } from "crypto";
|
|
6
6
|
import { LinearSyncEngine } from "./sync.js";
|
|
7
7
|
import { LinearAuthManager } from "./auth.js";
|
|
8
|
+
import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
|
|
8
9
|
import { logger } from "../../core/monitoring/logger.js";
|
|
9
10
|
function getEnv(key, defaultValue) {
|
|
10
11
|
const value = process.env[key];
|
|
11
12
|
if (value === void 0) {
|
|
12
13
|
if (defaultValue !== void 0) return defaultValue;
|
|
13
|
-
throw new
|
|
14
|
+
throw new IntegrationError(
|
|
15
|
+
`Environment variable ${key} is required`,
|
|
16
|
+
ErrorCode.LINEAR_WEBHOOK_FAILED
|
|
17
|
+
);
|
|
14
18
|
}
|
|
15
19
|
return value;
|
|
16
20
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/integrations/linear/webhook-handler.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Linear Webhook Handler\n * Processes incoming webhooks from Linear to update local task store\n */\n\nimport { createHmac } from 'crypto';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport { LinearSyncEngine } from './sync.js';\nimport { LinearAuthManager } from './auth.js';\nimport { LinearClient } from './client.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport type { Request, Response } from 'express';\n// Type-safe environment variable access\nfunction getEnv(key: string, defaultValue?: string): string {\n const value = process.env[key];\n if (value === undefined) {\n if (defaultValue !== undefined) return defaultValue;\n throw new
|
|
5
|
-
"mappings": ";;;;AAKA,SAAS,kBAAkB;AAE3B,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAElC,SAAS,cAAc;AAGvB,SAAS,OAAO,KAAa,cAA+B;AAC1D,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,UAAU,QAAW;AACvB,QAAI,iBAAiB,OAAW,QAAO;AACvC,UAAM,IAAI,
|
|
4
|
+
"sourcesContent": ["/**\n * Linear Webhook Handler\n * Processes incoming webhooks from Linear to update local task store\n */\n\nimport { createHmac } from 'crypto';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport { LinearSyncEngine } from './sync.js';\nimport { LinearAuthManager } from './auth.js';\nimport { LinearClient } from './client.js';\nimport { IntegrationError, ErrorCode } from '../../core/errors/index.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport type { Request, Response } from 'express';\n// Type-safe environment variable access\nfunction getEnv(key: string, defaultValue?: string): string {\n const value = process.env[key];\n if (value === undefined) {\n if (defaultValue !== undefined) return defaultValue;\n throw new IntegrationError(\n `Environment variable ${key} is required`,\n ErrorCode.LINEAR_WEBHOOK_FAILED\n );\n }\n return value;\n}\n\nfunction getOptionalEnv(key: string): string | undefined {\n return process.env[key];\n}\n\nexport interface LinearWebhookPayload {\n action: 'create' | 'update' | 'remove';\n createdAt: string;\n data: {\n id: string;\n identifier: string;\n title: string;\n description?: string;\n state: {\n id: string;\n name: string;\n type: 'backlog' | 'unstarted' | 'started' | 'completed' | 'cancelled';\n };\n priority?: number;\n estimate?: number;\n assignee?: {\n id: string;\n name: string;\n email: string;\n };\n labels?: Array<{ id: string; name: string }>;\n updatedAt: string;\n url: string;\n };\n type: 'Issue';\n organizationId: string;\n webhookId: string;\n}\n\nexport class LinearWebhookHandler {\n private taskStore: LinearTaskManager;\n private syncEngine: LinearSyncEngine | null = null;\n private webhookSecret: string;\n\n constructor(taskStore: LinearTaskManager, webhookSecret: string) {\n this.taskStore = taskStore;\n this.webhookSecret = webhookSecret;\n\n // Initialize sync engine if API key is available\n if (process.env['LINEAR_API_KEY']) {\n const authManager = new LinearAuthManager();\n this.syncEngine = new LinearSyncEngine(taskStore, authManager, {\n enabled: true,\n direction: 'from_linear',\n autoSync: false,\n conflictResolution: 'linear_wins',\n });\n }\n }\n\n /**\n * Verify webhook signature\n */\n private verifySignature(payload: string, signature: string): boolean {\n const hmac = createHmac('sha256', this.webhookSecret);\n hmac.update(payload);\n const expectedSignature = hmac.digest('hex');\n return signature === expectedSignature;\n }\n\n /**\n * Handle incoming webhook from Linear\n */\n async handleWebhook(req: Request, res: Response): Promise<void> {\n try {\n // Get raw body for signature verification\n const rawBody = JSON.stringify(req.body);\n const signature = req.headers['linear-signature'] as string;\n\n // Verify signature\n if (!this.verifySignature(rawBody, signature)) {\n logger.error('Invalid webhook signature');\n res.status(401).json({ error: 'Invalid signature' });\n return;\n }\n\n const payload = req.body as LinearWebhookPayload;\n\n // Only process Issue webhooks\n if (payload.type !== 'Issue') {\n res.status(200).json({ message: 'Ignored non-issue webhook' });\n return;\n }\n\n // Process based on action\n switch (payload.action) {\n case 'create':\n await this.handleIssueCreate(payload);\n break;\n case 'update':\n await this.handleIssueUpdate(payload);\n break;\n case 'remove':\n await this.handleIssueRemove(payload);\n break;\n default:\n logger.warn(`Unknown webhook action: ${payload.action}`);\n }\n\n res.status(200).json({ message: 'Webhook processed successfully' });\n } catch (error: unknown) {\n logger.error('Failed to process webhook:', error as Error);\n res.status(500).json({ error: 'Failed to process webhook' });\n }\n }\n\n /**\n * Handle issue creation\n */\n private async handleIssueCreate(\n payload: LinearWebhookPayload\n ): Promise<void> {\n const issue = payload.data;\n\n // Check if task already exists locally\n const existingTasks = this.taskStore.getActiveTasks();\n const exists = existingTasks.some(\n (t) =>\n t.title.includes(issue.identifier) ||\n t.external_refs?.linear === issue.id\n );\n\n if (exists) {\n logger.info(`Task ${issue.identifier} already exists locally`);\n return;\n }\n\n // Create local task\n const taskId = this.taskStore.createTask({\n title: `[${issue.identifier}] ${issue.title}`,\n description: issue.description || '',\n priority: this.mapLinearPriorityToLocal(issue.priority),\n frameId: 'linear-webhook',\n tags: issue.labels?.map((l) => l.name) || ['linear'],\n estimatedEffort: issue.estimate ? issue.estimate * 60 : undefined,\n assignee: issue.assignee?.name,\n });\n\n // Update task status if not pending\n const status = this.mapLinearStateToLocalStatus(issue.state.type);\n if (status !== 'pending') {\n this.taskStore.updateTaskStatus(\n taskId,\n status,\n `Synced from Linear (${issue.state.name})`\n );\n }\n\n // Store Linear mapping\n await this.storeLinearMapping(taskId, issue.id, issue.identifier);\n\n logger.info(`Created task ${taskId} from Linear issue ${issue.identifier}`);\n }\n\n /**\n * Handle issue update\n */\n private async handleIssueUpdate(\n payload: LinearWebhookPayload\n ): Promise<void> {\n const issue = payload.data;\n\n // Find local task by Linear ID or identifier\n const tasks = this.taskStore.getActiveTasks();\n const localTask = tasks.find(\n (t) =>\n t.title.includes(issue.identifier) ||\n t.external_refs?.linear === issue.id\n );\n\n if (!localTask) {\n // Task doesn't exist locally, create it\n await this.handleIssueCreate(payload);\n return;\n }\n\n // Update task status\n const newStatus = this.mapLinearStateToLocalStatus(issue.state.type);\n if (newStatus !== localTask.status) {\n this.taskStore.updateTaskStatus(\n localTask.id,\n newStatus,\n `Updated from Linear (${issue.state.name})`\n );\n }\n\n // Update priority if changed\n const newPriority = this.mapLinearPriorityToLocal(issue.priority);\n if (newPriority !== localTask.priority) {\n // Note: Would need to add updateTaskPriority method to taskStore\n logger.info(\n `Priority changed for ${issue.identifier}: ${localTask.priority} -> ${newPriority}`\n );\n }\n\n logger.info(\n `Updated task ${localTask.id} from Linear issue ${issue.identifier}`\n );\n }\n\n /**\n * Handle issue removal\n */\n private async handleIssueRemove(\n payload: LinearWebhookPayload\n ): Promise<void> {\n const issue = payload.data;\n\n // Find and cancel local task\n const tasks = this.taskStore.getActiveTasks();\n const localTask = tasks.find(\n (t) =>\n t.title.includes(issue.identifier) ||\n t.external_refs?.linear === issue.id\n );\n\n if (localTask) {\n this.taskStore.updateTaskStatus(\n localTask.id,\n 'cancelled',\n `Removed in Linear`\n );\n logger.info(\n `Cancelled task ${localTask.id} (Linear issue ${issue.identifier} was removed)`\n );\n }\n }\n\n /**\n * Store Linear mapping for a task\n */\n private async storeLinearMapping(\n taskId: string,\n linearId: string,\n linearIdentifier: string\n ): Promise<void> {\n // This would update the linear-mappings.json file\n // For now, just log it\n logger.info(\n `Mapped task ${taskId} to Linear ${linearIdentifier} (${linearId})`\n );\n }\n\n /**\n * Map Linear priority to local priority\n */\n private mapLinearPriorityToLocal(\n priority?: number\n ): 'urgent' | 'high' | 'medium' | 'low' {\n if (!priority) return 'medium';\n switch (priority) {\n case 0:\n return 'urgent';\n case 1:\n return 'high';\n case 2:\n return 'medium';\n case 3:\n case 4:\n return 'low';\n default:\n return 'medium';\n }\n }\n\n /**\n * Map Linear state to local status\n */\n private mapLinearStateToLocalStatus(\n state: string\n ): 'pending' | 'in_progress' | 'completed' | 'cancelled' | 'blocked' {\n switch (state) {\n case 'backlog':\n case 'unstarted':\n return 'pending';\n case 'started':\n return 'in_progress';\n case 'completed':\n return 'completed';\n case 'cancelled':\n return 'cancelled';\n default:\n return 'pending';\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|