@stackmemoryai/stackmemory 0.5.28 → 0.5.30

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.
Files changed (48) hide show
  1. package/README.md +37 -16
  2. package/dist/cli/claude-sm.js +17 -5
  3. package/dist/cli/claude-sm.js.map +2 -2
  4. package/dist/core/database/batch-operations.js +29 -4
  5. package/dist/core/database/batch-operations.js.map +2 -2
  6. package/dist/core/database/connection-pool.js +13 -2
  7. package/dist/core/database/connection-pool.js.map +2 -2
  8. package/dist/core/database/migration-manager.js +130 -34
  9. package/dist/core/database/migration-manager.js.map +2 -2
  10. package/dist/core/database/paradedb-adapter.js +23 -7
  11. package/dist/core/database/paradedb-adapter.js.map +2 -2
  12. package/dist/core/database/query-router.js +8 -3
  13. package/dist/core/database/query-router.js.map +2 -2
  14. package/dist/core/database/sqlite-adapter.js +152 -33
  15. package/dist/core/database/sqlite-adapter.js.map +2 -2
  16. package/dist/hooks/session-summary.js +23 -2
  17. package/dist/hooks/session-summary.js.map +2 -2
  18. package/dist/hooks/sms-notify.js +47 -13
  19. package/dist/hooks/sms-notify.js.map +2 -2
  20. package/dist/integrations/linear/auth.js +34 -20
  21. package/dist/integrations/linear/auth.js.map +2 -2
  22. package/dist/integrations/linear/auto-sync.js +18 -8
  23. package/dist/integrations/linear/auto-sync.js.map +2 -2
  24. package/dist/integrations/linear/client.js +42 -9
  25. package/dist/integrations/linear/client.js.map +2 -2
  26. package/dist/integrations/linear/migration.js +94 -36
  27. package/dist/integrations/linear/migration.js.map +2 -2
  28. package/dist/integrations/linear/oauth-server.js +77 -34
  29. package/dist/integrations/linear/oauth-server.js.map +2 -2
  30. package/dist/integrations/linear/rest-client.js +13 -3
  31. package/dist/integrations/linear/rest-client.js.map +2 -2
  32. package/dist/integrations/linear/sync-service.js +18 -15
  33. package/dist/integrations/linear/sync-service.js.map +2 -2
  34. package/dist/integrations/linear/sync.js +12 -4
  35. package/dist/integrations/linear/sync.js.map +2 -2
  36. package/dist/integrations/linear/unified-sync.js +33 -8
  37. package/dist/integrations/linear/unified-sync.js.map +2 -2
  38. package/dist/integrations/linear/webhook-handler.js +5 -1
  39. package/dist/integrations/linear/webhook-handler.js.map +2 -2
  40. package/dist/integrations/linear/webhook-server.js +7 -7
  41. package/dist/integrations/linear/webhook-server.js.map +2 -2
  42. package/dist/integrations/linear/webhook.js +9 -2
  43. package/dist/integrations/linear/webhook.js.map +2 -2
  44. package/dist/integrations/mcp/schemas.js +147 -0
  45. package/dist/integrations/mcp/schemas.js.map +7 -0
  46. package/dist/integrations/mcp/server.js +19 -3
  47. package/dist/integrations/mcp/server.js.map +2 -2
  48. package/package.json +1 -1
@@ -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": ";;;;AAMA,SAAS,oBAAyD;AAClE,SAAS,+BAAqD;AAG9D,SAAS,cAAc;AAEvB,SAAS,YAAY,cAAc,eAAe,iBAAiB;AACnE,SAAS,MAAM,eAAe;AAC9B,SAAS,oBAAoB;AAoCtB,MAAM,yBAA4C;AAAA,EACvD,SAAS;AAAA,EACT,WAAW;AAAA,EACX,oBAAoB;AAAA,EACpB,8BAA8B;AAAA,EAC9B,eAAe;AAAA,EACf,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,kBAAkB;AACpB;AA2CO,MAAM,0BAA0B,aAAa;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAgC,oBAAI,IAAI;AAAA;AAAA,EACxC;AAAA,EACA,iBAAiB;AAAA,EAEzB,YACE,WACA,aACA,aACA,QACA;AACA,UAAM;AACN,SAAK,YAAY;AACjB,SAAK,cAAc;AACnB,SAAK,cAAc;AACnB,SAAK,SAAS,EAAE,GAAG,wBAAwB,GAAG,OAAO;AAGrD,SAAK,eAAe;AACpB,SAAK,oBAAoB;AAGzB,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,QAAI;AAEF,YAAM,QAAQ,MAAM,KAAK,YAAY,cAAc;AACnD,UAAI,CAAC,OAAO;AACV,cAAM,IAAI,MAAM,sEAAsE;AAAA,MACxF;AAGA,YAAM,UAAU,KAAK,YAAY,QAAQ;AACzC,WAAK,eAAe,IAAI,aAAa;AAAA,QACnC,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,QAAQ,KAAK,OAAO;AAAA,QACpB,gBAAgB,UACZ,YAAY;AACV,gBAAM,YAAY,MAAM,KAAK,YAAY,mBAAmB;AAC5D,iBAAO,UAAU;AAAA,QACnB,IACA;AAAA,MACN,CAAC;AAGD,UAAI,KAAK,OAAO,oBAAoB;AAClC,aAAK,oBAAoB,IAAI,wBAAwB,KAAK,YAAY;AAAA,MACxE;AAGA,UAAI,KAAK,OAAO,qBAAqB;AACnC,cAAM,KAAK,uBAAuB;AAAA,MACpC;AAEA,aAAO,KAAK,mCAAmC;AAAA,QAC7C,WAAW,KAAK,OAAO;AAAA,QACvB,oBAAoB,KAAK,OAAO;AAAA,QAChC,cAAc,KAAK,OAAO;AAAA,MAC5B,CAAC;AAAA,IACH,SAAS,OAAgB;AACvB,aAAO,MAAM,qCAAqC,KAAc;AAChE,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAA2B;AAC/B,QAAI,KAAK,gBAAgB;AACvB,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AAEA,SAAK,iBAAiB;AACtB,UAAM,YAAY,KAAK,IAAI;AAE3B,UAAM,QAAmB;AAAA,MACvB,UAAU,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,kBAAkB,EAAE;AAAA,MACpE,YAAY,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,EAAE;AAAA,MACjD,WAAW,CAAC;AAAA,MACZ,QAAQ,CAAC;AAAA,MACT,UAAU;AAAA,MACV,WAAW,KAAK,IAAI;AAAA,IACtB;AAEA,QAAI;AACF,WAAK,KAAK,gBAAgB,EAAE,QAAQ,KAAK,OAAO,CAAC;AAGjD,cAAQ,KAAK,OAAO,WAAW;AAAA,QAC7B,KAAK;AACH,gBAAM,KAAK,eAAe,KAAK;AAC/B,gBAAM,KAAK,aAAa,KAAK;AAC7B;AAAA,QACF,KAAK;AACH,gBAAM,KAAK,eAAe,KAAK;AAC/B;AAAA,QACF,KAAK;AACH,gBAAM,KAAK,aAAa,KAAK;AAC7B;AAAA,MACJ;AAGA,UAAI,KAAK,OAAO,qBAAqB;AACnC,cAAM,KAAK,eAAe,KAAK;AAAA,MACjC;AAGA,WAAK,aAAa;AAElB,YAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,WAAK,gBAAgB;AAErB,WAAK,KAAK,kBAAkB,EAAE,MAAM,CAAC;AACrC,aAAO,KAAK,0BAA0B;AAAA,QACpC,UAAU,GAAG,MAAM,QAAQ;AAAA,QAC3B,UAAU,MAAM;AAAA,QAChB,YAAY,MAAM;AAAA,QAClB,WAAW,MAAM,UAAU;AAAA,MAC7B,CAAC;AAED,aAAO;AAAA,IACT,SAAS,OAAgB;AACvB,YAAM,OAAO,KAAM,MAAgB,OAAO;AAC1C,YAAM,WAAW,KAAK,IAAI,IAAI;AAE9B,WAAK,KAAK,eAAe,EAAE,OAAO,MAAM,CAAC;AACzC,aAAO,MAAM,wBAAwB,KAAc;AAEnD,YAAM;AAAA,IACR,UAAE;AACA,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,eAAe,OAAiC;AAC5D,QAAI;AACF,aAAO,MAAM,wBAAwB;AAGrC,YAAM,SAAS,KAAK,OAAO,iBAAkB,MAAM,KAAK,iBAAiB;AAGzE,YAAM,SAAS,MAAM,KAAK,aAAa,UAAU;AAAA,QAC/C;AAAA,QACA,OAAO,KAAK,OAAO;AAAA,MACrB,CAAC;AAED,iBAAW,SAAS,QAAQ;AAC1B,YAAI;AACF,gBAAM,KAAK,MAAM,KAAK,OAAO,cAAc;AAG3C,gBAAM,cAAc,KAAK,wBAAwB,MAAM,EAAE;AAEzD,cAAI,aAAa;AAEf,kBAAM,YAAY,MAAM,KAAK,UAAU,QAAQ,WAAW;AAC1D,gBAAI,aAAa,KAAK,WAAW,WAAW,KAAK,GAAG;AAClD,oBAAM,KAAK,gBAAgB,WAAW,KAAK;AAC3C,oBAAM,WAAW;AAAA,YACnB,OAAO;AACL,oBAAM,WAAW;AAAA,YACnB;AAAA,UACF,OAAO;AAEL,kBAAM,KAAK,gBAAgB,KAAK;AAChC,kBAAM,WAAW;AAAA,UACnB;AAAA,QACF,SAAS,OAAgB;AACvB,gBAAM,OAAO,KAAK,wBAAwB,MAAM,UAAU,KAAM,MAAgB,OAAO,EAAE;AAAA,QAC3F;AAAA,MACF;AAAA,IACF,SAAS,OAAgB;AACvB,aAAO,MAAM,+BAA+B,KAAc;AAC1D,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aAAa,OAAiC;AAC1D,QAAI;AACF,aAAO,MAAM,sBAAsB;AAGnC,YAAM,QAAQ,MAAM,KAAK,UAAU,YAAY;AAC/C,YAAM,SAAS,KAAK,OAAO,iBAAkB,MAAM,KAAK,iBAAiB;AAEzE,iBAAW,QAAQ,OAAO;AACxB,YAAI;AACF,gBAAM,KAAK,MAAM,KAAK,OAAO,cAAc;AAG3C,gBAAM,WAAW,KAAK,SAAS,IAAI,KAAK,EAAE;AAE1C,cAAI,UAAU;AAEZ,kBAAM,cAAc,MAAM,KAAK,aAAa,SAAS,QAAQ;AAC7D,gBAAI,eAAe,KAAK,gBAAgB,MAAM,WAAW,GAAG;AAC1D,oBAAM,KAAK,kBAAkB,aAAa,IAAI;AAC9C,oBAAM,SAAS;AAAA,YACjB,OAAO;AACL,oBAAM,SAAS;AAAA,YACjB;AAAA,UACF,OAAO;AAEL,gBAAI,KAAK,OAAO,oBAAoB;AAClC,oBAAM,iBAAiB,MAAM,KAAK,kBAAkB;AAAA,gBAClD,KAAK;AAAA,gBACL;AAAA,cACF;AAEA,kBAAI,eAAe,eAAe,eAAe,eAAe;AAC9D,oBAAI,KAAK,OAAO,kBAAkB,iBAAiB;AAEjD,wBAAM,KAAK,oBAAoB,MAAM,eAAe,aAAa;AACjE,uBAAK,SAAS,IAAI,KAAK,IAAI,eAAe,cAAc,EAAE;AAC1D,wBAAM,SAAS;AAAA,gBACjB,WAAW,KAAK,OAAO,kBAAkB,QAAQ;AAC/C,wBAAM,SAAS;AACf;AAAA,gBACF;AAAA,cACF,OAAO;AAEL,sBAAM,KAAK,kBAAkB,MAAM,MAAM;AACzC,sBAAM,SAAS;AAAA,cACjB;AAAA,YACF,OAAO;AAEL,oBAAM,KAAK,kBAAkB,MAAM,MAAM;AACzC,oBAAM,SAAS;AAAA,YACjB;AAAA,UACF;AAAA,QACF,SAAS,OAAgB;AACvB,gBAAM,OAAO,KAAK,uBAAuB,KAAK,EAAE,KAAM,MAAgB,OAAO,EAAE;AAAA,QACjF;AAAA,MACF;AAAA,IACF,SAAS,OAAgB;AACvB,aAAO,MAAM,6BAA6B,KAAc;AACxD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,yBAAwC;AACpD,UAAM,WAAW,KAAK,KAAK,aAAa,KAAK,OAAO,YAAa;AACjE,UAAM,UAAU,QAAQ,QAAQ;AAGhC,QAAI,CAAC,WAAW,OAAO,GAAG;AACxB,gBAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,IACxC;AAGA,QAAI,CAAC,WAAW,QAAQ,KAAK,KAAK,OAAO,oBAAoB;AAC3D,YAAM,cAAwB;AAAA,QAC5B,SAAS;AAAA,QACT,aAAa,oBAAI,KAAK;AAAA,QACtB,QAAQ;AAAA,UACN;AAAA,YACE,MAAM;AAAA,YACN,aAAa;AAAA,YACb,OAAO,CAAC;AAAA,UACV;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,aAAa;AAAA,YACb,OAAO,CAAC;AAAA,UACV;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,aAAa;AAAA,YACb,OAAO,CAAC;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAEA,WAAK,aAAa,WAAW;AAC7B,aAAO,KAAK,6BAA6B,EAAE,MAAM,SAAS,CAAC;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,eAAe,OAAiC;AAC5D,QAAI,CAAC,KAAK,OAAO,oBAAqB;AAEtC,QAAI;AACF,YAAM,OAAO,KAAK,aAAa;AAC/B,YAAM,QAAQ,MAAM,KAAK,UAAU,YAAY;AAG/C,WAAK,SAAS;AAAA,QACZ;AAAA,UACE,MAAM;AAAA,UACN,aAAa;AAAA,UACb,OAAO,MACJ,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EACjC,IAAI,CAAC,OAAO;AAAA,YACX,IAAI,EAAE;AAAA,YACN,OAAO,EAAE;AAAA,YACT,UAAU,EAAE,YAAY;AAAA,YACxB,QAAQ,EAAE;AAAA,YACV,UAAU,KAAK,SAAS,IAAI,EAAE,EAAE;AAAA,UAClC,EAAE;AAAA,QACN;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,aAAa;AAAA,UACb,OAAO,MACJ,OAAO,CAAC,MAAM,EAAE,WAAW,aAAa,EACxC,IAAI,CAAC,OAAO;AAAA,YACX,IAAI,EAAE;AAAA,YACN,OAAO,EAAE;AAAA,YACT,UAAU,EAAE,YAAY;AAAA,YACxB,QAAQ,EAAE;AAAA,YACV,UAAU,KAAK,SAAS,IAAI,EAAE,EAAE;AAAA,UAClC,EAAE;AAAA,QACN;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,aAAa;AAAA,UACb,OAAO,MACJ,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EACjC,MAAM,GAAG,EACT,IAAI,CAAC,OAAO;AAAA,YACX,IAAI,EAAE;AAAA,YACN,OAAO,EAAE;AAAA,YACT,UAAU,EAAE,YAAY;AAAA,YACxB,QAAQ,EAAE;AAAA,YACV,UAAU,KAAK,SAAS,IAAI,EAAE,EAAE;AAAA,UAClC,EAAE;AAAA,QACN;AAAA,MACF;AAEA,WAAK,cAAc,oBAAI,KAAK;AAC5B,WAAK,aAAa,IAAI;AAGtB,WAAK,mBAAmB,MAAM,KAAK;AAAA,IACrC,SAAS,OAAgB;AACvB,aAAO,MAAM,+BAA+B,KAAc;AAAA,IAC5D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmB,MAAgB,OAAwB;AACjE,UAAM,aAAa,KAAK,KAAK,aAAa,gBAAgB,gBAAgB;AAE1E,QAAI,UAAU;AAAA;AAAA;AACd,eAAW,qBAAqB,KAAK,YAAY,eAAe,CAAC;AAAA;AACjE,eAAW,sBAAsB,MAAM,QAAQ;AAAA;AAAA;AAE/C,eAAW;AAAA;AAAA;AACX,eAAW;AAAA;AACX,eAAW,cAAc,MAAM,SAAS,OAAO;AAAA;AAC/C,eAAW,cAAc,MAAM,SAAS,OAAO;AAAA;AAC/C,eAAW,wBAAwB,MAAM,SAAS,gBAAgB;AAAA;AAClE,eAAW,cAAc,MAAM,SAAS,OAAO;AAAA;AAAA;AAE/C,eAAW;AAAA;AACX,eAAW,cAAc,MAAM,WAAW,OAAO;AAAA;AACjD,eAAW,cAAc,MAAM,WAAW,OAAO;AAAA;AACjD,eAAW,cAAc,MAAM,WAAW,OAAO;AAAA;AAAA;AAEjD,QAAI,MAAM,UAAU,SAAS,GAAG;AAC9B,iBAAW;AAAA;AACX,YAAM,UAAU,QAAQ,CAAC,MAAM;AAC7B,mBAAW,OAAO,EAAE,MAAM,OAAO,EAAE,MAAM,KAAK,EAAE,UAAU;AAAA;AAAA,MAC5D,CAAC;AACD,iBAAW;AAAA,IACb;AAEA,eAAW;AAAA;AAAA;AACX,SAAK,OAAO,QAAQ,CAAC,UAAU;AAC7B,iBAAW,OAAO,MAAM,IAAI,KAAK,MAAM,MAAM,MAAM;AAAA;AACnD,iBAAW,KAAK,MAAM,WAAW;AAAA;AAAA;AAEjC,UAAI,MAAM,MAAM,SAAS,GAAG;AAC1B,cAAM,MAAM,MAAM,GAAG,EAAE,EAAE,QAAQ,CAAC,SAAS;AACzC,gBAAM,aAAa,KAAK,WAAW,cAAc;AACjD,qBAAW,OAAO,KAAK,KAAK,KAAK,UAAU;AAAA;AAAA,QAC7C,CAAC;AAED,YAAI,MAAM,MAAM,SAAS,IAAI;AAC3B,qBAAW,aAAa,MAAM,MAAM,SAAS,EAAE;AAAA;AAAA,QACjD;AAAA,MACF;AACA,iBAAW;AAAA,IACb,CAAC;AAED,kBAAc,YAAY,OAAO;AACjC,WAAO,MAAM,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,mBAAoC;AAChD,UAAM,QAAQ,MAAM,KAAK,aAAa,SAAS;AAC/C,QAAI,MAAM,WAAW,GAAG;AACtB,YAAM,IAAI,MAAM,uBAAuB;AAAA,IACzC;AACA,WAAO,MAAM,CAAC,EAAG;AAAA,EACnB;AAAA,EAEQ,wBAAwB,UAAsC;AACpE,eAAW,CAAC,QAAQ,KAAK,KAAK,KAAK,UAAU;AAC3C,UAAI,UAAU,SAAU,QAAO;AAAA,IACjC;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,WAAW,WAAiB,aAAmC;AACrE,WACE,UAAU,UAAU,YAAY,SAChC,UAAU,iBAAiB,YAAY,eAAe,OACtD,KAAK,uBAAuB,YAAY,MAAM,IAAI,MAAM,UAAU;AAAA,EAEtE;AAAA,EAEQ,gBAAgB,MAAY,aAAmC;AACrE,WACE,KAAK,UAAU,YAAY,SAC3B,KAAK,iBAAiB,YAAY,eAAe,OACjD,KAAK,WAAW,KAAK,uBAAuB,YAAY,MAAM,IAAI;AAAA,EAEtE;AAAA,EAEA,MAAc,gBAAgB,OAAmC;AAC/D,UAAM,OAAO,MAAM,KAAK,UAAU,WAAW;AAAA,MAC3C,OAAO,MAAM;AAAA,MACb,aAAa,MAAM,eAAe;AAAA,MAClC,QAAQ,KAAK,uBAAuB,MAAM,MAAM,IAAI;AAAA,MACpD,UAAU,KAAK,4BAA4B,MAAM,QAAQ;AAAA,MACzD,UAAU;AAAA,QACR,QAAQ;AAAA,UACN,IAAI,MAAM;AAAA,UACV,YAAY,MAAM;AAAA,UAClB,KAAK,MAAM;AAAA,QACb;AAAA,MACF;AAAA,IACF,CAAC;AAED,SAAK,SAAS,IAAI,KAAK,IAAI,MAAM,EAAE;AAAA,EACrC;AAAA,EAEA,MAAc,gBAAgB,MAAY,OAAmC;AAC3E,UAAM,KAAK,UAAU,WAAW,KAAK,IAAI;AAAA,MACvC,OAAO,MAAM;AAAA,MACb,aAAa,MAAM,eAAe;AAAA,MAClC,QAAQ,KAAK,uBAAuB,MAAM,MAAM,IAAI;AAAA,MACpD,UAAU,KAAK,4BAA4B,MAAM,QAAQ;AAAA,IAC3D,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,kBAAkB,MAAY,QAA+B;AACzE,UAAM,QAAgC;AAAA,MACpC,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK,eAAe;AAAA,MACjC;AAAA,MACA,UAAU,KAAK,4BAA4B,KAAK,QAAQ;AAAA,IAC1D;AAEA,UAAM,QAAQ,MAAM,KAAK,aAAa,YAAY,KAAK;AACvD,SAAK,SAAS,IAAI,KAAK,IAAI,MAAM,EAAE;AAGnC,UAAM,KAAK,UAAU,WAAW,KAAK,IAAI;AAAA,MACvC,UAAU;AAAA,QACR,GAAG,KAAK;AAAA,QACR,QAAQ;AAAA,UACN,IAAI,MAAM;AAAA,UACV,YAAY,MAAM;AAAA,UAClB,KAAK,MAAM;AAAA,QACb;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,kBAAkB,OAAoB,MAA2B;AAC7E,UAAM,KAAK,aAAa,YAAY,MAAM,IAAI;AAAA,MAC5C,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK;AAAA,MAClB,UAAU,KAAK,4BAA4B,KAAK,QAAQ;AAAA,IAC1D,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,oBAAoB,MAAY,eAA2C;AACvF,UAAM,KAAK,kBAAkB;AAAA,MAC3B;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,qBAAqB,KAAK,EAAE;AAAA,WAAa,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,IACnE;AAAA,EACF;AAAA,EAEQ,uBAAuB,OAA2B;AACxD,YAAQ,MAAM,YAAY,GAAG;AAAA,MAC3B,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEQ,4BAA4B,UAA6C;AAC/E,YAAQ,UAAU;AAAA,MAChB,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEQ,4BAA4B,UAAiC;AACnE,YAAQ,UAAU;AAAA,MAChB,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEQ,eAAqB;AAC3B,UAAM,cAAc,KAAK,KAAK,aAAa,gBAAgB,sBAAsB;AACjF,QAAI,WAAW,WAAW,GAAG;AAC3B,UAAI;AACF,cAAM,OAAO,KAAK,MAAM,aAAa,aAAa,MAAM,CAAC;AACzD,aAAK,WAAW,IAAI,IAAI,OAAO,QAAQ,IAAI,CAAC;AAAA,MAC9C,SAAS,OAAgB;AACvB,eAAO,MAAM,4BAA4B,KAAc;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,eAAqB;AAC3B,UAAM,cAAc,KAAK,KAAK,aAAa,gBAAgB,sBAAsB;AACjF,UAAM,OAAO,OAAO,YAAY,KAAK,QAAQ;AAC7C,kBAAc,aAAa,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,EAC1D;AAAA,EAEQ,eAAyB;AAC/B,UAAM,WAAW,KAAK,KAAK,aAAa,KAAK,OAAO,YAAa;AACjE,QAAI,WAAW,QAAQ,GAAG;AACxB,UAAI;AACF,eAAO,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AAAA,MAClD,SAAS,OAAgB;AACvB,eAAO,MAAM,6BAA6B,KAAc;AAAA,MAC1D;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,aAAa,oBAAI,KAAK;AAAA,MACtB,QAAQ,CAAC;AAAA,IACX;AAAA,EACF;AAAA,EAEQ,aAAa,MAAsB;AACzC,UAAM,WAAW,KAAK,KAAK,aAAa,KAAK,OAAO,YAAa;AACjE,kBAAc,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,EACvD;AAAA,EAEQ,MAAM,IAA2B;AACvC,WAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAKA,mBAA0C;AACxC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,QAAI,KAAK,mBAAmB;AAC1B,WAAK,kBAAkB,WAAW;AAAA,IACpC;AAAA,EACF;AACF;",
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 Error(`Environment variable ${key} is required`);
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 Error(`Environment variable ${key} is required`);\n }\n return value;\n}\n\nfunction getOptionalEnv(key: string): string | undefined {\n return process.env[key];\n}\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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",
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
  }
@@ -6,13 +6,17 @@ const __dirname = __pathDirname(__filename);
6
6
  import express from "express";
7
7
  import crypto from "crypto";
8
8
  import { LinearSyncService } from "./sync-service.js";
9
+ import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
9
10
  import { logger } from "../../core/monitoring/logger.js";
10
11
  import chalk from "chalk";
11
12
  function getEnv(key, defaultValue) {
12
13
  const value = process.env[key];
13
14
  if (value === void 0) {
14
15
  if (defaultValue !== void 0) return defaultValue;
15
- throw new Error(`Environment variable ${key} is required`);
16
+ throw new IntegrationError(
17
+ `Environment variable ${key} is required`,
18
+ ErrorCode.LINEAR_WEBHOOK_FAILED
19
+ );
16
20
  }
17
21
  return value;
18
22
  }
@@ -72,9 +76,7 @@ class LinearWebhookServer {
72
76
  return res.status(401).json({ error: "Unauthorized" });
73
77
  }
74
78
  const payload = JSON.parse(req.body.toString());
75
- logger.info(
76
- `Received webhook: ${payload.type} - ${payload.action}`
77
- );
79
+ logger.info(`Received webhook: ${payload.type} - ${payload.action}`);
78
80
  this.eventQueue.push(payload);
79
81
  this.processQueue();
80
82
  return res.status(200).json({
@@ -137,9 +139,7 @@ class LinearWebhookServer {
137
139
  const issue = data;
138
140
  switch (action) {
139
141
  case "create":
140
- logger.info(
141
- `New issue created: ${issue.identifier} - ${issue.title}`
142
- );
142
+ logger.info(`New issue created: ${issue.identifier} - ${issue.title}`);
143
143
  await this.syncService.syncIssueToLocal(issue);
144
144
  break;
145
145
  case "update":
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/integrations/linear/webhook-server.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n\nimport express from 'express';\nimport crypto from 'crypto';\nimport http from 'http';\nimport {\n LinearWebhookPayload,\n LinearIssue,\n LinearComment,\n LinearProject,\n} from './types.js';\nimport { LinearSyncService } from './sync-service.js';\nimport { LinearIssue as ClientLinearIssue } from './client.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport chalk from 'chalk';\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 Error(`Environment variable ${key} is required`);\n }\n return value;\n}\n\nfunction getOptionalEnv(key: string): string | undefined {\n return process.env[key];\n}\n\n\nexport interface WebhookServerConfig {\n port?: number;\n host?: string;\n webhookSecret?: string;\n maxPayloadSize?: string;\n rateLimit?: {\n windowMs?: number;\n max?: number;\n };\n}\n\nexport class LinearWebhookServer {\n private app: express.Application;\n private server: http.Server | null = null;\n // Using singleton logger from monitoring\n private syncService: LinearSyncService;\n private config: WebhookServerConfig;\n private eventQueue: LinearWebhookPayload[] = [];\n private isProcessing = false;\n\n constructor(config?: WebhookServerConfig) {\n this.app = express();\n // Use singleton logger\n this.syncService = new LinearSyncService();\n\n this.config = {\n port: config?.port || parseInt(process.env['WEBHOOK_PORT'] || '3456'),\n host: config?.host || process.env['WEBHOOK_HOST'] || 'localhost',\n webhookSecret: config?.webhookSecret || process.env['LINEAR_WEBHOOK_SECRET'],\n maxPayloadSize: config?.maxPayloadSize || '10mb',\n rateLimit: {\n windowMs: config?.rateLimit?.windowMs || 60000,\n max: config?.rateLimit?.max || 100,\n },\n };\n\n this.setupMiddleware();\n this.setupRoutes();\n }\n\n private setupMiddleware(): void {\n this.app.use(\n express.raw({\n type: 'application/json',\n limit: this.config.maxPayloadSize,\n })\n );\n\n this.app.use((req, res, next) => {\n res.setHeader('X-Powered-By', 'StackMemory');\n next();\n });\n }\n\n private setupRoutes(): void {\n this.app.get('/health', (req, res) => {\n res.json({\n status: 'healthy',\n service: 'linear-webhook',\n timestamp: new Date().toISOString(),\n queue: this.eventQueue.length,\n processing: this.isProcessing,\n });\n });\n\n this.app.post('/webhook/linear', async (req, res) => {\n try {\n if (!this.verifyWebhookSignature(req)) {\n logger.warn('Invalid webhook signature');\n return res.status(401).json({ error: 'Unauthorized' });\n }\n\n const payload = JSON.parse(req.body.toString()) as LinearWebhookPayload;\n\n logger.info(\n `Received webhook: ${payload.type} - ${payload.action}`\n );\n\n this.eventQueue.push(payload);\n this.processQueue();\n\n return res.status(200).json({\n status: 'accepted',\n queued: true,\n });\n } catch (error: unknown) {\n logger.error('Webhook processing error:', error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n this.app.use((req, res) => {\n res.status(404).json({ error: 'Not found' });\n });\n }\n\n private verifyWebhookSignature(req: express.Request): boolean {\n if (!this.config.webhookSecret) {\n logger.warn('No webhook secret configured, accepting all webhooks');\n return true;\n }\n\n const signature = req.headers['linear-signature'] as string;\n if (!signature) {\n return false;\n }\n\n const hash = crypto\n .createHmac('sha256', this.config.webhookSecret)\n .update(req.body)\n .digest('hex');\n\n return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hash));\n }\n\n private async processQueue(): Promise<void> {\n if (this.isProcessing || this.eventQueue.length === 0) {\n return;\n }\n\n this.isProcessing = true;\n\n while (this.eventQueue.length > 0) {\n const event = this.eventQueue.shift()!;\n\n try {\n await this.handleWebhookEvent(event);\n } catch (error: unknown) {\n logger.error(`Failed to process event: ${event.type}`, error);\n }\n }\n\n this.isProcessing = false;\n }\n\n private async handleWebhookEvent(\n payload: LinearWebhookPayload\n ): Promise<void> {\n const { type, action, data } = payload;\n\n switch (type) {\n case 'Issue':\n await this.handleIssueEvent(action, data as LinearIssue);\n break;\n case 'Comment':\n await this.handleCommentEvent(action, data as LinearComment);\n break;\n case 'Project':\n await this.handleProjectEvent(action, data as LinearProject);\n break;\n default:\n logger.debug(`Unhandled event type: ${type}`);\n }\n }\n\n private async handleIssueEvent(\n action: string,\n data: LinearIssue\n ): Promise<void> {\n const issue = data as ClientLinearIssue;\n\n switch (action) {\n case 'create':\n logger.info(\n `New issue created: ${issue.identifier} - ${issue.title}`\n );\n await this.syncService.syncIssueToLocal(issue);\n break;\n case 'update':\n logger.info(`Issue updated: ${issue.identifier} - ${issue.title}`);\n await this.syncService.syncIssueToLocal(issue);\n break;\n case 'remove':\n logger.info(`Issue removed: ${issue.identifier}`);\n await this.syncService.removeLocalIssue(issue.identifier);\n break;\n default:\n logger.debug(`Unhandled issue action: ${action}`);\n }\n }\n\n private async handleCommentEvent(\n action: string,\n data: LinearComment\n ): Promise<void> {\n logger.debug(`Comment event: ${action}`, { issueId: data.issue?.id });\n }\n\n private async handleProjectEvent(\n action: string,\n data: LinearProject\n ): Promise<void> {\n logger.debug(`Project event: ${action}`, { projectId: data.id });\n }\n\n public async start(): Promise<void> {\n return new Promise((resolve) => {\n this.server = this.app.listen(\n this.config.port!,\n this.config.host!,\n () => {\n console.log(\n chalk.green('\u2713') + chalk.bold(' Linear Webhook Server Started')\n );\n console.log(\n chalk.cyan(' URL: ') +\n `http://${this.config.host}:${this.config.port}/webhook/linear`\n );\n console.log(\n chalk.cyan(' Health: ') +\n `http://${this.config.host}:${this.config.port}/health`\n );\n\n if (!this.config.webhookSecret) {\n console.log(\n chalk.yellow(\n ' \u26A0 Warning: No webhook secret configured (insecure)'\n )\n );\n }\n\n resolve();\n }\n );\n });\n }\n\n public async stop(): Promise<void> {\n return new Promise((resolve) => {\n if (this.server) {\n this.server.close(() => {\n logger.info('Webhook server stopped');\n resolve();\n });\n } else {\n resolve();\n }\n });\n }\n}\n\n// Standalone execution support\nif (process.argv[1] === new URL(import.meta.url).pathname) {\n const server = new LinearWebhookServer();\n\n server.start().catch((error) => {\n console.error(chalk.red('Failed to start webhook server:'), error);\n process.exit(1);\n });\n\n process.on('SIGINT', async () => {\n console.log(chalk.yellow('\\n\\nShutting down webhook server...'));\n await server.stop();\n process.exit(0);\n });\n}\n"],
5
- "mappings": ";;;;;AAEA,OAAO,aAAa;AACpB,OAAO,YAAY;AAQnB,SAAS,yBAAyB;AAElC,SAAS,cAAc;AACvB,OAAO,WAAW;AAElB,SAAS,OAAO,KAAa,cAA+B;AAC1D,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,UAAU,QAAW;AACvB,QAAI,iBAAiB,OAAW,QAAO;AACvC,UAAM,IAAI,MAAM,wBAAwB,GAAG,cAAc;AAAA,EAC3D;AACA,SAAO;AACT;AAEA,SAAS,eAAe,KAAiC;AACvD,SAAO,QAAQ,IAAI,GAAG;AACxB;AAcO,MAAM,oBAAoB;AAAA,EACvB;AAAA,EACA,SAA6B;AAAA;AAAA,EAE7B;AAAA,EACA;AAAA,EACA,aAAqC,CAAC;AAAA,EACtC,eAAe;AAAA,EAEvB,YAAY,QAA8B;AACxC,SAAK,MAAM,QAAQ;AAEnB,SAAK,cAAc,IAAI,kBAAkB;AAEzC,SAAK,SAAS;AAAA,MACZ,MAAM,QAAQ,QAAQ,SAAS,QAAQ,IAAI,cAAc,KAAK,MAAM;AAAA,MACpE,MAAM,QAAQ,QAAQ,QAAQ,IAAI,cAAc,KAAK;AAAA,MACrD,eAAe,QAAQ,iBAAiB,QAAQ,IAAI,uBAAuB;AAAA,MAC3E,gBAAgB,QAAQ,kBAAkB;AAAA,MAC1C,WAAW;AAAA,QACT,UAAU,QAAQ,WAAW,YAAY;AAAA,QACzC,KAAK,QAAQ,WAAW,OAAO;AAAA,MACjC;AAAA,IACF;AAEA,SAAK,gBAAgB;AACrB,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,kBAAwB;AAC9B,SAAK,IAAI;AAAA,MACP,QAAQ,IAAI;AAAA,QACV,MAAM;AAAA,QACN,OAAO,KAAK,OAAO;AAAA,MACrB,CAAC;AAAA,IACH;AAEA,SAAK,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;AAC/B,UAAI,UAAU,gBAAgB,aAAa;AAC3C,WAAK;AAAA,IACP,CAAC;AAAA,EACH;AAAA,EAEQ,cAAoB;AAC1B,SAAK,IAAI,IAAI,WAAW,CAAC,KAAK,QAAQ;AACpC,UAAI,KAAK;AAAA,QACP,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,OAAO,KAAK,WAAW;AAAA,QACvB,YAAY,KAAK;AAAA,MACnB,CAAC;AAAA,IACH,CAAC;AAED,SAAK,IAAI,KAAK,mBAAmB,OAAO,KAAK,QAAQ;AACnD,UAAI;AACF,YAAI,CAAC,KAAK,uBAAuB,GAAG,GAAG;AACrC,iBAAO,KAAK,2BAA2B;AACvC,iBAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,QACvD;AAEA,cAAM,UAAU,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC;AAE9C,eAAO;AAAA,UACL,qBAAqB,QAAQ,IAAI,MAAM,QAAQ,MAAM;AAAA,QACvD;AAEA,aAAK,WAAW,KAAK,OAAO;AAC5B,aAAK,aAAa;AAElB,eAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,UAC1B,QAAQ;AAAA,UACR,QAAQ;AAAA,QACV,CAAC;AAAA,MACH,SAAS,OAAgB;AACvB,eAAO,MAAM,6BAA6B,KAAK;AAC/C,eAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,MAChE;AAAA,IACF,CAAC;AAED,SAAK,IAAI,IAAI,CAAC,KAAK,QAAQ;AACzB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,IAC7C,CAAC;AAAA,EACH;AAAA,EAEQ,uBAAuB,KAA+B;AAC5D,QAAI,CAAC,KAAK,OAAO,eAAe;AAC9B,aAAO,KAAK,sDAAsD;AAClE,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,IAAI,QAAQ,kBAAkB;AAChD,QAAI,CAAC,WAAW;AACd,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,OACV,WAAW,UAAU,KAAK,OAAO,aAAa,EAC9C,OAAO,IAAI,IAAI,EACf,OAAO,KAAK;AAEf,WAAO,OAAO,gBAAgB,OAAO,KAAK,SAAS,GAAG,OAAO,KAAK,IAAI,CAAC;AAAA,EACzE;AAAA,EAEA,MAAc,eAA8B;AAC1C,QAAI,KAAK,gBAAgB,KAAK,WAAW,WAAW,GAAG;AACrD;AAAA,IACF;AAEA,SAAK,eAAe;AAEpB,WAAO,KAAK,WAAW,SAAS,GAAG;AACjC,YAAM,QAAQ,KAAK,WAAW,MAAM;AAEpC,UAAI;AACF,cAAM,KAAK,mBAAmB,KAAK;AAAA,MACrC,SAAS,OAAgB;AACvB,eAAO,MAAM,4BAA4B,MAAM,IAAI,IAAI,KAAK;AAAA,MAC9D;AAAA,IACF;AAEA,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,MAAc,mBACZ,SACe;AACf,UAAM,EAAE,MAAM,QAAQ,KAAK,IAAI;AAE/B,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,cAAM,KAAK,iBAAiB,QAAQ,IAAmB;AACvD;AAAA,MACF,KAAK;AACH,cAAM,KAAK,mBAAmB,QAAQ,IAAqB;AAC3D;AAAA,MACF,KAAK;AACH,cAAM,KAAK,mBAAmB,QAAQ,IAAqB;AAC3D;AAAA,MACF;AACE,eAAO,MAAM,yBAAyB,IAAI,EAAE;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAc,iBACZ,QACA,MACe;AACf,UAAM,QAAQ;AAEd,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO;AAAA,UACL,sBAAsB,MAAM,UAAU,MAAM,MAAM,KAAK;AAAA,QACzD;AACA,cAAM,KAAK,YAAY,iBAAiB,KAAK;AAC7C;AAAA,MACF,KAAK;AACH,eAAO,KAAK,kBAAkB,MAAM,UAAU,MAAM,MAAM,KAAK,EAAE;AACjE,cAAM,KAAK,YAAY,iBAAiB,KAAK;AAC7C;AAAA,MACF,KAAK;AACH,eAAO,KAAK,kBAAkB,MAAM,UAAU,EAAE;AAChD,cAAM,KAAK,YAAY,iBAAiB,MAAM,UAAU;AACxD;AAAA,MACF;AACE,eAAO,MAAM,2BAA2B,MAAM,EAAE;AAAA,IACpD;AAAA,EACF;AAAA,EAEA,MAAc,mBACZ,QACA,MACe;AACf,WAAO,MAAM,kBAAkB,MAAM,IAAI,EAAE,SAAS,KAAK,OAAO,GAAG,CAAC;AAAA,EACtE;AAAA,EAEA,MAAc,mBACZ,QACA,MACe;AACf,WAAO,MAAM,kBAAkB,MAAM,IAAI,EAAE,WAAW,KAAK,GAAG,CAAC;AAAA,EACjE;AAAA,EAEA,MAAa,QAAuB;AAClC,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,WAAK,SAAS,KAAK,IAAI;AAAA,QACrB,KAAK,OAAO;AAAA,QACZ,KAAK,OAAO;AAAA,QACZ,MAAM;AACJ,kBAAQ;AAAA,YACN,MAAM,MAAM,QAAG,IAAI,MAAM,KAAK,gCAAgC;AAAA,UAChE;AACA,kBAAQ;AAAA,YACN,MAAM,KAAK,SAAS,IAClB,UAAU,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,IAAI;AAAA,UAClD;AACA,kBAAQ;AAAA,YACN,MAAM,KAAK,YAAY,IACrB,UAAU,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,IAAI;AAAA,UAClD;AAEA,cAAI,CAAC,KAAK,OAAO,eAAe;AAC9B,oBAAQ;AAAA,cACN,MAAM;AAAA,gBACJ;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAEA,kBAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAa,OAAsB;AACjC,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAI,KAAK,QAAQ;AACf,aAAK,OAAO,MAAM,MAAM;AACtB,iBAAO,KAAK,wBAAwB;AACpC,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH,OAAO;AACL,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAGA,IAAI,QAAQ,KAAK,CAAC,MAAM,IAAI,IAAI,YAAY,GAAG,EAAE,UAAU;AACzD,QAAM,SAAS,IAAI,oBAAoB;AAEvC,SAAO,MAAM,EAAE,MAAM,CAAC,UAAU;AAC9B,YAAQ,MAAM,MAAM,IAAI,iCAAiC,GAAG,KAAK;AACjE,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AAED,UAAQ,GAAG,UAAU,YAAY;AAC/B,YAAQ,IAAI,MAAM,OAAO,qCAAqC,CAAC;AAC/D,UAAM,OAAO,KAAK;AAClB,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;",
4
+ "sourcesContent": ["#!/usr/bin/env node\n\nimport express from 'express';\nimport crypto from 'crypto';\nimport http from 'http';\nimport {\n LinearWebhookPayload,\n LinearIssue,\n LinearComment,\n LinearProject,\n} from './types.js';\nimport { LinearSyncService } from './sync-service.js';\nimport { LinearIssue as ClientLinearIssue } from './client.js';\nimport { IntegrationError, ErrorCode } from '../../core/errors/index.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport chalk from 'chalk';\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 WebhookServerConfig {\n port?: number;\n host?: string;\n webhookSecret?: string;\n maxPayloadSize?: string;\n rateLimit?: {\n windowMs?: number;\n max?: number;\n };\n}\n\nexport class LinearWebhookServer {\n private app: express.Application;\n private server: http.Server | null = null;\n // Using singleton logger from monitoring\n private syncService: LinearSyncService;\n private config: WebhookServerConfig;\n private eventQueue: LinearWebhookPayload[] = [];\n private isProcessing = false;\n\n constructor(config?: WebhookServerConfig) {\n this.app = express();\n // Use singleton logger\n this.syncService = new LinearSyncService();\n\n this.config = {\n port: config?.port || parseInt(process.env['WEBHOOK_PORT'] || '3456'),\n host: config?.host || process.env['WEBHOOK_HOST'] || 'localhost',\n webhookSecret:\n config?.webhookSecret || process.env['LINEAR_WEBHOOK_SECRET'],\n maxPayloadSize: config?.maxPayloadSize || '10mb',\n rateLimit: {\n windowMs: config?.rateLimit?.windowMs || 60000,\n max: config?.rateLimit?.max || 100,\n },\n };\n\n this.setupMiddleware();\n this.setupRoutes();\n }\n\n private setupMiddleware(): void {\n this.app.use(\n express.raw({\n type: 'application/json',\n limit: this.config.maxPayloadSize,\n })\n );\n\n this.app.use((req, res, next) => {\n res.setHeader('X-Powered-By', 'StackMemory');\n next();\n });\n }\n\n private setupRoutes(): void {\n this.app.get('/health', (req, res) => {\n res.json({\n status: 'healthy',\n service: 'linear-webhook',\n timestamp: new Date().toISOString(),\n queue: this.eventQueue.length,\n processing: this.isProcessing,\n });\n });\n\n this.app.post('/webhook/linear', async (req, res) => {\n try {\n if (!this.verifyWebhookSignature(req)) {\n logger.warn('Invalid webhook signature');\n return res.status(401).json({ error: 'Unauthorized' });\n }\n\n const payload = JSON.parse(req.body.toString()) as LinearWebhookPayload;\n\n logger.info(`Received webhook: ${payload.type} - ${payload.action}`);\n\n this.eventQueue.push(payload);\n this.processQueue();\n\n return res.status(200).json({\n status: 'accepted',\n queued: true,\n });\n } catch (error: unknown) {\n logger.error('Webhook processing error:', error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n this.app.use((req, res) => {\n res.status(404).json({ error: 'Not found' });\n });\n }\n\n private verifyWebhookSignature(req: express.Request): boolean {\n if (!this.config.webhookSecret) {\n logger.warn('No webhook secret configured, accepting all webhooks');\n return true;\n }\n\n const signature = req.headers['linear-signature'] as string;\n if (!signature) {\n return false;\n }\n\n const hash = crypto\n .createHmac('sha256', this.config.webhookSecret)\n .update(req.body)\n .digest('hex');\n\n return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hash));\n }\n\n private async processQueue(): Promise<void> {\n if (this.isProcessing || this.eventQueue.length === 0) {\n return;\n }\n\n this.isProcessing = true;\n\n while (this.eventQueue.length > 0) {\n const event = this.eventQueue.shift()!;\n\n try {\n await this.handleWebhookEvent(event);\n } catch (error: unknown) {\n logger.error(`Failed to process event: ${event.type}`, error);\n }\n }\n\n this.isProcessing = false;\n }\n\n private async handleWebhookEvent(\n payload: LinearWebhookPayload\n ): Promise<void> {\n const { type, action, data } = payload;\n\n switch (type) {\n case 'Issue':\n await this.handleIssueEvent(action, data as LinearIssue);\n break;\n case 'Comment':\n await this.handleCommentEvent(action, data as LinearComment);\n break;\n case 'Project':\n await this.handleProjectEvent(action, data as LinearProject);\n break;\n default:\n logger.debug(`Unhandled event type: ${type}`);\n }\n }\n\n private async handleIssueEvent(\n action: string,\n data: LinearIssue\n ): Promise<void> {\n const issue = data as ClientLinearIssue;\n\n switch (action) {\n case 'create':\n logger.info(`New issue created: ${issue.identifier} - ${issue.title}`);\n await this.syncService.syncIssueToLocal(issue);\n break;\n case 'update':\n logger.info(`Issue updated: ${issue.identifier} - ${issue.title}`);\n await this.syncService.syncIssueToLocal(issue);\n break;\n case 'remove':\n logger.info(`Issue removed: ${issue.identifier}`);\n await this.syncService.removeLocalIssue(issue.identifier);\n break;\n default:\n logger.debug(`Unhandled issue action: ${action}`);\n }\n }\n\n private async handleCommentEvent(\n action: string,\n data: LinearComment\n ): Promise<void> {\n logger.debug(`Comment event: ${action}`, { issueId: data.issue?.id });\n }\n\n private async handleProjectEvent(\n action: string,\n data: LinearProject\n ): Promise<void> {\n logger.debug(`Project event: ${action}`, { projectId: data.id });\n }\n\n public async start(): Promise<void> {\n return new Promise((resolve) => {\n this.server = this.app.listen(\n this.config.port!,\n this.config.host!,\n () => {\n console.log(\n chalk.green('\u2713') + chalk.bold(' Linear Webhook Server Started')\n );\n console.log(\n chalk.cyan(' URL: ') +\n `http://${this.config.host}:${this.config.port}/webhook/linear`\n );\n console.log(\n chalk.cyan(' Health: ') +\n `http://${this.config.host}:${this.config.port}/health`\n );\n\n if (!this.config.webhookSecret) {\n console.log(\n chalk.yellow(\n ' \u26A0 Warning: No webhook secret configured (insecure)'\n )\n );\n }\n\n resolve();\n }\n );\n });\n }\n\n public async stop(): Promise<void> {\n return new Promise((resolve) => {\n if (this.server) {\n this.server.close(() => {\n logger.info('Webhook server stopped');\n resolve();\n });\n } else {\n resolve();\n }\n });\n }\n}\n\n// Standalone execution support\nif (process.argv[1] === new URL(import.meta.url).pathname) {\n const server = new LinearWebhookServer();\n\n server.start().catch((error) => {\n console.error(chalk.red('Failed to start webhook server:'), error);\n process.exit(1);\n });\n\n process.on('SIGINT', async () => {\n console.log(chalk.yellow('\\n\\nShutting down webhook server...'));\n await server.stop();\n process.exit(0);\n });\n}\n"],
5
+ "mappings": ";;;;;AAEA,OAAO,aAAa;AACpB,OAAO,YAAY;AAQnB,SAAS,yBAAyB;AAElC,SAAS,kBAAkB,iBAAiB;AAC5C,SAAS,cAAc;AACvB,OAAO,WAAW;AAElB,SAAS,OAAO,KAAa,cAA+B;AAC1D,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,UAAU,QAAW;AACvB,QAAI,iBAAiB,OAAW,QAAO;AACvC,UAAM,IAAI;AAAA,MACR,wBAAwB,GAAG;AAAA,MAC3B,UAAU;AAAA,IACZ;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,eAAe,KAAiC;AACvD,SAAO,QAAQ,IAAI,GAAG;AACxB;AAaO,MAAM,oBAAoB;AAAA,EACvB;AAAA,EACA,SAA6B;AAAA;AAAA,EAE7B;AAAA,EACA;AAAA,EACA,aAAqC,CAAC;AAAA,EACtC,eAAe;AAAA,EAEvB,YAAY,QAA8B;AACxC,SAAK,MAAM,QAAQ;AAEnB,SAAK,cAAc,IAAI,kBAAkB;AAEzC,SAAK,SAAS;AAAA,MACZ,MAAM,QAAQ,QAAQ,SAAS,QAAQ,IAAI,cAAc,KAAK,MAAM;AAAA,MACpE,MAAM,QAAQ,QAAQ,QAAQ,IAAI,cAAc,KAAK;AAAA,MACrD,eACE,QAAQ,iBAAiB,QAAQ,IAAI,uBAAuB;AAAA,MAC9D,gBAAgB,QAAQ,kBAAkB;AAAA,MAC1C,WAAW;AAAA,QACT,UAAU,QAAQ,WAAW,YAAY;AAAA,QACzC,KAAK,QAAQ,WAAW,OAAO;AAAA,MACjC;AAAA,IACF;AAEA,SAAK,gBAAgB;AACrB,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,kBAAwB;AAC9B,SAAK,IAAI;AAAA,MACP,QAAQ,IAAI;AAAA,QACV,MAAM;AAAA,QACN,OAAO,KAAK,OAAO;AAAA,MACrB,CAAC;AAAA,IACH;AAEA,SAAK,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;AAC/B,UAAI,UAAU,gBAAgB,aAAa;AAC3C,WAAK;AAAA,IACP,CAAC;AAAA,EACH;AAAA,EAEQ,cAAoB;AAC1B,SAAK,IAAI,IAAI,WAAW,CAAC,KAAK,QAAQ;AACpC,UAAI,KAAK;AAAA,QACP,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,OAAO,KAAK,WAAW;AAAA,QACvB,YAAY,KAAK;AAAA,MACnB,CAAC;AAAA,IACH,CAAC;AAED,SAAK,IAAI,KAAK,mBAAmB,OAAO,KAAK,QAAQ;AACnD,UAAI;AACF,YAAI,CAAC,KAAK,uBAAuB,GAAG,GAAG;AACrC,iBAAO,KAAK,2BAA2B;AACvC,iBAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,QACvD;AAEA,cAAM,UAAU,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC;AAE9C,eAAO,KAAK,qBAAqB,QAAQ,IAAI,MAAM,QAAQ,MAAM,EAAE;AAEnE,aAAK,WAAW,KAAK,OAAO;AAC5B,aAAK,aAAa;AAElB,eAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,UAC1B,QAAQ;AAAA,UACR,QAAQ;AAAA,QACV,CAAC;AAAA,MACH,SAAS,OAAgB;AACvB,eAAO,MAAM,6BAA6B,KAAK;AAC/C,eAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,MAChE;AAAA,IACF,CAAC;AAED,SAAK,IAAI,IAAI,CAAC,KAAK,QAAQ;AACzB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,IAC7C,CAAC;AAAA,EACH;AAAA,EAEQ,uBAAuB,KAA+B;AAC5D,QAAI,CAAC,KAAK,OAAO,eAAe;AAC9B,aAAO,KAAK,sDAAsD;AAClE,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,IAAI,QAAQ,kBAAkB;AAChD,QAAI,CAAC,WAAW;AACd,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,OACV,WAAW,UAAU,KAAK,OAAO,aAAa,EAC9C,OAAO,IAAI,IAAI,EACf,OAAO,KAAK;AAEf,WAAO,OAAO,gBAAgB,OAAO,KAAK,SAAS,GAAG,OAAO,KAAK,IAAI,CAAC;AAAA,EACzE;AAAA,EAEA,MAAc,eAA8B;AAC1C,QAAI,KAAK,gBAAgB,KAAK,WAAW,WAAW,GAAG;AACrD;AAAA,IACF;AAEA,SAAK,eAAe;AAEpB,WAAO,KAAK,WAAW,SAAS,GAAG;AACjC,YAAM,QAAQ,KAAK,WAAW,MAAM;AAEpC,UAAI;AACF,cAAM,KAAK,mBAAmB,KAAK;AAAA,MACrC,SAAS,OAAgB;AACvB,eAAO,MAAM,4BAA4B,MAAM,IAAI,IAAI,KAAK;AAAA,MAC9D;AAAA,IACF;AAEA,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,MAAc,mBACZ,SACe;AACf,UAAM,EAAE,MAAM,QAAQ,KAAK,IAAI;AAE/B,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,cAAM,KAAK,iBAAiB,QAAQ,IAAmB;AACvD;AAAA,MACF,KAAK;AACH,cAAM,KAAK,mBAAmB,QAAQ,IAAqB;AAC3D;AAAA,MACF,KAAK;AACH,cAAM,KAAK,mBAAmB,QAAQ,IAAqB;AAC3D;AAAA,MACF;AACE,eAAO,MAAM,yBAAyB,IAAI,EAAE;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAc,iBACZ,QACA,MACe;AACf,UAAM,QAAQ;AAEd,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO,KAAK,sBAAsB,MAAM,UAAU,MAAM,MAAM,KAAK,EAAE;AACrE,cAAM,KAAK,YAAY,iBAAiB,KAAK;AAC7C;AAAA,MACF,KAAK;AACH,eAAO,KAAK,kBAAkB,MAAM,UAAU,MAAM,MAAM,KAAK,EAAE;AACjE,cAAM,KAAK,YAAY,iBAAiB,KAAK;AAC7C;AAAA,MACF,KAAK;AACH,eAAO,KAAK,kBAAkB,MAAM,UAAU,EAAE;AAChD,cAAM,KAAK,YAAY,iBAAiB,MAAM,UAAU;AACxD;AAAA,MACF;AACE,eAAO,MAAM,2BAA2B,MAAM,EAAE;AAAA,IACpD;AAAA,EACF;AAAA,EAEA,MAAc,mBACZ,QACA,MACe;AACf,WAAO,MAAM,kBAAkB,MAAM,IAAI,EAAE,SAAS,KAAK,OAAO,GAAG,CAAC;AAAA,EACtE;AAAA,EAEA,MAAc,mBACZ,QACA,MACe;AACf,WAAO,MAAM,kBAAkB,MAAM,IAAI,EAAE,WAAW,KAAK,GAAG,CAAC;AAAA,EACjE;AAAA,EAEA,MAAa,QAAuB;AAClC,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,WAAK,SAAS,KAAK,IAAI;AAAA,QACrB,KAAK,OAAO;AAAA,QACZ,KAAK,OAAO;AAAA,QACZ,MAAM;AACJ,kBAAQ;AAAA,YACN,MAAM,MAAM,QAAG,IAAI,MAAM,KAAK,gCAAgC;AAAA,UAChE;AACA,kBAAQ;AAAA,YACN,MAAM,KAAK,SAAS,IAClB,UAAU,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,IAAI;AAAA,UAClD;AACA,kBAAQ;AAAA,YACN,MAAM,KAAK,YAAY,IACrB,UAAU,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,IAAI;AAAA,UAClD;AAEA,cAAI,CAAC,KAAK,OAAO,eAAe;AAC9B,oBAAQ;AAAA,cACN,MAAM;AAAA,gBACJ;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAEA,kBAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAa,OAAsB;AACjC,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAI,KAAK,QAAQ;AACf,aAAK,OAAO,MAAM,MAAM;AACtB,iBAAO,KAAK,wBAAwB;AACpC,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH,OAAO;AACL,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAGA,IAAI,QAAQ,KAAK,CAAC,MAAM,IAAI,IAAI,YAAY,GAAG,EAAE,UAAU;AACzD,QAAM,SAAS,IAAI,oBAAoB;AAEvC,SAAO,MAAM,EAAE,MAAM,CAAC,UAAU;AAC9B,YAAQ,MAAM,MAAM,IAAI,iCAAiC,GAAG,KAAK;AACjE,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AAED,UAAQ,GAAG,UAAU,YAAY;AAC/B,YAAQ,IAAI,MAAM,OAAO,qCAAqC,CAAC;AAC/D,UAAM,OAAO,KAAK;AAClB,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;",
6
6
  "names": []
7
7
  }
@@ -3,12 +3,16 @@ import { dirname as __pathDirname } from 'path';
3
3
  const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
5
  import { logger } from "../../core/monitoring/logger.js";
6
+ import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
6
7
  import crypto from "crypto";
7
8
  function getEnv(key, defaultValue) {
8
9
  const value = process.env[key];
9
10
  if (value === void 0) {
10
11
  if (defaultValue !== void 0) return defaultValue;
11
- throw new Error(`Environment variable ${key} is required`);
12
+ throw new IntegrationError(
13
+ `Environment variable ${key} is required`,
14
+ ErrorCode.LINEAR_WEBHOOK_FAILED
15
+ );
12
16
  }
13
17
  return value;
14
18
  }
@@ -72,7 +76,10 @@ class LinearWebhookHandler {
72
76
  const validatedPayload = this.validateWebhookPayload(payload);
73
77
  if (!validatedPayload) {
74
78
  logger.error("Invalid webhook payload received");
75
- throw new Error("Invalid webhook payload");
79
+ throw new IntegrationError(
80
+ "Invalid webhook payload",
81
+ ErrorCode.LINEAR_WEBHOOK_FAILED
82
+ );
76
83
  }
77
84
  logger.info("Processing Linear webhook", {
78
85
  action: validatedPayload.action,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/integrations/linear/webhook.ts"],
4
- "sourcesContent": ["/**\n * Linear Webhook Handler\n * Processes incoming webhooks from Linear for real-time sync\n */\n\nimport { logger } from '../../core/monitoring/logger.js';\nimport { LinearSyncEngine } from './sync.js';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport crypto from 'crypto';\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 Error(`Environment variable ${key} is required`);\n }\n return value;\n}\n\nfunction getOptionalEnv(key: string): string | undefined {\n return process.env[key];\n}\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: string;\n };\n priority?: number;\n assignee?: {\n id: string;\n name: string;\n email: string;\n };\n team?: {\n id: string;\n key: string;\n name: string;\n };\n labels?: Array<{\n id: string;\n name: string;\n color: string;\n }>;\n dueDate?: string;\n completedAt?: string;\n updatedAt: string;\n };\n type: 'Issue' | 'Comment' | 'Project' | 'Cycle';\n url: string;\n webhookId: string;\n webhookTimestamp: number;\n}\n\nexport class LinearWebhookHandler {\n private syncEngine?: LinearSyncEngine;\n private taskStore?: LinearTaskManager;\n private webhookSecret?: string;\n\n constructor(webhookSecret?: string) {\n this.webhookSecret = webhookSecret || process.env['LINEAR_WEBHOOK_SECRET'];\n }\n\n /**\n * Set the sync engine for processing webhooks\n */\n setSyncEngine(syncEngine: LinearSyncEngine): void {\n this.syncEngine = syncEngine;\n }\n\n /**\n * Set the task store for direct updates\n */\n setTaskStore(taskStore: LinearTaskManager): void {\n this.taskStore = taskStore;\n }\n\n /**\n * Verify webhook signature\n */\n verifySignature(body: string, signature: string): boolean {\n if (!this.webhookSecret) {\n logger.warn('No webhook secret configured, skipping verification');\n return true; // Allow in development\n }\n\n const hmac = crypto.createHmac('sha256', this.webhookSecret);\n hmac.update(body);\n const expectedSignature = hmac.digest('hex');\n\n return signature === expectedSignature;\n }\n\n /**\n * Validate webhook payload structure\n */\n private validateWebhookPayload(\n payload: unknown\n ): LinearWebhookPayload | null {\n if (!payload || typeof payload !== 'object') return null;\n\n const p = payload as any;\n\n // Validate required fields\n if (!p.action || typeof p.action !== 'string') return null;\n if (!p.type || typeof p.type !== 'string') return null;\n if (!p.data || typeof p.data !== 'object') return null;\n if (!p.data.id || typeof p.data.id !== 'string') return null;\n\n // Sanitize string fields to prevent injection\n if (p.data.title && typeof p.data.title === 'string') {\n p.data.title = p.data.title.substring(0, 500); // Limit length\n }\n if (p.data.description && typeof p.data.description === 'string') {\n p.data.description = p.data.description.substring(0, 5000); // Limit length\n }\n\n return p as LinearWebhookPayload;\n }\n\n /**\n * Process incoming webhook\n */\n async processWebhook(payload: LinearWebhookPayload): Promise<void> {\n // Validate payload first\n const validatedPayload = this.validateWebhookPayload(payload);\n if (!validatedPayload) {\n logger.error('Invalid webhook payload received');\n throw new Error('Invalid webhook payload');\n }\n\n logger.info('Processing Linear webhook', {\n action: validatedPayload.action,\n type: validatedPayload.type,\n id: validatedPayload.data.id,\n });\n\n payload = validatedPayload;\n\n // Only process Issue webhooks for now\n if (payload.type !== 'Issue') {\n logger.info(`Ignoring webhook for type: ${payload.type}`);\n return;\n }\n\n switch (payload.action) {\n case 'create':\n await this.handleIssueCreated(payload);\n break;\n case 'update':\n await this.handleIssueUpdated(payload);\n break;\n case 'remove':\n await this.handleIssueRemoved(payload);\n break;\n default:\n logger.warn(`Unknown webhook action: ${payload.action}`);\n }\n }\n\n /**\n * Handle issue created in Linear\n */\n private async handleIssueCreated(\n payload: LinearWebhookPayload\n ): Promise<void> {\n logger.info('Linear issue created', {\n identifier: payload.data.identifier,\n });\n\n // Check if we should sync this issue\n if (!this.shouldSyncIssue(payload.data)) {\n return;\n }\n\n // For now, just log it - full implementation would create a StackMemory task\n logger.info('Would create StackMemory task for Linear issue', {\n identifier: payload.data.identifier,\n title: payload.data.title,\n });\n\n // Create a StackMemory task from Linear issue\n if (this.taskStore) {\n try {\n const taskId = this.taskStore.createTask({\n frameId: 'linear-import', // Special frame for Linear imports\n title: payload.data.title || 'Untitled Linear Issue',\n description: payload.data.description || '',\n priority: this.mapLinearPriorityToStackMemory(payload.data.priority),\n assignee: payload.data.assignee?.email,\n tags: payload.data.labels?.map((l: any) => l.name) || [],\n });\n\n // Store mapping for future syncing\n this.storeMapping(taskId, payload.data.id);\n\n logger.info('Created StackMemory task from Linear issue', {\n stackmemoryId: taskId,\n linearId: payload.data.id,\n });\n } catch (error: unknown) {\n logger.error('Failed to create task from Linear issue', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n\n /**\n * Handle issue updated in Linear\n */\n private async handleIssueUpdated(\n payload: LinearWebhookPayload\n ): Promise<void> {\n logger.info('Linear issue updated', {\n identifier: payload.data.identifier,\n });\n\n if (!this.syncEngine) {\n logger.warn('No sync engine configured, cannot process update');\n return;\n }\n\n // Find mapped StackMemory task\n const mapping = this.findMappingByLinearId(payload.data.id);\n if (!mapping) {\n logger.info('No mapping found for Linear issue', { id: payload.data.id });\n return;\n }\n\n // Check for conflicts\n const task = this.taskStore?.getTask(mapping.stackmemoryId);\n if (!task) {\n logger.warn('StackMemory task not found', { id: mapping.stackmemoryId });\n return;\n }\n\n // Update the task based on Linear changes\n let newStatus:\n | 'pending'\n | 'in_progress'\n | 'completed'\n | 'cancelled'\n | undefined;\n\n if (payload.data.state) {\n const mappedStatus = this.mapLinearStateToStatus(payload.data.state) as\n | 'pending'\n | 'in_progress'\n | 'completed'\n | 'cancelled';\n if (mappedStatus !== task.status) {\n newStatus = mappedStatus;\n }\n }\n\n if (payload.data.completedAt) {\n newStatus = 'completed';\n }\n\n // Update status if changed\n if (newStatus) {\n this.taskStore?.updateTaskStatus(\n mapping.stackmemoryId,\n newStatus,\n 'Linear webhook update'\n );\n logger.info('Updated StackMemory task status from webhook', {\n taskId: mapping.stackmemoryId,\n newStatus,\n });\n }\n\n // For other properties, we'd need to implement a more complete update method\n // For now, log what changed\n if (payload.data.title && payload.data.title !== task.title) {\n logger.info(\n 'Task title changed in Linear but not updated in StackMemory',\n {\n taskId: mapping.stackmemoryId,\n oldTitle: task.title,\n newTitle: payload.data.title,\n }\n );\n }\n }\n\n /**\n * Handle issue removed in Linear\n */\n private async handleIssueRemoved(\n payload: LinearWebhookPayload\n ): Promise<void> {\n logger.info('Linear issue removed', {\n identifier: payload.data.identifier,\n });\n\n const mapping = this.findMappingByLinearId(payload.data.id);\n if (!mapping) {\n logger.info('No mapping found for removed Linear issue');\n return;\n }\n\n // Mark the StackMemory task as cancelled\n this.taskStore?.updateTaskStatus(\n mapping.stackmemoryId,\n 'cancelled',\n 'Linear issue deleted'\n );\n\n logger.info('Marked StackMemory task as cancelled due to Linear deletion', {\n taskId: mapping.stackmemoryId,\n });\n }\n\n /**\n * Check if we should sync this issue\n */\n private shouldSyncIssue(issue: LinearWebhookPayload['data']): boolean {\n // Add your filtering logic here\n // For example, only sync issues from specific teams or with certain labels\n\n // Skip issues without a title\n if (!issue.title) {\n return false;\n }\n\n // Skip archived/cancelled issues\n if (issue.state?.type === 'canceled' || issue.state?.type === 'archived') {\n return false;\n }\n\n return true;\n }\n\n /**\n * Find mapping by Linear ID\n */\n private findMappingByLinearId(\n linearId: string\n ): { stackmemoryId: string; linearId: string } | null {\n // Use in-memory mapping for now\n // In production, this would query a database\n const mapping = this.taskMappings.get(linearId);\n if (mapping) {\n return { stackmemoryId: mapping, linearId };\n }\n return null;\n }\n\n // In-memory task mappings (Linear ID -> StackMemory ID)\n private taskMappings = new Map<string, string>();\n\n /**\n * Store mapping between Linear and StackMemory IDs\n */\n private storeMapping(stackmemoryId: string, linearId: string): void {\n this.taskMappings.set(linearId, stackmemoryId);\n // In production, persist to database\n }\n\n /**\n * Map Linear priority to StackMemory priority\n */\n private mapLinearPriorityToStackMemory(\n priority: number | undefined\n ): 'low' | 'medium' | 'high' | 'urgent' {\n if (!priority) return 'medium';\n if (priority <= 1) return 'urgent';\n if (priority === 2) return 'high';\n if (priority === 3) return 'medium';\n return 'low';\n }\n\n /**\n * Map Linear state to StackMemory status\n */\n private mapLinearStateToStatus(state: {\n type?: string;\n name?: string;\n }): string {\n const stateType = state.type?.toLowerCase() || state.name?.toLowerCase();\n\n switch (stateType) {\n case 'backlog':\n case 'unstarted':\n return 'pending';\n case 'started':\n case 'in progress':\n return 'in_progress';\n case 'completed':\n case 'done':\n return 'completed';\n case 'canceled':\n case 'cancelled':\n return 'cancelled';\n default:\n return 'pending';\n }\n }\n\n /**\n * Map Linear priority to StackMemory priority\n */\n private mapLinearPriorityToPriority(priority: number): number {\n // Linear uses 0-4, StackMemory uses 1-5\n return 5 - priority;\n }\n}\n"],
5
- "mappings": ";;;;AAKA,SAAS,cAAc;AAGvB,OAAO,YAAY;AAEnB,SAAS,OAAO,KAAa,cAA+B;AAC1D,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,UAAU,QAAW;AACvB,QAAI,iBAAiB,OAAW,QAAO;AACvC,UAAM,IAAI,MAAM,wBAAwB,GAAG,cAAc;AAAA,EAC3D;AACA,SAAO;AACT;AAEA,SAAS,eAAe,KAAiC;AACvD,SAAO,QAAQ,IAAI,GAAG;AACxB;AA0CO,MAAM,qBAAqB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,eAAwB;AAClC,SAAK,gBAAgB,iBAAiB,QAAQ,IAAI,uBAAuB;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,YAAoC;AAChD,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,WAAoC;AAC/C,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,MAAc,WAA4B;AACxD,QAAI,CAAC,KAAK,eAAe;AACvB,aAAO,KAAK,qDAAqD;AACjE,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,OAAO,WAAW,UAAU,KAAK,aAAa;AAC3D,SAAK,OAAO,IAAI;AAChB,UAAM,oBAAoB,KAAK,OAAO,KAAK;AAE3C,WAAO,cAAc;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKQ,uBACN,SAC6B;AAC7B,QAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AAEpD,UAAM,IAAI;AAGV,QAAI,CAAC,EAAE,UAAU,OAAO,EAAE,WAAW,SAAU,QAAO;AACtD,QAAI,CAAC,EAAE,QAAQ,OAAO,EAAE,SAAS,SAAU,QAAO;AAClD,QAAI,CAAC,EAAE,QAAQ,OAAO,EAAE,SAAS,SAAU,QAAO;AAClD,QAAI,CAAC,EAAE,KAAK,MAAM,OAAO,EAAE,KAAK,OAAO,SAAU,QAAO;AAGxD,QAAI,EAAE,KAAK,SAAS,OAAO,EAAE,KAAK,UAAU,UAAU;AACpD,QAAE,KAAK,QAAQ,EAAE,KAAK,MAAM,UAAU,GAAG,GAAG;AAAA,IAC9C;AACA,QAAI,EAAE,KAAK,eAAe,OAAO,EAAE,KAAK,gBAAgB,UAAU;AAChE,QAAE,KAAK,cAAc,EAAE,KAAK,YAAY,UAAU,GAAG,GAAI;AAAA,IAC3D;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,SAA8C;AAEjE,UAAM,mBAAmB,KAAK,uBAAuB,OAAO;AAC5D,QAAI,CAAC,kBAAkB;AACrB,aAAO,MAAM,kCAAkC;AAC/C,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AAEA,WAAO,KAAK,6BAA6B;AAAA,MACvC,QAAQ,iBAAiB;AAAA,MACzB,MAAM,iBAAiB;AAAA,MACvB,IAAI,iBAAiB,KAAK;AAAA,IAC5B,CAAC;AAED,cAAU;AAGV,QAAI,QAAQ,SAAS,SAAS;AAC5B,aAAO,KAAK,8BAA8B,QAAQ,IAAI,EAAE;AACxD;AAAA,IACF;AAEA,YAAQ,QAAQ,QAAQ;AAAA,MACtB,KAAK;AACH,cAAM,KAAK,mBAAmB,OAAO;AACrC;AAAA,MACF,KAAK;AACH,cAAM,KAAK,mBAAmB,OAAO;AACrC;AAAA,MACF,KAAK;AACH,cAAM,KAAK,mBAAmB,OAAO;AACrC;AAAA,MACF;AACE,eAAO,KAAK,2BAA2B,QAAQ,MAAM,EAAE;AAAA,IAC3D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,mBACZ,SACe;AACf,WAAO,KAAK,wBAAwB;AAAA,MAClC,YAAY,QAAQ,KAAK;AAAA,IAC3B,CAAC;AAGD,QAAI,CAAC,KAAK,gBAAgB,QAAQ,IAAI,GAAG;AACvC;AAAA,IACF;AAGA,WAAO,KAAK,kDAAkD;AAAA,MAC5D,YAAY,QAAQ,KAAK;AAAA,MACzB,OAAO,QAAQ,KAAK;AAAA,IACtB,CAAC;AAGD,QAAI,KAAK,WAAW;AAClB,UAAI;AACF,cAAM,SAAS,KAAK,UAAU,WAAW;AAAA,UACvC,SAAS;AAAA;AAAA,UACT,OAAO,QAAQ,KAAK,SAAS;AAAA,UAC7B,aAAa,QAAQ,KAAK,eAAe;AAAA,UACzC,UAAU,KAAK,+BAA+B,QAAQ,KAAK,QAAQ;AAAA,UACnE,UAAU,QAAQ,KAAK,UAAU;AAAA,UACjC,MAAM,QAAQ,KAAK,QAAQ,IAAI,CAAC,MAAW,EAAE,IAAI,KAAK,CAAC;AAAA,QACzD,CAAC;AAGD,aAAK,aAAa,QAAQ,QAAQ,KAAK,EAAE;AAEzC,eAAO,KAAK,8CAA8C;AAAA,UACxD,eAAe;AAAA,UACf,UAAU,QAAQ,KAAK;AAAA,QACzB,CAAC;AAAA,MACH,SAAS,OAAgB;AACvB,eAAO,MAAM,2CAA2C;AAAA,UACtD,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,QAC9D,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,mBACZ,SACe;AACf,WAAO,KAAK,wBAAwB;AAAA,MAClC,YAAY,QAAQ,KAAK;AAAA,IAC3B,CAAC;AAED,QAAI,CAAC,KAAK,YAAY;AACpB,aAAO,KAAK,kDAAkD;AAC9D;AAAA,IACF;AAGA,UAAM,UAAU,KAAK,sBAAsB,QAAQ,KAAK,EAAE;AAC1D,QAAI,CAAC,SAAS;AACZ,aAAO,KAAK,qCAAqC,EAAE,IAAI,QAAQ,KAAK,GAAG,CAAC;AACxE;AAAA,IACF;AAGA,UAAM,OAAO,KAAK,WAAW,QAAQ,QAAQ,aAAa;AAC1D,QAAI,CAAC,MAAM;AACT,aAAO,KAAK,8BAA8B,EAAE,IAAI,QAAQ,cAAc,CAAC;AACvE;AAAA,IACF;AAGA,QAAI;AAOJ,QAAI,QAAQ,KAAK,OAAO;AACtB,YAAM,eAAe,KAAK,uBAAuB,QAAQ,KAAK,KAAK;AAKnE,UAAI,iBAAiB,KAAK,QAAQ;AAChC,oBAAY;AAAA,MACd;AAAA,IACF;AAEA,QAAI,QAAQ,KAAK,aAAa;AAC5B,kBAAY;AAAA,IACd;AAGA,QAAI,WAAW;AACb,WAAK,WAAW;AAAA,QACd,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,MACF;AACA,aAAO,KAAK,gDAAgD;AAAA,QAC1D,QAAQ,QAAQ;AAAA,QAChB;AAAA,MACF,CAAC;AAAA,IACH;AAIA,QAAI,QAAQ,KAAK,SAAS,QAAQ,KAAK,UAAU,KAAK,OAAO;AAC3D,aAAO;AAAA,QACL;AAAA,QACA;AAAA,UACE,QAAQ,QAAQ;AAAA,UAChB,UAAU,KAAK;AAAA,UACf,UAAU,QAAQ,KAAK;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,mBACZ,SACe;AACf,WAAO,KAAK,wBAAwB;AAAA,MAClC,YAAY,QAAQ,KAAK;AAAA,IAC3B,CAAC;AAED,UAAM,UAAU,KAAK,sBAAsB,QAAQ,KAAK,EAAE;AAC1D,QAAI,CAAC,SAAS;AACZ,aAAO,KAAK,2CAA2C;AACvD;AAAA,IACF;AAGA,SAAK,WAAW;AAAA,MACd,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAEA,WAAO,KAAK,+DAA+D;AAAA,MACzE,QAAQ,QAAQ;AAAA,IAClB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,OAA8C;AAKpE,QAAI,CAAC,MAAM,OAAO;AAChB,aAAO;AAAA,IACT;AAGA,QAAI,MAAM,OAAO,SAAS,cAAc,MAAM,OAAO,SAAS,YAAY;AACxE,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,sBACN,UACoD;AAGpD,UAAM,UAAU,KAAK,aAAa,IAAI,QAAQ;AAC9C,QAAI,SAAS;AACX,aAAO,EAAE,eAAe,SAAS,SAAS;AAAA,IAC5C;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,eAAe,oBAAI,IAAoB;AAAA;AAAA;AAAA;AAAA,EAKvC,aAAa,eAAuB,UAAwB;AAClE,SAAK,aAAa,IAAI,UAAU,aAAa;AAAA,EAE/C;AAAA;AAAA;AAAA;AAAA,EAKQ,+BACN,UACsC;AACtC,QAAI,CAAC,SAAU,QAAO;AACtB,QAAI,YAAY,EAAG,QAAO;AAC1B,QAAI,aAAa,EAAG,QAAO;AAC3B,QAAI,aAAa,EAAG,QAAO;AAC3B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAAuB,OAGpB;AACT,UAAM,YAAY,MAAM,MAAM,YAAY,KAAK,MAAM,MAAM,YAAY;AAEvE,YAAQ,WAAW;AAAA,MACjB,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,4BAA4B,UAA0B;AAE5D,WAAO,IAAI;AAAA,EACb;AACF;",
4
+ "sourcesContent": ["/**\n * Linear Webhook Handler\n * Processes incoming webhooks from Linear for real-time sync\n */\n\nimport { logger } from '../../core/monitoring/logger.js';\nimport { IntegrationError, ErrorCode } from '../../core/errors/index.js';\nimport { LinearSyncEngine } from './sync.js';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport crypto from 'crypto';\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: string;\n };\n priority?: number;\n assignee?: {\n id: string;\n name: string;\n email: string;\n };\n team?: {\n id: string;\n key: string;\n name: string;\n };\n labels?: Array<{\n id: string;\n name: string;\n color: string;\n }>;\n dueDate?: string;\n completedAt?: string;\n updatedAt: string;\n };\n type: 'Issue' | 'Comment' | 'Project' | 'Cycle';\n url: string;\n webhookId: string;\n webhookTimestamp: number;\n}\n\nexport class LinearWebhookHandler {\n private syncEngine?: LinearSyncEngine;\n private taskStore?: LinearTaskManager;\n private webhookSecret?: string;\n\n constructor(webhookSecret?: string) {\n this.webhookSecret = webhookSecret || process.env['LINEAR_WEBHOOK_SECRET'];\n }\n\n /**\n * Set the sync engine for processing webhooks\n */\n setSyncEngine(syncEngine: LinearSyncEngine): void {\n this.syncEngine = syncEngine;\n }\n\n /**\n * Set the task store for direct updates\n */\n setTaskStore(taskStore: LinearTaskManager): void {\n this.taskStore = taskStore;\n }\n\n /**\n * Verify webhook signature\n */\n verifySignature(body: string, signature: string): boolean {\n if (!this.webhookSecret) {\n logger.warn('No webhook secret configured, skipping verification');\n return true; // Allow in development\n }\n\n const hmac = crypto.createHmac('sha256', this.webhookSecret);\n hmac.update(body);\n const expectedSignature = hmac.digest('hex');\n\n return signature === expectedSignature;\n }\n\n /**\n * Validate webhook payload structure\n */\n private validateWebhookPayload(\n payload: unknown\n ): LinearWebhookPayload | null {\n if (!payload || typeof payload !== 'object') return null;\n\n const p = payload as any;\n\n // Validate required fields\n if (!p.action || typeof p.action !== 'string') return null;\n if (!p.type || typeof p.type !== 'string') return null;\n if (!p.data || typeof p.data !== 'object') return null;\n if (!p.data.id || typeof p.data.id !== 'string') return null;\n\n // Sanitize string fields to prevent injection\n if (p.data.title && typeof p.data.title === 'string') {\n p.data.title = p.data.title.substring(0, 500); // Limit length\n }\n if (p.data.description && typeof p.data.description === 'string') {\n p.data.description = p.data.description.substring(0, 5000); // Limit length\n }\n\n return p as LinearWebhookPayload;\n }\n\n /**\n * Process incoming webhook\n */\n async processWebhook(payload: LinearWebhookPayload): Promise<void> {\n // Validate payload first\n const validatedPayload = this.validateWebhookPayload(payload);\n if (!validatedPayload) {\n logger.error('Invalid webhook payload received');\n throw new IntegrationError(\n 'Invalid webhook payload',\n ErrorCode.LINEAR_WEBHOOK_FAILED\n );\n }\n\n logger.info('Processing Linear webhook', {\n action: validatedPayload.action,\n type: validatedPayload.type,\n id: validatedPayload.data.id,\n });\n\n payload = validatedPayload;\n\n // Only process Issue webhooks for now\n if (payload.type !== 'Issue') {\n logger.info(`Ignoring webhook for type: ${payload.type}`);\n return;\n }\n\n switch (payload.action) {\n case 'create':\n await this.handleIssueCreated(payload);\n break;\n case 'update':\n await this.handleIssueUpdated(payload);\n break;\n case 'remove':\n await this.handleIssueRemoved(payload);\n break;\n default:\n logger.warn(`Unknown webhook action: ${payload.action}`);\n }\n }\n\n /**\n * Handle issue created in Linear\n */\n private async handleIssueCreated(\n payload: LinearWebhookPayload\n ): Promise<void> {\n logger.info('Linear issue created', {\n identifier: payload.data.identifier,\n });\n\n // Check if we should sync this issue\n if (!this.shouldSyncIssue(payload.data)) {\n return;\n }\n\n // For now, just log it - full implementation would create a StackMemory task\n logger.info('Would create StackMemory task for Linear issue', {\n identifier: payload.data.identifier,\n title: payload.data.title,\n });\n\n // Create a StackMemory task from Linear issue\n if (this.taskStore) {\n try {\n const taskId = this.taskStore.createTask({\n frameId: 'linear-import', // Special frame for Linear imports\n title: payload.data.title || 'Untitled Linear Issue',\n description: payload.data.description || '',\n priority: this.mapLinearPriorityToStackMemory(payload.data.priority),\n assignee: payload.data.assignee?.email,\n tags: payload.data.labels?.map((l: any) => l.name) || [],\n });\n\n // Store mapping for future syncing\n this.storeMapping(taskId, payload.data.id);\n\n logger.info('Created StackMemory task from Linear issue', {\n stackmemoryId: taskId,\n linearId: payload.data.id,\n });\n } catch (error: unknown) {\n logger.error('Failed to create task from Linear issue', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n\n /**\n * Handle issue updated in Linear\n */\n private async handleIssueUpdated(\n payload: LinearWebhookPayload\n ): Promise<void> {\n logger.info('Linear issue updated', {\n identifier: payload.data.identifier,\n });\n\n if (!this.syncEngine) {\n logger.warn('No sync engine configured, cannot process update');\n return;\n }\n\n // Find mapped StackMemory task\n const mapping = this.findMappingByLinearId(payload.data.id);\n if (!mapping) {\n logger.info('No mapping found for Linear issue', { id: payload.data.id });\n return;\n }\n\n // Check for conflicts\n const task = this.taskStore?.getTask(mapping.stackmemoryId);\n if (!task) {\n logger.warn('StackMemory task not found', { id: mapping.stackmemoryId });\n return;\n }\n\n // Update the task based on Linear changes\n let newStatus:\n | 'pending'\n | 'in_progress'\n | 'completed'\n | 'cancelled'\n | undefined;\n\n if (payload.data.state) {\n const mappedStatus = this.mapLinearStateToStatus(payload.data.state) as\n | 'pending'\n | 'in_progress'\n | 'completed'\n | 'cancelled';\n if (mappedStatus !== task.status) {\n newStatus = mappedStatus;\n }\n }\n\n if (payload.data.completedAt) {\n newStatus = 'completed';\n }\n\n // Update status if changed\n if (newStatus) {\n this.taskStore?.updateTaskStatus(\n mapping.stackmemoryId,\n newStatus,\n 'Linear webhook update'\n );\n logger.info('Updated StackMemory task status from webhook', {\n taskId: mapping.stackmemoryId,\n newStatus,\n });\n }\n\n // For other properties, we'd need to implement a more complete update method\n // For now, log what changed\n if (payload.data.title && payload.data.title !== task.title) {\n logger.info(\n 'Task title changed in Linear but not updated in StackMemory',\n {\n taskId: mapping.stackmemoryId,\n oldTitle: task.title,\n newTitle: payload.data.title,\n }\n );\n }\n }\n\n /**\n * Handle issue removed in Linear\n */\n private async handleIssueRemoved(\n payload: LinearWebhookPayload\n ): Promise<void> {\n logger.info('Linear issue removed', {\n identifier: payload.data.identifier,\n });\n\n const mapping = this.findMappingByLinearId(payload.data.id);\n if (!mapping) {\n logger.info('No mapping found for removed Linear issue');\n return;\n }\n\n // Mark the StackMemory task as cancelled\n this.taskStore?.updateTaskStatus(\n mapping.stackmemoryId,\n 'cancelled',\n 'Linear issue deleted'\n );\n\n logger.info('Marked StackMemory task as cancelled due to Linear deletion', {\n taskId: mapping.stackmemoryId,\n });\n }\n\n /**\n * Check if we should sync this issue\n */\n private shouldSyncIssue(issue: LinearWebhookPayload['data']): boolean {\n // Add your filtering logic here\n // For example, only sync issues from specific teams or with certain labels\n\n // Skip issues without a title\n if (!issue.title) {\n return false;\n }\n\n // Skip archived/cancelled issues\n if (issue.state?.type === 'canceled' || issue.state?.type === 'archived') {\n return false;\n }\n\n return true;\n }\n\n /**\n * Find mapping by Linear ID\n */\n private findMappingByLinearId(\n linearId: string\n ): { stackmemoryId: string; linearId: string } | null {\n // Use in-memory mapping for now\n // In production, this would query a database\n const mapping = this.taskMappings.get(linearId);\n if (mapping) {\n return { stackmemoryId: mapping, linearId };\n }\n return null;\n }\n\n // In-memory task mappings (Linear ID -> StackMemory ID)\n private taskMappings = new Map<string, string>();\n\n /**\n * Store mapping between Linear and StackMemory IDs\n */\n private storeMapping(stackmemoryId: string, linearId: string): void {\n this.taskMappings.set(linearId, stackmemoryId);\n // In production, persist to database\n }\n\n /**\n * Map Linear priority to StackMemory priority\n */\n private mapLinearPriorityToStackMemory(\n priority: number | undefined\n ): 'low' | 'medium' | 'high' | 'urgent' {\n if (!priority) return 'medium';\n if (priority <= 1) return 'urgent';\n if (priority === 2) return 'high';\n if (priority === 3) return 'medium';\n return 'low';\n }\n\n /**\n * Map Linear state to StackMemory status\n */\n private mapLinearStateToStatus(state: {\n type?: string;\n name?: string;\n }): string {\n const stateType = state.type?.toLowerCase() || state.name?.toLowerCase();\n\n switch (stateType) {\n case 'backlog':\n case 'unstarted':\n return 'pending';\n case 'started':\n case 'in progress':\n return 'in_progress';\n case 'completed':\n case 'done':\n return 'completed';\n case 'canceled':\n case 'cancelled':\n return 'cancelled';\n default:\n return 'pending';\n }\n }\n\n /**\n * Map Linear priority to StackMemory priority\n */\n private mapLinearPriorityToPriority(priority: number): number {\n // Linear uses 0-4, StackMemory uses 1-5\n return 5 - priority;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }