@stackmemoryai/stackmemory 0.5.29 → 0.5.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +53 -32
  2. package/dist/core/database/batch-operations.js +29 -4
  3. package/dist/core/database/batch-operations.js.map +2 -2
  4. package/dist/core/database/connection-pool.js +13 -2
  5. package/dist/core/database/connection-pool.js.map +2 -2
  6. package/dist/core/database/migration-manager.js +130 -34
  7. package/dist/core/database/migration-manager.js.map +2 -2
  8. package/dist/core/database/paradedb-adapter.js +23 -7
  9. package/dist/core/database/paradedb-adapter.js.map +2 -2
  10. package/dist/core/database/query-router.js +8 -3
  11. package/dist/core/database/query-router.js.map +2 -2
  12. package/dist/core/database/sqlite-adapter.js +152 -33
  13. package/dist/core/database/sqlite-adapter.js.map +2 -2
  14. package/dist/integrations/linear/auth.js +34 -20
  15. package/dist/integrations/linear/auth.js.map +2 -2
  16. package/dist/integrations/linear/auto-sync.js +18 -8
  17. package/dist/integrations/linear/auto-sync.js.map +2 -2
  18. package/dist/integrations/linear/client.js +42 -9
  19. package/dist/integrations/linear/client.js.map +2 -2
  20. package/dist/integrations/linear/migration.js +94 -36
  21. package/dist/integrations/linear/migration.js.map +2 -2
  22. package/dist/integrations/linear/oauth-server.js +77 -34
  23. package/dist/integrations/linear/oauth-server.js.map +2 -2
  24. package/dist/integrations/linear/rest-client.js +13 -3
  25. package/dist/integrations/linear/rest-client.js.map +2 -2
  26. package/dist/integrations/linear/sync-service.js +18 -15
  27. package/dist/integrations/linear/sync-service.js.map +2 -2
  28. package/dist/integrations/linear/sync.js +12 -4
  29. package/dist/integrations/linear/sync.js.map +2 -2
  30. package/dist/integrations/linear/unified-sync.js +33 -8
  31. package/dist/integrations/linear/unified-sync.js.map +2 -2
  32. package/dist/integrations/linear/webhook-handler.js +5 -1
  33. package/dist/integrations/linear/webhook-handler.js.map +2 -2
  34. package/dist/integrations/linear/webhook-server.js +7 -7
  35. package/dist/integrations/linear/webhook-server.js.map +2 -2
  36. package/dist/integrations/linear/webhook.js +9 -2
  37. package/dist/integrations/linear/webhook.js.map +2 -2
  38. package/dist/integrations/mcp/schemas.js +147 -0
  39. package/dist/integrations/mcp/schemas.js.map +7 -0
  40. package/dist/integrations/mcp/server.js +19 -3
  41. package/dist/integrations/mcp/server.js.map +2 -2
  42. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/integrations/linear/oauth-server.ts"],
4
- "sourcesContent": ["/**\n * OAuth Callback Server for Linear Integration\n * Handles the OAuth callback redirect and completes the authentication flow\n */\n\nimport express from 'express';\nimport http from 'http';\nimport { URL } from 'url';\nimport { logger } from '../../core/monitoring/logger.js';\nimport { LinearAuthManager } from './auth.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 OAuthServerConfig {\n port?: number;\n host?: string;\n redirectPath?: string;\n autoShutdown?: boolean;\n shutdownDelay?: number;\n}\n\nexport class LinearOAuthServer {\n private app: express.Application;\n private server: http.Server | null = null;\n private authManager: LinearAuthManager;\n private config: OAuthServerConfig;\n private pendingCodeVerifiers: Map<string, string> = new Map();\n private authCompleteCallbacks: Map<string, (success: boolean) => void> = new Map();\n\n constructor(projectRoot: string, config?: OAuthServerConfig) {\n this.app = express();\n this.authManager = new LinearAuthManager(projectRoot);\n \n this.config = {\n port: config?.port || 3456,\n host: config?.host || 'localhost',\n redirectPath: config?.redirectPath || '/auth/linear/callback',\n autoShutdown: config?.autoShutdown !== false,\n shutdownDelay: config?.shutdownDelay || 5000,\n };\n\n this.setupRoutes();\n }\n\n private setupRoutes(): void {\n // Health check endpoint\n this.app.get('/health', (req, res) => {\n res.json({\n status: 'healthy',\n service: 'linear-oauth',\n timestamp: new Date().toISOString(),\n });\n });\n\n // OAuth callback endpoint\n this.app.get(this.config.redirectPath!, async (req, res) => {\n const { code, state, error, error_description } = req.query;\n\n // Handle OAuth errors\n if (error) {\n logger.error(`OAuth error: ${error} - ${error_description}`);\n res.send(this.generateErrorPage(\n 'Authorization Failed',\n `${error}: ${error_description || 'An error occurred during authorization'}`\n ));\n \n if (state && this.authCompleteCallbacks.has(state as string)) {\n this.authCompleteCallbacks.get(state as string)!(false);\n this.authCompleteCallbacks.delete(state as string);\n }\n \n this.scheduleShutdown();\n return;\n }\n\n // Validate required parameters\n if (!code) {\n res.send(this.generateErrorPage(\n 'Missing Authorization Code',\n 'No authorization code was provided in the callback'\n ));\n this.scheduleShutdown();\n return;\n }\n\n try {\n // Get the code verifier for this session\n const codeVerifier = state \n ? this.pendingCodeVerifiers.get(state as string)\n : process.env['_LINEAR_CODE_VERIFIER'];\n\n if (!codeVerifier) {\n throw new Error('Code verifier not found. Please restart the authorization process.');\n }\n\n // Exchange code for tokens\n logger.info('Exchanging authorization code for tokens...');\n await this.authManager.exchangeCodeForToken(code as string, codeVerifier);\n\n // Clean up\n if (state) {\n this.pendingCodeVerifiers.delete(state as string);\n }\n delete process.env['_LINEAR_CODE_VERIFIER'];\n\n // Test the connection\n const testSuccess = await this.testConnection();\n \n if (testSuccess) {\n res.send(this.generateSuccessPage());\n logger.info('Linear OAuth authentication completed successfully!');\n } else {\n throw new Error('Failed to verify Linear connection');\n }\n\n // Notify callback if registered\n if (state && this.authCompleteCallbacks.has(state as string)) {\n this.authCompleteCallbacks.get(state as string)!(true);\n this.authCompleteCallbacks.delete(state as string);\n }\n\n // Schedule server shutdown if auto-shutdown is enabled\n this.scheduleShutdown();\n } catch (error: unknown) {\n logger.error('Failed to complete OAuth flow:', error as Error);\n res.send(this.generateErrorPage(\n 'Authentication Failed',\n (error as Error).message\n ));\n \n if (state && this.authCompleteCallbacks.has(state as string)) {\n this.authCompleteCallbacks.get(state as string)!(false);\n this.authCompleteCallbacks.delete(state as string);\n }\n \n this.scheduleShutdown();\n }\n });\n\n // Start OAuth flow endpoint\n this.app.get('/auth/linear/start', (req, res) => {\n try {\n const config = this.authManager.loadConfig();\n if (!config) {\n res.status(400).send(this.generateErrorPage(\n 'Configuration Missing',\n 'Linear OAuth configuration not found. Please configure your client ID and secret.'\n ));\n return;\n }\n\n // Generate state for CSRF protection\n const state = this.generateState();\n const { url, codeVerifier } = this.authManager.generateAuthUrl(state);\n \n // Store code verifier for this session\n this.pendingCodeVerifiers.set(state, codeVerifier);\n\n // Redirect to Linear OAuth page\n res.redirect(url);\n } catch (error: unknown) {\n logger.error('Failed to start OAuth flow:', error as Error);\n res.status(500).send(this.generateErrorPage(\n 'OAuth Start Failed',\n (error as Error).message\n ));\n }\n });\n\n // 404 handler\n this.app.use((req, res) => {\n res.status(404).json({ error: 'Not found' });\n });\n }\n\n private generateState(): string {\n return Math.random().toString(36).substring(2, 15) + \n Math.random().toString(36).substring(2, 15);\n }\n\n private generateSuccessPage(): string {\n return `\n <!DOCTYPE html>\n <html>\n <head>\n <title>Linear Authorization Successful</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 12px;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #2d3748;\n margin-bottom: 1rem;\n }\n .success-icon {\n font-size: 4rem;\n margin-bottom: 1rem;\n }\n p {\n color: #4a5568;\n line-height: 1.6;\n margin: 1rem 0;\n }\n .close-note {\n color: #718096;\n font-size: 0.875rem;\n margin-top: 2rem;\n }\n code {\n background: #f7fafc;\n padding: 0.25rem 0.5rem;\n border-radius: 4px;\n font-family: 'Courier New', monospace;\n }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <div class=\"success-icon\">\u2705</div>\n <h1>Authorization Successful!</h1>\n <p>Your Linear account has been successfully connected to StackMemory.</p>\n <p>You can now use Linear integration features:</p>\n <p><code>stackmemory linear sync</code></p>\n <p><code>stackmemory linear create</code></p>\n <p class=\"close-note\">You can safely close this window and return to your terminal.</p>\n </div>\n </body>\n </html>\n `;\n }\n\n private generateErrorPage(title: string, message: string): string {\n return `\n <!DOCTYPE html>\n <html>\n <head>\n <title>${title}</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 12px;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #e53e3e;\n margin-bottom: 1rem;\n }\n .error-icon {\n font-size: 4rem;\n margin-bottom: 1rem;\n }\n p {\n color: #4a5568;\n line-height: 1.6;\n margin: 1rem 0;\n }\n .error-message {\n background: #fff5f5;\n border: 1px solid #fed7d7;\n color: #742a2a;\n padding: 1rem;\n border-radius: 6px;\n margin-top: 1rem;\n font-size: 0.875rem;\n }\n .retry-note {\n color: #718096;\n font-size: 0.875rem;\n margin-top: 2rem;\n }\n code {\n background: #f7fafc;\n padding: 0.25rem 0.5rem;\n border-radius: 4px;\n font-family: 'Courier New', monospace;\n }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <div class=\"error-icon\">\u274C</div>\n <h1>${title}</h1>\n <p>Unable to complete Linear authorization.</p>\n <div class=\"error-message\">${message}</div>\n <p class=\"retry-note\">\n Please try again with:<br>\n <code>stackmemory linear auth</code>\n </p>\n </div>\n </body>\n </html>\n `;\n }\n\n private async testConnection(): Promise<boolean> {\n try {\n const token = await this.authManager.getValidToken();\n \n const response = await fetch('https://api.linear.app/graphql', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n query: 'query { viewer { id name email } }',\n }),\n });\n\n if (response.ok) {\n const result = await response.json() as {\n data?: { viewer?: { id: string; name: string; email: string } };\n };\n if (result.data?.viewer) {\n logger.info(`Connected to Linear as: ${result.data.viewer.name} (${result.data.viewer.email})`);\n return true;\n }\n }\n\n return false;\n } catch (error: unknown) {\n logger.error('Linear connection test failed:', error as Error);\n return false;\n }\n }\n\n private scheduleShutdown(): void {\n if (this.config.autoShutdown && this.server) {\n setTimeout(() => {\n logger.info('Auto-shutting down OAuth server...');\n this.stop();\n }, this.config.shutdownDelay);\n }\n }\n\n public async start(): Promise<{ url: string; codeVerifier?: string }> {\n return new Promise((resolve, reject) => {\n try {\n // Load config and generate auth URL\n const config = this.authManager.loadConfig();\n if (!config) {\n // If no config, provide setup instructions\n const setupUrl = `http://${this.config.host}:${this.config.port}/auth/linear/start`;\n \n this.server = this.app.listen(\n this.config.port!,\n this.config.host!,\n () => {\n console.log(chalk.green('\u2713') + chalk.bold(' Linear OAuth Server Started'));\n console.log(chalk.cyan(' Authorization URL: ') + setupUrl);\n console.log(chalk.cyan(' Callback URL: ') + \n `http://${this.config.host}:${this.config.port}${this.config.redirectPath}`);\n console.log('');\n console.log(chalk.yellow(' \u26A0 Configuration Required:'));\n console.log(' 1. Create a Linear OAuth app at: https://linear.app/settings/api');\n console.log(` 2. Set redirect URI to: http://${this.config.host}:${this.config.port}${this.config.redirectPath}`);\n console.log(' 3. Set environment variables:');\n console.log(' export LINEAR_CLIENT_ID=\"your_client_id\"');\n console.log(' export LINEAR_CLIENT_SECRET=\"your_client_secret\"');\n console.log(' 4. Restart the auth process');\n \n resolve({ url: setupUrl });\n }\n );\n return;\n }\n\n // Generate state and auth URL\n const state = this.generateState();\n const { url, codeVerifier } = this.authManager.generateAuthUrl(state);\n \n // Store code verifier\n this.pendingCodeVerifiers.set(state, codeVerifier);\n\n this.server = this.app.listen(\n this.config.port!,\n this.config.host!,\n () => {\n console.log(chalk.green('\u2713') + chalk.bold(' Linear OAuth Server Started'));\n console.log(chalk.cyan(' Open this URL in your browser:'));\n console.log(' ' + chalk.underline(url));\n console.log('');\n console.log(chalk.gray(' The server will automatically shut down after authorization completes.'));\n \n resolve({ url, codeVerifier });\n }\n );\n\n // Register auth complete callback\n this.authCompleteCallbacks.set(state, (success) => {\n if (success) {\n console.log(chalk.green('\\n\u2713 Linear authorization completed successfully!'));\n } else {\n console.log(chalk.red('\\n\u2717 Linear authorization failed'));\n }\n });\n\n } catch (error: unknown) {\n reject(error);\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('OAuth server stopped');\n this.server = null;\n resolve();\n });\n } else {\n resolve();\n }\n });\n }\n\n public async waitForAuth(state: string, timeout: number = 300000): Promise<boolean> {\n return new Promise((resolve) => {\n const timeoutId = setTimeout(() => {\n this.authCompleteCallbacks.delete(state);\n resolve(false);\n }, timeout);\n\n this.authCompleteCallbacks.set(state, (success) => {\n clearTimeout(timeoutId);\n resolve(success);\n });\n });\n }\n}\n\n// Standalone execution support\nif (process.argv[1] === new URL(import.meta.url).pathname) {\n const projectRoot = process.cwd();\n const server = new LinearOAuthServer(projectRoot, {\n autoShutdown: true,\n shutdownDelay: 5000,\n });\n\n server.start()\n .then(({ url }) => {\n if (url) {\n console.log(chalk.cyan('\\nWaiting for authorization...'));\n console.log(chalk.gray('Press Ctrl+C to cancel\\n'));\n }\n })\n .catch((error) => {\n console.error(chalk.red('Failed to start OAuth server:'), error);\n process.exit(1);\n });\n\n process.on('SIGINT', async () => {\n console.log(chalk.yellow('\\n\\nShutting down OAuth server...'));\n await server.stop();\n process.exit(0);\n });\n}"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * OAuth Callback Server for Linear Integration\n * Handles the OAuth callback redirect and completes the authentication flow\n */\n\nimport express from 'express';\nimport http from 'http';\nimport { URL } from 'url';\nimport { logger } from '../../core/monitoring/logger.js';\nimport { LinearAuthManager } from './auth.js';\nimport chalk from 'chalk';\nimport { IntegrationError, ErrorCode } from '../../core/errors/index.js';\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_AUTH_FAILED\n );\n }\n return value;\n}\n\nfunction getOptionalEnv(key: string): string | undefined {\n return process.env[key];\n}\n\nexport interface OAuthServerConfig {\n port?: number;\n host?: string;\n redirectPath?: string;\n autoShutdown?: boolean;\n shutdownDelay?: number;\n}\n\nexport class LinearOAuthServer {\n private app: express.Application;\n private server: http.Server | null = null;\n private authManager: LinearAuthManager;\n private config: OAuthServerConfig;\n private pendingCodeVerifiers: Map<string, string> = new Map();\n private authCompleteCallbacks: Map<string, (success: boolean) => void> =\n new Map();\n\n constructor(projectRoot: string, config?: OAuthServerConfig) {\n this.app = express();\n this.authManager = new LinearAuthManager(projectRoot);\n\n this.config = {\n port: config?.port || 3456,\n host: config?.host || 'localhost',\n redirectPath: config?.redirectPath || '/auth/linear/callback',\n autoShutdown: config?.autoShutdown !== false,\n shutdownDelay: config?.shutdownDelay || 5000,\n };\n\n this.setupRoutes();\n }\n\n private setupRoutes(): void {\n // Health check endpoint\n this.app.get('/health', (req, res) => {\n res.json({\n status: 'healthy',\n service: 'linear-oauth',\n timestamp: new Date().toISOString(),\n });\n });\n\n // OAuth callback endpoint\n this.app.get(this.config.redirectPath!, async (req, res) => {\n const { code, state, error, error_description } = req.query;\n\n // Handle OAuth errors\n if (error) {\n logger.error(`OAuth error: ${error} - ${error_description}`);\n res.send(\n this.generateErrorPage(\n 'Authorization Failed',\n `${error}: ${error_description || 'An error occurred during authorization'}`\n )\n );\n\n if (state && this.authCompleteCallbacks.has(state as string)) {\n this.authCompleteCallbacks.get(state as string)!(false);\n this.authCompleteCallbacks.delete(state as string);\n }\n\n this.scheduleShutdown();\n return;\n }\n\n // Validate required parameters\n if (!code) {\n res.send(\n this.generateErrorPage(\n 'Missing Authorization Code',\n 'No authorization code was provided in the callback'\n )\n );\n this.scheduleShutdown();\n return;\n }\n\n try {\n // Get the code verifier for this session\n const codeVerifier = state\n ? this.pendingCodeVerifiers.get(state as string)\n : process.env['_LINEAR_CODE_VERIFIER'];\n\n if (!codeVerifier) {\n throw new IntegrationError(\n 'Code verifier not found. Please restart the authorization process.',\n ErrorCode.LINEAR_AUTH_FAILED\n );\n }\n\n // Exchange code for tokens\n logger.info('Exchanging authorization code for tokens...');\n await this.authManager.exchangeCodeForToken(\n code as string,\n codeVerifier\n );\n\n // Clean up\n if (state) {\n this.pendingCodeVerifiers.delete(state as string);\n }\n delete process.env['_LINEAR_CODE_VERIFIER'];\n\n // Test the connection\n const testSuccess = await this.testConnection();\n\n if (testSuccess) {\n res.send(this.generateSuccessPage());\n logger.info('Linear OAuth authentication completed successfully!');\n } else {\n throw new IntegrationError(\n 'Failed to verify Linear connection',\n ErrorCode.LINEAR_AUTH_FAILED\n );\n }\n\n // Notify callback if registered\n if (state && this.authCompleteCallbacks.has(state as string)) {\n this.authCompleteCallbacks.get(state as string)!(true);\n this.authCompleteCallbacks.delete(state as string);\n }\n\n // Schedule server shutdown if auto-shutdown is enabled\n this.scheduleShutdown();\n } catch (error: unknown) {\n logger.error('Failed to complete OAuth flow:', error as Error);\n res.send(\n this.generateErrorPage(\n 'Authentication Failed',\n (error as Error).message\n )\n );\n\n if (state && this.authCompleteCallbacks.has(state as string)) {\n this.authCompleteCallbacks.get(state as string)!(false);\n this.authCompleteCallbacks.delete(state as string);\n }\n\n this.scheduleShutdown();\n }\n });\n\n // Start OAuth flow endpoint\n this.app.get('/auth/linear/start', (req, res) => {\n try {\n const config = this.authManager.loadConfig();\n if (!config) {\n res\n .status(400)\n .send(\n this.generateErrorPage(\n 'Configuration Missing',\n 'Linear OAuth configuration not found. Please configure your client ID and secret.'\n )\n );\n return;\n }\n\n // Generate state for CSRF protection\n const state = this.generateState();\n const { url, codeVerifier } = this.authManager.generateAuthUrl(state);\n\n // Store code verifier for this session\n this.pendingCodeVerifiers.set(state, codeVerifier);\n\n // Redirect to Linear OAuth page\n res.redirect(url);\n } catch (error: unknown) {\n logger.error('Failed to start OAuth flow:', error as Error);\n res\n .status(500)\n .send(\n this.generateErrorPage(\n 'OAuth Start Failed',\n (error as Error).message\n )\n );\n }\n });\n\n // 404 handler\n this.app.use((req, res) => {\n res.status(404).json({ error: 'Not found' });\n });\n }\n\n private generateState(): string {\n return (\n Math.random().toString(36).substring(2, 15) +\n Math.random().toString(36).substring(2, 15)\n );\n }\n\n private generateSuccessPage(): string {\n return `\n <!DOCTYPE html>\n <html>\n <head>\n <title>Linear Authorization Successful</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 12px;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #2d3748;\n margin-bottom: 1rem;\n }\n .success-icon {\n font-size: 4rem;\n margin-bottom: 1rem;\n }\n p {\n color: #4a5568;\n line-height: 1.6;\n margin: 1rem 0;\n }\n .close-note {\n color: #718096;\n font-size: 0.875rem;\n margin-top: 2rem;\n }\n code {\n background: #f7fafc;\n padding: 0.25rem 0.5rem;\n border-radius: 4px;\n font-family: 'Courier New', monospace;\n }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <div class=\"success-icon\">\u2705</div>\n <h1>Authorization Successful!</h1>\n <p>Your Linear account has been successfully connected to StackMemory.</p>\n <p>You can now use Linear integration features:</p>\n <p><code>stackmemory linear sync</code></p>\n <p><code>stackmemory linear create</code></p>\n <p class=\"close-note\">You can safely close this window and return to your terminal.</p>\n </div>\n </body>\n </html>\n `;\n }\n\n private generateErrorPage(title: string, message: string): string {\n return `\n <!DOCTYPE html>\n <html>\n <head>\n <title>${title}</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 12px;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #e53e3e;\n margin-bottom: 1rem;\n }\n .error-icon {\n font-size: 4rem;\n margin-bottom: 1rem;\n }\n p {\n color: #4a5568;\n line-height: 1.6;\n margin: 1rem 0;\n }\n .error-message {\n background: #fff5f5;\n border: 1px solid #fed7d7;\n color: #742a2a;\n padding: 1rem;\n border-radius: 6px;\n margin-top: 1rem;\n font-size: 0.875rem;\n }\n .retry-note {\n color: #718096;\n font-size: 0.875rem;\n margin-top: 2rem;\n }\n code {\n background: #f7fafc;\n padding: 0.25rem 0.5rem;\n border-radius: 4px;\n font-family: 'Courier New', monospace;\n }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <div class=\"error-icon\">\u274C</div>\n <h1>${title}</h1>\n <p>Unable to complete Linear authorization.</p>\n <div class=\"error-message\">${message}</div>\n <p class=\"retry-note\">\n Please try again with:<br>\n <code>stackmemory linear auth</code>\n </p>\n </div>\n </body>\n </html>\n `;\n }\n\n private async testConnection(): Promise<boolean> {\n try {\n const token = await this.authManager.getValidToken();\n\n const response = await fetch('https://api.linear.app/graphql', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n query: 'query { viewer { id name email } }',\n }),\n });\n\n if (response.ok) {\n const result = (await response.json()) as {\n data?: { viewer?: { id: string; name: string; email: string } };\n };\n if (result.data?.viewer) {\n logger.info(\n `Connected to Linear as: ${result.data.viewer.name} (${result.data.viewer.email})`\n );\n return true;\n }\n }\n\n return false;\n } catch (error: unknown) {\n logger.error('Linear connection test failed:', error as Error);\n return false;\n }\n }\n\n private scheduleShutdown(): void {\n if (this.config.autoShutdown && this.server) {\n setTimeout(() => {\n logger.info('Auto-shutting down OAuth server...');\n this.stop();\n }, this.config.shutdownDelay);\n }\n }\n\n public async start(): Promise<{ url: string; codeVerifier?: string }> {\n return new Promise((resolve, reject) => {\n try {\n // Load config and generate auth URL\n const config = this.authManager.loadConfig();\n if (!config) {\n // If no config, provide setup instructions\n const setupUrl = `http://${this.config.host}:${this.config.port}/auth/linear/start`;\n\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 OAuth Server Started')\n );\n console.log(chalk.cyan(' Authorization URL: ') + setupUrl);\n console.log(\n chalk.cyan(' Callback URL: ') +\n `http://${this.config.host}:${this.config.port}${this.config.redirectPath}`\n );\n console.log('');\n console.log(chalk.yellow(' \u26A0 Configuration Required:'));\n console.log(\n ' 1. Create a Linear OAuth app at: https://linear.app/settings/api'\n );\n console.log(\n ` 2. Set redirect URI to: http://${this.config.host}:${this.config.port}${this.config.redirectPath}`\n );\n console.log(' 3. Set environment variables:');\n console.log(' export LINEAR_CLIENT_ID=\"your_client_id\"');\n console.log(\n ' export LINEAR_CLIENT_SECRET=\"your_client_secret\"'\n );\n console.log(' 4. Restart the auth process');\n\n resolve({ url: setupUrl });\n }\n );\n return;\n }\n\n // Generate state and auth URL\n const state = this.generateState();\n const { url, codeVerifier } = this.authManager.generateAuthUrl(state);\n\n // Store code verifier\n this.pendingCodeVerifiers.set(state, codeVerifier);\n\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 OAuth Server Started')\n );\n console.log(chalk.cyan(' Open this URL in your browser:'));\n console.log(' ' + chalk.underline(url));\n console.log('');\n console.log(\n chalk.gray(\n ' The server will automatically shut down after authorization completes.'\n )\n );\n\n resolve({ url, codeVerifier });\n }\n );\n\n // Register auth complete callback\n this.authCompleteCallbacks.set(state, (success) => {\n if (success) {\n console.log(\n chalk.green('\\n\u2713 Linear authorization completed successfully!')\n );\n } else {\n console.log(chalk.red('\\n\u2717 Linear authorization failed'));\n }\n });\n } catch (error: unknown) {\n reject(error);\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('OAuth server stopped');\n this.server = null;\n resolve();\n });\n } else {\n resolve();\n }\n });\n }\n\n public async waitForAuth(\n state: string,\n timeout: number = 300000\n ): Promise<boolean> {\n return new Promise((resolve) => {\n const timeoutId = setTimeout(() => {\n this.authCompleteCallbacks.delete(state);\n resolve(false);\n }, timeout);\n\n this.authCompleteCallbacks.set(state, (success) => {\n clearTimeout(timeoutId);\n resolve(success);\n });\n });\n }\n}\n\n// Standalone execution support\nif (process.argv[1] === new URL(import.meta.url).pathname) {\n const projectRoot = process.cwd();\n const server = new LinearOAuthServer(projectRoot, {\n autoShutdown: true,\n shutdownDelay: 5000,\n });\n\n server\n .start()\n .then(({ url }) => {\n if (url) {\n console.log(chalk.cyan('\\nWaiting for authorization...'));\n console.log(chalk.gray('Press Ctrl+C to cancel\\n'));\n }\n })\n .catch((error) => {\n console.error(chalk.red('Failed to start OAuth server:'), error);\n process.exit(1);\n });\n\n process.on('SIGINT', async () => {\n console.log(chalk.yellow('\\n\\nShutting down OAuth server...'));\n await server.stop();\n process.exit(0);\n });\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["error"]
7
7
  }
@@ -3,6 +3,7 @@ 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
  class LinearRestClient {
7
8
  apiKey;
8
9
  baseUrl = "https://api.linear.app/graphql";
@@ -60,7 +61,10 @@ class LinearRestClient {
60
61
  }
61
62
  `;
62
63
  const variables = cursor ? { after: cursor } : {};
63
- const response = await this.makeRequest(query, variables);
64
+ const response = await this.makeRequest(
65
+ query,
66
+ variables
67
+ );
64
68
  const tasks = response.data.issues.nodes;
65
69
  allTasks.push(...tasks);
66
70
  tasks.forEach((task) => {
@@ -153,7 +157,10 @@ class LinearRestClient {
153
157
  `;
154
158
  const response = await this.makeRequest(query);
155
159
  if (response.data.teams.nodes.length === 0) {
156
- throw new Error("ENG team not found");
160
+ throw new IntegrationError(
161
+ "ENG team not found",
162
+ ErrorCode.LINEAR_API_ERROR
163
+ );
157
164
  }
158
165
  return response.data.teams.nodes[0];
159
166
  }
@@ -192,7 +199,10 @@ class LinearRestClient {
192
199
  const result = await response.json();
193
200
  if (!response.ok || result.errors) {
194
201
  const errorMsg = result.errors?.[0]?.message || `${response.status} ${response.statusText}`;
195
- throw new Error(`Linear API error: ${errorMsg}`);
202
+ throw new IntegrationError(
203
+ `Linear API error: ${errorMsg}`,
204
+ ErrorCode.LINEAR_API_ERROR
205
+ );
196
206
  }
197
207
  return result;
198
208
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/integrations/linear/rest-client.ts"],
4
- "sourcesContent": ["/**\n * Linear REST Client for StackMemory\n * Provides memory-based task storage using REST API instead of GraphQL\n */\n\nimport { logger } from '../../core/monitoring/logger.js';\n\nexport interface LinearTask {\n id: string;\n identifier: string;\n title: string;\n description?: string;\n state: {\n name: string;\n type: string;\n };\n priority: number;\n assignee?: {\n id: string;\n name: string;\n };\n estimate?: number;\n createdAt: string;\n updatedAt: string;\n url: string;\n}\n\nexport interface LinearTasksResponse {\n data: {\n issues: {\n nodes: LinearTask[];\n pageInfo: {\n hasNextPage: boolean;\n endCursor?: string;\n };\n };\n };\n}\n\nexport class LinearRestClient {\n private apiKey: string;\n private baseUrl = 'https://api.linear.app/graphql';\n private taskCache = new Map<string, LinearTask>();\n private lastSync = 0;\n private cacheTTL = 5 * 60 * 1000; // 5 minutes\n\n constructor(apiKey: string) {\n this.apiKey = apiKey;\n }\n\n /**\n * Get all tasks and store in memory\n */\n async getAllTasks(forceRefresh = false): Promise<LinearTask[]> {\n const now = Date.now();\n \n // Return cached data if fresh\n if (!forceRefresh && (now - this.lastSync) < this.cacheTTL && this.taskCache.size > 0) {\n return Array.from(this.taskCache.values());\n }\n\n try {\n const allTasks: LinearTask[] = [];\n let hasNextPage = true;\n let cursor: string | undefined;\n\n while (hasNextPage) {\n const query = `\n query($after: String) {\n issues(\n filter: { team: { key: { eq: \"ENG\" } } }\n first: 100\n after: $after\n ) {\n nodes {\n id\n identifier\n title\n description\n state {\n name\n type\n }\n priority\n assignee {\n id\n name\n }\n estimate\n createdAt\n updatedAt\n url\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n `;\n\n const variables = cursor ? { after: cursor } : {};\n const response = await this.makeRequest<LinearTasksResponse>(query, variables);\n\n const tasks = response.data.issues.nodes;\n allTasks.push(...tasks);\n\n // Update cache\n tasks.forEach(task => {\n this.taskCache.set(task.id, task);\n });\n\n hasNextPage = response.data.issues.pageInfo.hasNextPage;\n cursor = response.data.issues.pageInfo.endCursor;\n\n logger.info(`Fetched ${tasks.length} tasks, total: ${allTasks.length}`);\n }\n\n this.lastSync = now;\n logger.info(`Cached ${allTasks.length} Linear tasks in memory`);\n\n return allTasks;\n } catch (error: unknown) {\n logger.error('Failed to fetch Linear tasks:', error as Error);\n return Array.from(this.taskCache.values()); // Return cached data on error\n }\n }\n\n /**\n * Get tasks by status\n */\n async getTasksByStatus(status: string): Promise<LinearTask[]> {\n const tasks = await this.getAllTasks();\n return tasks.filter((task: any) => task.state.type === status);\n }\n\n /**\n * Get tasks assigned to current user\n */\n async getMyTasks(): Promise<LinearTask[]> {\n try {\n const viewer = await this.getViewer();\n const tasks = await this.getAllTasks();\n return tasks.filter((task: any) => task.assignee?.id === viewer.id);\n } catch (error: unknown) {\n logger.error('Failed to get assigned tasks:', error as Error);\n return [];\n }\n }\n\n /**\n * Get task count by status\n */\n async getTaskCounts(): Promise<Record<string, number>> {\n const tasks = await this.getAllTasks();\n const counts: Record<string, number> = {};\n\n tasks.forEach(task => {\n const status = task.state.type;\n counts[status] = (counts[status] || 0) + 1;\n });\n\n return counts;\n }\n\n /**\n * Search tasks by title or description\n */\n async searchTasks(query: string): Promise<LinearTask[]> {\n const tasks = await this.getAllTasks();\n const searchTerm = query.toLowerCase();\n\n return tasks.filter((task: any) => \n task.title.toLowerCase().includes(searchTerm) ||\n task.description?.toLowerCase().includes(searchTerm) ||\n task.identifier.toLowerCase().includes(searchTerm)\n );\n }\n\n /**\n * Get current viewer info\n */\n async getViewer(): Promise<{ id: string; name: string; email: string }> {\n const query = `\n query {\n viewer {\n id\n name\n email\n }\n }\n `;\n\n const response = await this.makeRequest<{\n data: {\n viewer: { id: string; name: string; email: string };\n };\n }>(query);\n\n return response.data.viewer;\n }\n\n /**\n * Get team info\n */\n async getTeam(): Promise<{ id: string; name: string; key: string }> {\n const query = `\n query {\n teams(filter: { key: { eq: \"ENG\" } }, first: 1) {\n nodes {\n id\n name\n key\n }\n }\n }\n `;\n\n const response = await this.makeRequest<{\n data: {\n teams: {\n nodes: Array<{ id: string; name: string; key: string }>;\n };\n };\n }>(query);\n\n if (response.data.teams.nodes.length === 0) {\n throw new Error('ENG team not found');\n }\n\n return response.data.teams.nodes[0]!;\n }\n\n /**\n * Get cache stats\n */\n getCacheStats(): {\n size: number;\n lastSync: number;\n age: number;\n fresh: boolean;\n } {\n const now = Date.now();\n return {\n size: this.taskCache.size,\n lastSync: this.lastSync,\n age: now - this.lastSync,\n fresh: (now - this.lastSync) < this.cacheTTL,\n };\n }\n\n /**\n * Clear cache\n */\n clearCache(): void {\n this.taskCache.clear();\n this.lastSync = 0;\n logger.info('Linear task cache cleared');\n }\n\n /**\n * Make GraphQL request\n */\n async makeRequest<T>(\n query: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n const response = await fetch(this.baseUrl, {\n method: 'POST',\n headers: {\n Authorization: this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ query, variables }),\n });\n\n const result = await response.json() as {\n data?: unknown;\n errors?: Array<{ message: string }>;\n };\n\n if (!response.ok || result.errors) {\n const errorMsg = result.errors?.[0]?.message || `${response.status} ${response.statusText}`;\n throw new Error(`Linear API error: ${errorMsg}`);\n }\n\n return result as T;\n }\n}"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Linear REST Client for StackMemory\n * Provides memory-based task storage using REST API instead of GraphQL\n */\n\nimport { logger } from '../../core/monitoring/logger.js';\nimport { IntegrationError, ErrorCode } from '../../core/errors/index.js';\n\nexport interface LinearTask {\n id: string;\n identifier: string;\n title: string;\n description?: string;\n state: {\n name: string;\n type: string;\n };\n priority: number;\n assignee?: {\n id: string;\n name: string;\n };\n estimate?: number;\n createdAt: string;\n updatedAt: string;\n url: string;\n}\n\nexport interface LinearTasksResponse {\n data: {\n issues: {\n nodes: LinearTask[];\n pageInfo: {\n hasNextPage: boolean;\n endCursor?: string;\n };\n };\n };\n}\n\nexport class LinearRestClient {\n private apiKey: string;\n private baseUrl = 'https://api.linear.app/graphql';\n private taskCache = new Map<string, LinearTask>();\n private lastSync = 0;\n private cacheTTL = 5 * 60 * 1000; // 5 minutes\n\n constructor(apiKey: string) {\n this.apiKey = apiKey;\n }\n\n /**\n * Get all tasks and store in memory\n */\n async getAllTasks(forceRefresh = false): Promise<LinearTask[]> {\n const now = Date.now();\n\n // Return cached data if fresh\n if (\n !forceRefresh &&\n now - this.lastSync < this.cacheTTL &&\n this.taskCache.size > 0\n ) {\n return Array.from(this.taskCache.values());\n }\n\n try {\n const allTasks: LinearTask[] = [];\n let hasNextPage = true;\n let cursor: string | undefined;\n\n while (hasNextPage) {\n const query = `\n query($after: String) {\n issues(\n filter: { team: { key: { eq: \"ENG\" } } }\n first: 100\n after: $after\n ) {\n nodes {\n id\n identifier\n title\n description\n state {\n name\n type\n }\n priority\n assignee {\n id\n name\n }\n estimate\n createdAt\n updatedAt\n url\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n `;\n\n const variables = cursor ? { after: cursor } : {};\n const response = await this.makeRequest<LinearTasksResponse>(\n query,\n variables\n );\n\n const tasks = response.data.issues.nodes;\n allTasks.push(...tasks);\n\n // Update cache\n tasks.forEach((task) => {\n this.taskCache.set(task.id, task);\n });\n\n hasNextPage = response.data.issues.pageInfo.hasNextPage;\n cursor = response.data.issues.pageInfo.endCursor;\n\n logger.info(`Fetched ${tasks.length} tasks, total: ${allTasks.length}`);\n }\n\n this.lastSync = now;\n logger.info(`Cached ${allTasks.length} Linear tasks in memory`);\n\n return allTasks;\n } catch (error: unknown) {\n logger.error('Failed to fetch Linear tasks:', error as Error);\n return Array.from(this.taskCache.values()); // Return cached data on error\n }\n }\n\n /**\n * Get tasks by status\n */\n async getTasksByStatus(status: string): Promise<LinearTask[]> {\n const tasks = await this.getAllTasks();\n return tasks.filter((task: any) => task.state.type === status);\n }\n\n /**\n * Get tasks assigned to current user\n */\n async getMyTasks(): Promise<LinearTask[]> {\n try {\n const viewer = await this.getViewer();\n const tasks = await this.getAllTasks();\n return tasks.filter((task: any) => task.assignee?.id === viewer.id);\n } catch (error: unknown) {\n logger.error('Failed to get assigned tasks:', error as Error);\n return [];\n }\n }\n\n /**\n * Get task count by status\n */\n async getTaskCounts(): Promise<Record<string, number>> {\n const tasks = await this.getAllTasks();\n const counts: Record<string, number> = {};\n\n tasks.forEach((task) => {\n const status = task.state.type;\n counts[status] = (counts[status] || 0) + 1;\n });\n\n return counts;\n }\n\n /**\n * Search tasks by title or description\n */\n async searchTasks(query: string): Promise<LinearTask[]> {\n const tasks = await this.getAllTasks();\n const searchTerm = query.toLowerCase();\n\n return tasks.filter(\n (task: any) =>\n task.title.toLowerCase().includes(searchTerm) ||\n task.description?.toLowerCase().includes(searchTerm) ||\n task.identifier.toLowerCase().includes(searchTerm)\n );\n }\n\n /**\n * Get current viewer info\n */\n async getViewer(): Promise<{ id: string; name: string; email: string }> {\n const query = `\n query {\n viewer {\n id\n name\n email\n }\n }\n `;\n\n const response = await this.makeRequest<{\n data: {\n viewer: { id: string; name: string; email: string };\n };\n }>(query);\n\n return response.data.viewer;\n }\n\n /**\n * Get team info\n */\n async getTeam(): Promise<{ id: string; name: string; key: string }> {\n const query = `\n query {\n teams(filter: { key: { eq: \"ENG\" } }, first: 1) {\n nodes {\n id\n name\n key\n }\n }\n }\n `;\n\n const response = await this.makeRequest<{\n data: {\n teams: {\n nodes: Array<{ id: string; name: string; key: string }>;\n };\n };\n }>(query);\n\n if (response.data.teams.nodes.length === 0) {\n throw new IntegrationError(\n 'ENG team not found',\n ErrorCode.LINEAR_API_ERROR\n );\n }\n\n return response.data.teams.nodes[0]!;\n }\n\n /**\n * Get cache stats\n */\n getCacheStats(): {\n size: number;\n lastSync: number;\n age: number;\n fresh: boolean;\n } {\n const now = Date.now();\n return {\n size: this.taskCache.size,\n lastSync: this.lastSync,\n age: now - this.lastSync,\n fresh: now - this.lastSync < this.cacheTTL,\n };\n }\n\n /**\n * Clear cache\n */\n clearCache(): void {\n this.taskCache.clear();\n this.lastSync = 0;\n logger.info('Linear task cache cleared');\n }\n\n /**\n * Make GraphQL request\n */\n async makeRequest<T>(\n query: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n const response = await fetch(this.baseUrl, {\n method: 'POST',\n headers: {\n Authorization: this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ query, variables }),\n });\n\n const result = (await response.json()) as {\n data?: unknown;\n errors?: Array<{ message: string }>;\n };\n\n if (!response.ok || result.errors) {\n const errorMsg =\n result.errors?.[0]?.message ||\n `${response.status} ${response.statusText}`;\n throw new IntegrationError(\n `Linear API error: ${errorMsg}`,\n ErrorCode.LINEAR_API_ERROR\n );\n }\n\n return result as T;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -6,17 +6,7 @@ import { LinearClient } from "./client.js";
6
6
  import { ContextService } from "../../services/context-service.js";
7
7
  import { ConfigService } from "../../services/config-service.js";
8
8
  import { logger } from "../../core/monitoring/logger.js";
9
- function getEnv(key, defaultValue) {
10
- const value = process.env[key];
11
- if (value === void 0) {
12
- if (defaultValue !== void 0) return defaultValue;
13
- throw new Error(`Environment variable ${key} is required`);
14
- }
15
- return value;
16
- }
17
- function getOptionalEnv(key) {
18
- return process.env[key];
19
- }
9
+ import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
20
10
  class LinearSyncService {
21
11
  linearClient;
22
12
  contextService;
@@ -27,7 +17,10 @@ class LinearSyncService {
27
17
  this.contextService = new ContextService();
28
18
  const apiKey = process.env["LINEAR_API_KEY"];
29
19
  if (!apiKey) {
30
- throw new Error("LINEAR_API_KEY environment variable not set");
20
+ throw new IntegrationError(
21
+ "LINEAR_API_KEY environment variable not set",
22
+ ErrorCode.LINEAR_AUTH_FAILED
23
+ );
31
24
  }
32
25
  this.linearClient = new LinearClient({ apiKey });
33
26
  }
@@ -43,7 +36,10 @@ class LinearSyncService {
43
36
  const config = await this.configService.getConfig();
44
37
  const teamId = config.integrations?.linear?.teamId;
45
38
  if (!teamId) {
46
- throw new Error("Linear team ID not configured");
39
+ throw new IntegrationError(
40
+ "Linear team ID not configured",
41
+ ErrorCode.LINEAR_SYNC_FAILED
42
+ );
47
43
  }
48
44
  const issues = await this.linearClient.getIssues({ teamId });
49
45
  for (const issue of issues) {
@@ -93,7 +89,11 @@ class LinearSyncService {
93
89
  try {
94
90
  const task = await this.contextService.getTask(taskId);
95
91
  if (!task) {
96
- throw new Error(`Task ${taskId} not found`);
92
+ throw new IntegrationError(
93
+ `Task ${taskId} not found`,
94
+ ErrorCode.LINEAR_SYNC_FAILED,
95
+ { taskId }
96
+ );
97
97
  }
98
98
  if (task.externalId) {
99
99
  const updateData = this.convertTaskToUpdateData(task);
@@ -107,7 +107,10 @@ class LinearSyncService {
107
107
  const config = await this.configService.getConfig();
108
108
  const teamId = config.integrations?.linear?.teamId;
109
109
  if (!teamId) {
110
- throw new Error("Linear team ID not configured");
110
+ throw new IntegrationError(
111
+ "Linear team ID not configured",
112
+ ErrorCode.LINEAR_SYNC_FAILED
113
+ );
111
114
  }
112
115
  const createData = {
113
116
  title: task.title,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/integrations/linear/sync-service.ts"],
4
- "sourcesContent": ["import { LinearClient, LinearIssue, LinearCreateIssueInput } from './client.js';\nimport { ContextService } from '../../services/context-service.js';\nimport { ConfigService } from '../../services/config-service.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport { Task, TaskStatus, TaskPriority } from '../../types/task.js';\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\n// Minimal issue data needed for sync (webhook payloads may have fewer fields)\nexport interface LinearIssueData {\n id: string;\n identifier: string;\n title: string;\n description?: string;\n state: { id?: string; name?: string; type: string };\n priority?: number;\n assignee?: { id: string; name: string };\n labels?: Array<{ name: string }>;\n url?: string;\n updatedAt: string;\n}\n\nexport interface SyncResult {\n created: number;\n updated: number;\n deleted: number;\n conflicts: number;\n errors: string[];\n}\n\nexport class LinearSyncService {\n private linearClient: LinearClient;\n private contextService: ContextService;\n private configService: ConfigService;\n // Using singleton logger from monitoring\n\n constructor() {\n // Use singleton logger\n this.configService = new ConfigService();\n this.contextService = new ContextService();\n\n const apiKey = process.env['LINEAR_API_KEY'];\n if (!apiKey) {\n throw new Error('LINEAR_API_KEY environment variable not set');\n }\n\n this.linearClient = new LinearClient({ apiKey });\n }\n\n public async syncAllIssues(): Promise<SyncResult> {\n const result: SyncResult = {\n created: 0,\n updated: 0,\n deleted: 0,\n conflicts: 0,\n errors: [],\n };\n\n try {\n const config = await this.configService.getConfig();\n const teamId = config.integrations?.linear?.teamId;\n\n if (!teamId) {\n throw new Error('Linear team ID not configured');\n }\n\n const issues = await this.linearClient.getIssues({ teamId });\n\n for (const issue of issues) {\n try {\n const synced = await this.syncIssueToLocal(issue);\n if (synced === 'created') result.created++;\n else if (synced === 'updated') result.updated++;\n } catch (error: unknown) {\n const message =\n error instanceof Error ? error.message : String(error);\n result.errors.push(`Failed to sync ${issue.identifier}: ${message}`);\n }\n }\n\n logger.info(\n `Sync complete: ${result.created} created, ${result.updated} updated`\n );\n } catch (error: unknown) {\n logger.error('Sync failed:', error);\n const message = error instanceof Error ? error.message : String(error);\n result.errors.push(message);\n }\n\n return result;\n }\n\n public async syncIssueToLocal(\n issue: LinearIssueData\n ): Promise<'created' | 'updated' | 'skipped'> {\n try {\n const task = this.convertIssueToTask(issue);\n const existingTask = await this.contextService.getTaskByExternalId(\n issue.id\n );\n\n if (existingTask) {\n if (this.hasChanges(existingTask, task)) {\n await this.contextService.updateTask(existingTask.id, task);\n logger.debug(`Updated task: ${issue.identifier}`);\n return 'updated';\n }\n return 'skipped';\n } else {\n await this.contextService.createTask(task);\n logger.debug(`Created task: ${issue.identifier}`);\n return 'created';\n }\n } catch (error: unknown) {\n logger.error(`Failed to sync issue ${issue.identifier}:`, error);\n throw error;\n }\n }\n\n public async syncLocalToLinear(taskId: string): Promise<any> {\n try {\n const task = await this.contextService.getTask(taskId);\n if (!task) {\n throw new Error(`Task ${taskId} not found`);\n }\n\n if (task.externalId) {\n const updateData = this.convertTaskToUpdateData(task);\n const updated = await this.linearClient.updateIssue(\n task.externalId,\n updateData\n );\n logger.debug(`Updated Linear issue: ${updated.identifier}`);\n return updated;\n } else {\n const config = await this.configService.getConfig();\n const teamId = config.integrations?.linear?.teamId;\n if (!teamId) {\n throw new Error('Linear team ID not configured');\n }\n const createData: LinearCreateIssueInput = {\n title: task.title,\n description: task.description,\n teamId,\n priority: this.mapTaskPriorityToLinearPriority(task.priority),\n };\n const created = await this.linearClient.createIssue(createData);\n await this.contextService.updateTask(taskId, {\n externalId: created.id,\n });\n logger.debug(`Created Linear issue: ${created.identifier}`);\n return created;\n }\n } catch (error: unknown) {\n logger.error(`Failed to sync task ${taskId} to Linear:`, error);\n throw error;\n }\n }\n\n public async removeLocalIssue(identifier: string): Promise<void> {\n try {\n const tasks = await this.contextService.getAllTasks();\n const task = tasks.find((t) => t.externalIdentifier === identifier);\n\n if (task) {\n await this.contextService.deleteTask(task.id);\n logger.debug(`Removed local task: ${identifier}`);\n }\n } catch (error: unknown) {\n logger.error(`Failed to remove task ${identifier}:`, error);\n throw error;\n }\n }\n\n private convertIssueToTask(issue: LinearIssueData): Partial<Task> {\n return {\n title: issue.title,\n description: issue.description || '',\n status: this.mapLinearStateToTaskStatus(issue.state.type),\n priority: this.mapLinearPriorityToTaskPriority(issue.priority),\n externalId: issue.id,\n externalIdentifier: issue.identifier,\n externalUrl: issue.url,\n tags: issue.labels?.map((l) => l.name) || [],\n metadata: {\n linear: {\n stateId: issue.state.id,\n stateName: issue.state.name,\n assigneeId: issue.assignee?.id,\n assigneeName: issue.assignee?.name,\n },\n },\n updatedAt: new Date(issue.updatedAt),\n };\n }\n\n private convertTaskToUpdateData(\n task: Task\n ): Partial<LinearCreateIssueInput> & { stateId?: string } {\n return {\n title: task.title,\n description: task.description,\n priority: this.mapTaskPriorityToLinearPriority(task.priority),\n stateId: task.metadata?.linear?.stateId as string | undefined,\n };\n }\n\n private mapLinearStateToTaskStatus(state: string): TaskStatus {\n switch (state.toLowerCase()) {\n case 'backlog':\n case 'triage':\n return 'todo';\n case 'unstarted':\n case 'todo':\n return 'todo';\n case 'started':\n case 'in_progress':\n return 'in_progress';\n case 'completed':\n case 'done':\n return 'done';\n case 'canceled':\n case 'cancelled':\n return 'cancelled';\n default:\n return 'todo';\n }\n }\n\n private mapTaskPriorityToLinearPriority(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 mapLinearPriorityToTaskPriority(\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 hasChanges(existing: Task, updated: Partial<Task>): boolean {\n return (\n existing.title !== updated.title ||\n existing.description !== updated.description ||\n existing.status !== updated.status ||\n existing.priority !== updated.priority ||\n JSON.stringify(existing.tags) !== JSON.stringify(updated.tags)\n );\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { LinearClient, LinearCreateIssueInput } from './client.js';\nimport { ContextService } from '../../services/context-service.js';\nimport { ConfigService } from '../../services/config-service.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport { Task, TaskStatus, TaskPriority } from '../../types/task.js';\nimport { IntegrationError, ErrorCode } from '../../core/errors/index.js';\n\n// Minimal issue data needed for sync (webhook payloads may have fewer fields)\nexport interface LinearIssueData {\n id: string;\n identifier: string;\n title: string;\n description?: string;\n state: { id?: string; name?: string; type: string };\n priority?: number;\n assignee?: { id: string; name: string };\n labels?: Array<{ name: string }>;\n url?: string;\n updatedAt: string;\n}\n\nexport interface SyncResult {\n created: number;\n updated: number;\n deleted: number;\n conflicts: number;\n errors: string[];\n}\n\nexport class LinearSyncService {\n private linearClient: LinearClient;\n private contextService: ContextService;\n private configService: ConfigService;\n // Using singleton logger from monitoring\n\n constructor() {\n // Use singleton logger\n this.configService = new ConfigService();\n this.contextService = new ContextService();\n\n const apiKey = process.env['LINEAR_API_KEY'];\n if (!apiKey) {\n throw new IntegrationError(\n 'LINEAR_API_KEY environment variable not set',\n ErrorCode.LINEAR_AUTH_FAILED\n );\n }\n\n this.linearClient = new LinearClient({ apiKey });\n }\n\n public async syncAllIssues(): Promise<SyncResult> {\n const result: SyncResult = {\n created: 0,\n updated: 0,\n deleted: 0,\n conflicts: 0,\n errors: [],\n };\n\n try {\n const config = await this.configService.getConfig();\n const teamId = config.integrations?.linear?.teamId;\n\n if (!teamId) {\n throw new IntegrationError(\n 'Linear team ID not configured',\n ErrorCode.LINEAR_SYNC_FAILED\n );\n }\n\n const issues = await this.linearClient.getIssues({ teamId });\n\n for (const issue of issues) {\n try {\n const synced = await this.syncIssueToLocal(issue);\n if (synced === 'created') result.created++;\n else if (synced === 'updated') result.updated++;\n } catch (error: unknown) {\n const message =\n error instanceof Error ? error.message : String(error);\n result.errors.push(`Failed to sync ${issue.identifier}: ${message}`);\n }\n }\n\n logger.info(\n `Sync complete: ${result.created} created, ${result.updated} updated`\n );\n } catch (error: unknown) {\n logger.error('Sync failed:', error);\n const message = error instanceof Error ? error.message : String(error);\n result.errors.push(message);\n }\n\n return result;\n }\n\n public async syncIssueToLocal(\n issue: LinearIssueData\n ): Promise<'created' | 'updated' | 'skipped'> {\n try {\n const task = this.convertIssueToTask(issue);\n const existingTask = await this.contextService.getTaskByExternalId(\n issue.id\n );\n\n if (existingTask) {\n if (this.hasChanges(existingTask, task)) {\n await this.contextService.updateTask(existingTask.id, task);\n logger.debug(`Updated task: ${issue.identifier}`);\n return 'updated';\n }\n return 'skipped';\n } else {\n await this.contextService.createTask(task);\n logger.debug(`Created task: ${issue.identifier}`);\n return 'created';\n }\n } catch (error: unknown) {\n logger.error(`Failed to sync issue ${issue.identifier}:`, error);\n throw error;\n }\n }\n\n public async syncLocalToLinear(taskId: string): Promise<any> {\n try {\n const task = await this.contextService.getTask(taskId);\n if (!task) {\n throw new IntegrationError(\n `Task ${taskId} not found`,\n ErrorCode.LINEAR_SYNC_FAILED,\n { taskId }\n );\n }\n\n if (task.externalId) {\n const updateData = this.convertTaskToUpdateData(task);\n const updated = await this.linearClient.updateIssue(\n task.externalId,\n updateData\n );\n logger.debug(`Updated Linear issue: ${updated.identifier}`);\n return updated;\n } else {\n const config = await this.configService.getConfig();\n const teamId = config.integrations?.linear?.teamId;\n if (!teamId) {\n throw new IntegrationError(\n 'Linear team ID not configured',\n ErrorCode.LINEAR_SYNC_FAILED\n );\n }\n const createData: LinearCreateIssueInput = {\n title: task.title,\n description: task.description,\n teamId,\n priority: this.mapTaskPriorityToLinearPriority(task.priority),\n };\n const created = await this.linearClient.createIssue(createData);\n await this.contextService.updateTask(taskId, {\n externalId: created.id,\n });\n logger.debug(`Created Linear issue: ${created.identifier}`);\n return created;\n }\n } catch (error: unknown) {\n logger.error(`Failed to sync task ${taskId} to Linear:`, error);\n throw error;\n }\n }\n\n public async removeLocalIssue(identifier: string): Promise<void> {\n try {\n const tasks = await this.contextService.getAllTasks();\n const task = tasks.find((t) => t.externalIdentifier === identifier);\n\n if (task) {\n await this.contextService.deleteTask(task.id);\n logger.debug(`Removed local task: ${identifier}`);\n }\n } catch (error: unknown) {\n logger.error(`Failed to remove task ${identifier}:`, error);\n throw error;\n }\n }\n\n private convertIssueToTask(issue: LinearIssueData): Partial<Task> {\n return {\n title: issue.title,\n description: issue.description || '',\n status: this.mapLinearStateToTaskStatus(issue.state.type),\n priority: this.mapLinearPriorityToTaskPriority(issue.priority),\n externalId: issue.id,\n externalIdentifier: issue.identifier,\n externalUrl: issue.url,\n tags: issue.labels?.map((l) => l.name) || [],\n metadata: {\n linear: {\n stateId: issue.state.id,\n stateName: issue.state.name,\n assigneeId: issue.assignee?.id,\n assigneeName: issue.assignee?.name,\n },\n },\n updatedAt: new Date(issue.updatedAt),\n };\n }\n\n private convertTaskToUpdateData(\n task: Task\n ): Partial<LinearCreateIssueInput> & { stateId?: string } {\n return {\n title: task.title,\n description: task.description,\n priority: this.mapTaskPriorityToLinearPriority(task.priority),\n stateId: task.metadata?.linear?.stateId as string | undefined,\n };\n }\n\n private mapLinearStateToTaskStatus(state: string): TaskStatus {\n switch (state.toLowerCase()) {\n case 'backlog':\n case 'triage':\n return 'todo';\n case 'unstarted':\n case 'todo':\n return 'todo';\n case 'started':\n case 'in_progress':\n return 'in_progress';\n case 'completed':\n case 'done':\n return 'done';\n case 'canceled':\n case 'cancelled':\n return 'cancelled';\n default:\n return 'todo';\n }\n }\n\n private mapTaskPriorityToLinearPriority(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 mapLinearPriorityToTaskPriority(\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 hasChanges(existing: Task, updated: Partial<Task>): boolean {\n return (\n existing.title !== updated.title ||\n existing.description !== updated.description ||\n existing.status !== updated.status ||\n existing.priority !== updated.priority ||\n JSON.stringify(existing.tags) !== JSON.stringify(updated.tags)\n );\n }\n}\n"],
5
+ "mappings": ";;;;AAAA,SAAS,oBAA4C;AACrD,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,cAAc;AAEvB,SAAS,kBAAkB,iBAAiB;AAwBrC,MAAM,kBAAkB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGR,cAAc;AAEZ,SAAK,gBAAgB,IAAI,cAAc;AACvC,SAAK,iBAAiB,IAAI,eAAe;AAEzC,UAAM,SAAS,QAAQ,IAAI,gBAAgB;AAC3C,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,QACA,UAAU;AAAA,MACZ;AAAA,IACF;AAEA,SAAK,eAAe,IAAI,aAAa,EAAE,OAAO,CAAC;AAAA,EACjD;AAAA,EAEA,MAAa,gBAAqC;AAChD,UAAM,SAAqB;AAAA,MACzB,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,MACT,WAAW;AAAA,MACX,QAAQ,CAAC;AAAA,IACX;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,cAAc,UAAU;AAClD,YAAM,SAAS,OAAO,cAAc,QAAQ;AAE5C,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI;AAAA,UACR;AAAA,UACA,UAAU;AAAA,QACZ;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,KAAK,aAAa,UAAU,EAAE,OAAO,CAAC;AAE3D,iBAAW,SAAS,QAAQ;AAC1B,YAAI;AACF,gBAAM,SAAS,MAAM,KAAK,iBAAiB,KAAK;AAChD,cAAI,WAAW,UAAW,QAAO;AAAA,mBACxB,WAAW,UAAW,QAAO;AAAA,QACxC,SAAS,OAAgB;AACvB,gBAAM,UACJ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACvD,iBAAO,OAAO,KAAK,kBAAkB,MAAM,UAAU,KAAK,OAAO,EAAE;AAAA,QACrE;AAAA,MACF;AAEA,aAAO;AAAA,QACL,kBAAkB,OAAO,OAAO,aAAa,OAAO,OAAO;AAAA,MAC7D;AAAA,IACF,SAAS,OAAgB;AACvB,aAAO,MAAM,gBAAgB,KAAK;AAClC,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,aAAO,OAAO,KAAK,OAAO;AAAA,IAC5B;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAa,iBACX,OAC4C;AAC5C,QAAI;AACF,YAAM,OAAO,KAAK,mBAAmB,KAAK;AAC1C,YAAM,eAAe,MAAM,KAAK,eAAe;AAAA,QAC7C,MAAM;AAAA,MACR;AAEA,UAAI,cAAc;AAChB,YAAI,KAAK,WAAW,cAAc,IAAI,GAAG;AACvC,gBAAM,KAAK,eAAe,WAAW,aAAa,IAAI,IAAI;AAC1D,iBAAO,MAAM,iBAAiB,MAAM,UAAU,EAAE;AAChD,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,MACT,OAAO;AACL,cAAM,KAAK,eAAe,WAAW,IAAI;AACzC,eAAO,MAAM,iBAAiB,MAAM,UAAU,EAAE;AAChD,eAAO;AAAA,MACT;AAAA,IACF,SAAS,OAAgB;AACvB,aAAO,MAAM,wBAAwB,MAAM,UAAU,KAAK,KAAK;AAC/D,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAa,kBAAkB,QAA8B;AAC3D,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,eAAe,QAAQ,MAAM;AACrD,UAAI,CAAC,MAAM;AACT,cAAM,IAAI;AAAA,UACR,QAAQ,MAAM;AAAA,UACd,UAAU;AAAA,UACV,EAAE,OAAO;AAAA,QACX;AAAA,MACF;AAEA,UAAI,KAAK,YAAY;AACnB,cAAM,aAAa,KAAK,wBAAwB,IAAI;AACpD,cAAM,UAAU,MAAM,KAAK,aAAa;AAAA,UACtC,KAAK;AAAA,UACL;AAAA,QACF;AACA,eAAO,MAAM,yBAAyB,QAAQ,UAAU,EAAE;AAC1D,eAAO;AAAA,MACT,OAAO;AACL,cAAM,SAAS,MAAM,KAAK,cAAc,UAAU;AAClD,cAAM,SAAS,OAAO,cAAc,QAAQ;AAC5C,YAAI,CAAC,QAAQ;AACX,gBAAM,IAAI;AAAA,YACR;AAAA,YACA,UAAU;AAAA,UACZ;AAAA,QACF;AACA,cAAM,aAAqC;AAAA,UACzC,OAAO,KAAK;AAAA,UACZ,aAAa,KAAK;AAAA,UAClB;AAAA,UACA,UAAU,KAAK,gCAAgC,KAAK,QAAQ;AAAA,QAC9D;AACA,cAAM,UAAU,MAAM,KAAK,aAAa,YAAY,UAAU;AAC9D,cAAM,KAAK,eAAe,WAAW,QAAQ;AAAA,UAC3C,YAAY,QAAQ;AAAA,QACtB,CAAC;AACD,eAAO,MAAM,yBAAyB,QAAQ,UAAU,EAAE;AAC1D,eAAO;AAAA,MACT;AAAA,IACF,SAAS,OAAgB;AACvB,aAAO,MAAM,uBAAuB,MAAM,eAAe,KAAK;AAC9D,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAa,iBAAiB,YAAmC;AAC/D,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,eAAe,YAAY;AACpD,YAAM,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,uBAAuB,UAAU;AAElE,UAAI,MAAM;AACR,cAAM,KAAK,eAAe,WAAW,KAAK,EAAE;AAC5C,eAAO,MAAM,uBAAuB,UAAU,EAAE;AAAA,MAClD;AAAA,IACF,SAAS,OAAgB;AACvB,aAAO,MAAM,yBAAyB,UAAU,KAAK,KAAK;AAC1D,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEQ,mBAAmB,OAAuC;AAChE,WAAO;AAAA,MACL,OAAO,MAAM;AAAA,MACb,aAAa,MAAM,eAAe;AAAA,MAClC,QAAQ,KAAK,2BAA2B,MAAM,MAAM,IAAI;AAAA,MACxD,UAAU,KAAK,gCAAgC,MAAM,QAAQ;AAAA,MAC7D,YAAY,MAAM;AAAA,MAClB,oBAAoB,MAAM;AAAA,MAC1B,aAAa,MAAM;AAAA,MACnB,MAAM,MAAM,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC;AAAA,MAC3C,UAAU;AAAA,QACR,QAAQ;AAAA,UACN,SAAS,MAAM,MAAM;AAAA,UACrB,WAAW,MAAM,MAAM;AAAA,UACvB,YAAY,MAAM,UAAU;AAAA,UAC5B,cAAc,MAAM,UAAU;AAAA,QAChC;AAAA,MACF;AAAA,MACA,WAAW,IAAI,KAAK,MAAM,SAAS;AAAA,IACrC;AAAA,EACF;AAAA,EAEQ,wBACN,MACwD;AACxD,WAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK;AAAA,MAClB,UAAU,KAAK,gCAAgC,KAAK,QAAQ;AAAA,MAC5D,SAAS,KAAK,UAAU,QAAQ;AAAA,IAClC;AAAA,EACF;AAAA,EAEQ,2BAA2B,OAA2B;AAC5D,YAAQ,MAAM,YAAY,GAAG;AAAA,MAC3B,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,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEQ,gCAAgC,UAAiC;AACvE,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,gCACN,UAC0B;AAC1B,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,WAAW,UAAgB,SAAiC;AAClE,WACE,SAAS,UAAU,QAAQ,SAC3B,SAAS,gBAAgB,QAAQ,eACjC,SAAS,WAAW,QAAQ,UAC5B,SAAS,aAAa,QAAQ,YAC9B,KAAK,UAAU,SAAS,IAAI,MAAM,KAAK,UAAU,QAAQ,IAAI;AAAA,EAEjE;AACF;",
6
6
  "names": []
7
7
  }
@@ -5,6 +5,7 @@ const __dirname = __pathDirname(__filename);
5
5
  import { readFileSync, writeFileSync, existsSync } from "fs";
6
6
  import { join } from "path";
7
7
  import { logger } from "../../core/monitoring/logger.js";
8
+ import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
8
9
  import { LinearClient } from "./client.js";
9
10
  class LinearSyncEngine {
10
11
  taskStore;
@@ -32,8 +33,9 @@ class LinearSyncEngine {
32
33
  } else {
33
34
  const tokens = this.authManager.loadTokens();
34
35
  if (!tokens) {
35
- throw new Error(
36
- 'Linear API key or authentication tokens not found. Set LINEAR_API_KEY environment variable or run "stackmemory linear setup" first.'
36
+ throw new IntegrationError(
37
+ 'Linear API key or authentication tokens not found. Set LINEAR_API_KEY environment variable or run "stackmemory linear setup" first.',
38
+ ErrorCode.LINEAR_SYNC_FAILED
37
39
  );
38
40
  }
39
41
  this.linearClient = new LinearClient({
@@ -620,7 +622,10 @@ class LinearDuplicateDetector {
620
622
  const matchingIssues = allIssues.filter((issue) => {
621
623
  const issueNormalized = this.normalizeTitle(issue.title);
622
624
  if (issueNormalized === normalizedTitle) return true;
623
- const similarity = this.calculateSimilarity(normalizedTitle, issueNormalized);
625
+ const similarity = this.calculateSimilarity(
626
+ normalizedTitle,
627
+ issueNormalized
628
+ );
624
629
  return similarity > 0.85;
625
630
  });
626
631
  this.titleCache.set(normalizedTitle, matchingIssues);
@@ -705,7 +710,10 @@ ${additionalContext}`;
705
710
  }
706
711
  return existingIssue;
707
712
  } catch (error) {
708
- logger.error("Failed to merge into existing Linear issue:", error);
713
+ logger.error(
714
+ "Failed to merge into existing Linear issue:",
715
+ error
716
+ );
709
717
  return existingIssue;
710
718
  }
711
719
  }