@stackmemoryai/stackmemory 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/core/agent-task-manager.js +5 -5
- package/dist/agents/core/agent-task-manager.js.map +2 -2
- package/dist/agents/verifiers/base-verifier.js +2 -2
- package/dist/agents/verifiers/base-verifier.js.map +2 -2
- package/dist/cli/claude-sm.js +0 -11
- package/dist/cli/claude-sm.js.map +2 -2
- package/dist/cli/codex-sm.js +0 -11
- package/dist/cli/codex-sm.js.map +2 -2
- package/dist/cli/commands/chromadb.js +64 -34
- package/dist/cli/commands/chromadb.js.map +2 -2
- package/dist/cli/commands/clear.js +9 -13
- package/dist/cli/commands/clear.js.map +2 -2
- package/dist/cli/commands/config.js +43 -33
- package/dist/cli/commands/config.js.map +2 -2
- package/dist/cli/commands/context.js.map +2 -2
- package/dist/cli/commands/dashboard.js +41 -13
- package/dist/cli/commands/dashboard.js.map +2 -2
- package/dist/cli/commands/gc.js +69 -20
- package/dist/cli/commands/gc.js.map +2 -2
- package/dist/cli/commands/handoff.js.map +2 -2
- package/dist/cli/commands/infinite-storage.js +60 -19
- package/dist/cli/commands/infinite-storage.js.map +2 -2
- package/dist/cli/commands/linear-create.js +36 -8
- package/dist/cli/commands/linear-create.js.map +2 -2
- package/dist/cli/commands/linear-list.js +33 -10
- package/dist/cli/commands/linear-list.js.map +2 -2
- package/dist/cli/commands/linear-migrate.js +17 -4
- package/dist/cli/commands/linear-migrate.js.map +2 -2
- package/dist/cli/commands/linear-test.js +14 -6
- package/dist/cli/commands/linear-test.js.map +2 -2
- package/dist/cli/commands/linear-unified.js +123 -35
- package/dist/cli/commands/linear-unified.js.map +2 -2
- package/dist/cli/commands/linear.js.map +2 -2
- package/dist/cli/commands/monitor.js.map +2 -2
- package/dist/cli/commands/onboard.js +35 -8
- package/dist/cli/commands/onboard.js.map +2 -2
- package/dist/cli/commands/quality.js +2 -7
- package/dist/cli/commands/quality.js.map +2 -2
- package/dist/cli/commands/session.js +23 -6
- package/dist/cli/commands/session.js.map +2 -2
- package/dist/cli/commands/skills.js +72 -27
- package/dist/cli/commands/skills.js.map +2 -2
- package/dist/cli/commands/storage.js +108 -38
- package/dist/cli/commands/storage.js.map +2 -2
- package/dist/cli/commands/tui.js.map +2 -2
- package/dist/cli/commands/webhook.js +57 -18
- package/dist/cli/commands/webhook.js.map +2 -2
- package/dist/cli/commands/workflow.js +8 -15
- package/dist/cli/commands/workflow.js.map +2 -2
- package/dist/cli/commands/worktree.js +34 -13
- package/dist/cli/commands/worktree.js.map +2 -2
- package/dist/cli/index.js +0 -11
- package/dist/cli/index.js.map +2 -2
- package/dist/core/config/types.js.map +1 -1
- package/dist/core/context/auto-context.js +10 -6
- package/dist/core/context/auto-context.js.map +2 -2
- package/dist/core/context/context-bridge.js.map +2 -2
- package/dist/core/context/frame-database.js +13 -3
- package/dist/core/context/frame-database.js.map +2 -2
- package/dist/core/context/frame-digest.js +7 -5
- package/dist/core/context/frame-digest.js.map +2 -2
- package/dist/core/context/frame-manager.js.map +2 -2
- package/dist/core/context/frame-stack.js +16 -5
- package/dist/core/context/frame-stack.js.map +2 -2
- package/dist/core/context/incremental-gc.js +10 -3
- package/dist/core/context/incremental-gc.js.map +2 -2
- package/dist/core/context/index.js.map +1 -1
- package/dist/core/context/permission-manager.js.map +2 -2
- package/dist/core/context/recursive-context-manager.js +582 -0
- package/dist/core/context/recursive-context-manager.js.map +7 -0
- package/dist/core/context/refactored-frame-manager.js +12 -3
- package/dist/core/context/refactored-frame-manager.js.map +2 -2
- package/dist/core/context/shared-context-layer.js +4 -2
- package/dist/core/context/shared-context-layer.js.map +2 -2
- package/dist/core/database/batch-operations.js +112 -86
- package/dist/core/database/batch-operations.js.map +2 -2
- package/dist/core/database/query-cache.js +19 -9
- package/dist/core/database/query-cache.js.map +2 -2
- package/dist/core/database/sqlite-adapter.js +1 -1
- package/dist/core/database/sqlite-adapter.js.map +2 -2
- package/dist/core/digest/enhanced-hybrid-digest.js +8 -2
- package/dist/core/digest/enhanced-hybrid-digest.js.map +2 -2
- package/dist/core/errors/recovery.js +9 -2
- package/dist/core/errors/recovery.js.map +2 -2
- package/dist/core/execution/parallel-executor.js +254 -0
- package/dist/core/execution/parallel-executor.js.map +7 -0
- package/dist/core/frame/workflow-templates-stub.js.map +1 -1
- package/dist/core/frame/workflow-templates.js +40 -1
- package/dist/core/frame/workflow-templates.js.map +2 -2
- package/dist/core/monitoring/logger.js +6 -1
- package/dist/core/monitoring/logger.js.map +2 -2
- package/dist/core/monitoring/metrics.js.map +2 -2
- package/dist/core/monitoring/progress-tracker.js.map +2 -2
- package/dist/core/performance/context-cache.js.map +2 -2
- package/dist/core/performance/lazy-context-loader.js +24 -20
- package/dist/core/performance/lazy-context-loader.js.map +2 -2
- package/dist/core/performance/optimized-frame-context.js +27 -12
- package/dist/core/performance/optimized-frame-context.js.map +2 -2
- package/dist/core/performance/performance-benchmark.js +10 -6
- package/dist/core/performance/performance-benchmark.js.map +2 -2
- package/dist/core/performance/performance-profiler.js +51 -14
- package/dist/core/performance/performance-profiler.js.map +2 -2
- package/dist/core/performance/streaming-jsonl-parser.js +5 -1
- package/dist/core/performance/streaming-jsonl-parser.js.map +2 -2
- package/dist/core/projects/project-manager.js +14 -20
- package/dist/core/projects/project-manager.js.map +2 -2
- package/dist/core/retrieval/context-retriever.js.map +1 -1
- package/dist/core/retrieval/llm-context-retrieval.js.map +2 -2
- package/dist/core/session/clear-survival-stub.js +5 -1
- package/dist/core/session/clear-survival-stub.js.map +2 -2
- package/dist/core/session/clear-survival.js +35 -0
- package/dist/core/session/clear-survival.js.map +2 -2
- package/dist/core/session/index.js.map +1 -1
- package/dist/core/session/session-manager.js.map +2 -2
- package/dist/core/storage/chromadb-adapter.js +6 -2
- package/dist/core/storage/chromadb-adapter.js.map +2 -2
- package/dist/core/storage/chromadb-simple.js +17 -5
- package/dist/core/storage/chromadb-simple.js.map +2 -2
- package/dist/core/storage/infinite-storage.js +109 -46
- package/dist/core/storage/infinite-storage.js.map +2 -2
- package/dist/core/storage/railway-optimized-storage.js +48 -22
- package/dist/core/storage/railway-optimized-storage.js.map +2 -2
- package/dist/core/storage/remote-storage.js +41 -23
- package/dist/core/storage/remote-storage.js.map +2 -2
- package/dist/core/trace/cli-trace-wrapper.js +9 -2
- package/dist/core/trace/cli-trace-wrapper.js.map +2 -2
- package/dist/core/trace/db-trace-wrapper.js +96 -68
- package/dist/core/trace/db-trace-wrapper.js.map +2 -2
- package/dist/core/trace/debug-trace.js +25 -8
- package/dist/core/trace/debug-trace.js.map +2 -2
- package/dist/core/trace/index.js +6 -2
- package/dist/core/trace/index.js.map +2 -2
- package/dist/core/trace/linear-api-wrapper.js +10 -5
- package/dist/core/trace/linear-api-wrapper.js.map +2 -2
- package/dist/core/trace/trace-demo.js +14 -10
- package/dist/core/trace/trace-demo.js.map +2 -2
- package/dist/core/trace/trace-detector.js +9 -2
- package/dist/core/trace/trace-detector.js.map +2 -2
- package/dist/core/trace/types.js.map +1 -1
- package/dist/core/utils/compression.js.map +1 -1
- package/dist/core/utils/update-checker.js.map +1 -1
- package/dist/core/worktree/worktree-manager.js +18 -7
- package/dist/core/worktree/worktree-manager.js.map +2 -2
- package/dist/features/analytics/core/analytics-service.js.map +2 -2
- package/dist/features/analytics/queries/metrics-queries.js +1 -1
- package/dist/features/analytics/queries/metrics-queries.js.map +2 -2
- package/dist/features/tasks/pebbles-task-store.js.map +1 -1
- package/dist/features/tui/components/analytics-panel.js +36 -15
- package/dist/features/tui/components/analytics-panel.js.map +2 -2
- package/dist/features/tui/components/pr-tracker.js +19 -7
- package/dist/features/tui/components/pr-tracker.js.map +2 -2
- package/dist/features/tui/components/session-monitor.js +22 -9
- package/dist/features/tui/components/session-monitor.js.map +2 -2
- package/dist/features/tui/components/subagent-fleet.js +20 -13
- package/dist/features/tui/components/subagent-fleet.js.map +2 -2
- package/dist/features/tui/components/task-board.js +26 -10
- package/dist/features/tui/components/task-board.js.map +2 -2
- package/dist/features/tui/index.js.map +2 -2
- package/dist/features/tui/services/data-service.js +6 -2
- package/dist/features/tui/services/data-service.js.map +2 -2
- package/dist/features/tui/services/linear-task-reader.js +3 -1
- package/dist/features/tui/services/linear-task-reader.js.map +2 -2
- package/dist/features/tui/services/websocket-client.js +3 -1
- package/dist/features/tui/services/websocket-client.js.map +2 -2
- package/dist/features/tui/terminal-compat.js +6 -2
- package/dist/features/tui/terminal-compat.js.map +2 -2
- package/dist/features/web/client/stores/task-store.js.map +2 -2
- package/dist/features/web/server/index.js +18 -10
- package/dist/features/web/server/index.js.map +2 -2
- package/dist/integrations/anthropic/client.js +259 -0
- package/dist/integrations/anthropic/client.js.map +7 -0
- package/dist/integrations/claude-code/subagent-client.js +404 -0
- package/dist/integrations/claude-code/subagent-client.js.map +7 -0
- package/dist/integrations/linear/sync-service.js +12 -13
- package/dist/integrations/linear/sync-service.js.map +2 -2
- package/dist/integrations/linear/sync.js +174 -12
- package/dist/integrations/linear/sync.js.map +2 -2
- package/dist/integrations/linear/unified-sync.js +1 -1
- package/dist/integrations/linear/unified-sync.js.map +1 -1
- package/dist/integrations/linear/webhook-server.js +15 -16
- package/dist/integrations/linear/webhook-server.js.map +2 -2
- package/dist/mcp/stackmemory-mcp-server.js +0 -11
- package/dist/mcp/stackmemory-mcp-server.js.map +2 -2
- package/dist/servers/production/auth-middleware.js.map +2 -2
- package/dist/servers/railway/index.js.map +2 -2
- package/dist/services/config-service.js +6 -7
- package/dist/services/config-service.js.map +2 -2
- package/dist/services/context-service.js +11 -12
- package/dist/services/context-service.js.map +2 -2
- package/dist/skills/claude-skills.js +101 -2
- package/dist/skills/claude-skills.js.map +2 -2
- package/dist/skills/dashboard-launcher.js.map +2 -2
- package/dist/skills/recursive-agent-orchestrator.js +559 -0
- package/dist/skills/recursive-agent-orchestrator.js.map +7 -0
- package/dist/skills/repo-ingestion-skill.js.map +2 -2
- package/dist/skills/security-secrets-scanner.js +265 -0
- package/dist/skills/security-secrets-scanner.js.map +7 -0
- package/dist/utils/env.js +46 -0
- package/dist/utils/env.js.map +7 -0
- package/dist/utils/logger.js +0 -11
- package/dist/utils/logger.js.map +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/integrations/claude-code/subagent-client.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Claude Code Subagent Client\n * \n * Uses Claude Code's Task tool to spawn subagents instead of direct API calls\n * This leverages the Claude Code Max plan for unlimited subagent execution\n */\n\nimport { logger } from '../../core/monitoring/logger.js';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\n\nconst execAsync = promisify(exec);\n\nexport interface SubagentRequest {\n type: 'planning' | 'code' | 'testing' | 'linting' | 'review' | 'improve' | 'context' | 'publish';\n task: string;\n context: Record<string, any>;\n systemPrompt?: string;\n files?: string[];\n timeout?: number;\n}\n\nexport interface SubagentResponse {\n success: boolean;\n result: any;\n output?: string;\n error?: string;\n tokens?: number;\n duration: number;\n subagentType: string;\n}\n\n/**\n * Claude Code Subagent Client\n * Spawns subagents using Claude Code's Task tool\n */\nexport class ClaudeCodeSubagentClient {\n private tempDir: string;\n private activeSubagents: Map<string, AbortController> = new Map();\n \n constructor() {\n // Create temp directory for subagent communication\n this.tempDir = path.join(os.tmpdir(), 'stackmemory-rlm');\n if (!fs.existsSync(this.tempDir)) {\n fs.mkdirSync(this.tempDir, { recursive: true });\n }\n \n logger.info('Claude Code Subagent Client initialized', {\n tempDir: this.tempDir,\n });\n }\n \n /**\n * Execute a subagent task using Claude Code's Task tool\n * This will spawn a new Claude instance with specific instructions\n */\n async executeSubagent(request: SubagentRequest): Promise<SubagentResponse> {\n const startTime = Date.now();\n const subagentId = `${request.type}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n \n logger.info(`Spawning ${request.type} subagent`, {\n subagentId,\n task: request.task.slice(0, 100),\n });\n \n try {\n // Create subagent prompt based on type\n const prompt = this.buildSubagentPrompt(request);\n \n // Write context to temp file for large contexts\n const contextFile = path.join(this.tempDir, `${subagentId}-context.json`);\n await fs.promises.writeFile(\n contextFile,\n JSON.stringify(request.context, null, 2)\n );\n \n // Create result file path\n const resultFile = path.join(this.tempDir, `${subagentId}-result.json`);\n \n // Build the Task tool command\n // The Task tool will spawn a new Claude instance with the specified prompt\n const taskCommand = this.buildTaskCommand(request, prompt, contextFile, resultFile);\n \n // Execute via Claude Code's Task tool\n const result = await this.executeTaskTool(taskCommand, request.timeout);\n \n // Read result from file\n let subagentResult: any = {};\n if (fs.existsSync(resultFile)) {\n const resultContent = await fs.promises.readFile(resultFile, 'utf-8');\n try {\n subagentResult = JSON.parse(resultContent);\n } catch (e) {\n subagentResult = { rawOutput: resultContent };\n }\n }\n \n // Cleanup temp files\n this.cleanup(subagentId);\n \n return {\n success: true,\n result: subagentResult,\n output: result.stdout,\n duration: Date.now() - startTime,\n subagentType: request.type,\n tokens: this.estimateTokens(prompt + JSON.stringify(subagentResult)),\n };\n \n } catch (error: any) {\n logger.error(`Subagent execution failed: ${request.type}`, { error, subagentId });\n \n return {\n success: false,\n result: null,\n error: error.message,\n duration: Date.now() - startTime,\n subagentType: request.type,\n };\n }\n }\n \n /**\n * Execute multiple subagents in parallel\n */\n async executeParallel(requests: SubagentRequest[]): Promise<SubagentResponse[]> {\n logger.info(`Executing ${requests.length} subagents in parallel`);\n \n const promises = requests.map(request => this.executeSubagent(request));\n const results = await Promise.allSettled(promises);\n \n return results.map((result, index) => {\n if (result.status === 'fulfilled') {\n return result.value;\n } else {\n return {\n success: false,\n result: null,\n error: result.reason?.message || 'Unknown error',\n duration: 0,\n subagentType: requests[index].type,\n };\n }\n });\n }\n \n /**\n * Build subagent prompt based on type\n */\n private buildSubagentPrompt(request: SubagentRequest): string {\n const prompts: Record<string, string> = {\n planning: `You are a Planning Subagent. Your role is to decompose complex tasks into manageable subtasks.\n \n Task: ${request.task}\n \n Instructions:\n 1. Analyze the task and identify all components\n 2. Create a dependency graph of subtasks\n 3. Assign appropriate agent types to each subtask\n 4. Consider parallel execution opportunities\n 5. Include comprehensive testing at each stage\n \n Context is available in the provided file.\n \n Output a JSON structure with the task decomposition.`,\n \n code: `You are a Code Generation Subagent. Your role is to implement high-quality, production-ready code.\n \n Task: ${request.task}\n \n Instructions:\n 1. Write clean, maintainable code\n 2. Follow project conventions (check context)\n 3. Include comprehensive error handling\n 4. Add clear comments for complex logic\n 5. Ensure code is testable\n \n Context and requirements are in the provided file.\n \n Output the implementation code.`,\n \n testing: `You are a Testing Subagent specializing in comprehensive test generation.\n \n Task: ${request.task}\n \n Instructions:\n 1. Generate unit tests for all functions/methods\n 2. Create integration tests for API endpoints\n 3. Add E2E tests for critical user flows\n 4. Include edge cases and error scenarios\n 5. Ensure high code coverage (aim for 100%)\n 6. Validate that all tests pass\n \n Context and code to test are in the provided file.\n \n Output a complete test suite.`,\n \n linting: `You are a Linting Subagent ensuring code quality and standards.\n \n Task: ${request.task}\n \n Instructions:\n 1. Check for syntax errors and type issues\n 2. Verify code formatting and style\n 3. Identify security vulnerabilities\n 4. Find performance anti-patterns\n 5. Detect unused imports and dead code\n 6. Provide specific fixes for each issue\n \n Code to analyze is in the context file.\n \n Output a JSON report with issues and fixes.`,\n \n review: `You are a Code Review Subagent performing thorough multi-stage reviews.\n \n Task: ${request.task}\n \n Instructions:\n 1. Evaluate architecture and design patterns\n 2. Assess code quality and maintainability\n 3. Check performance implications\n 4. Review security considerations\n 5. Verify test coverage adequacy\n 6. Suggest specific improvements with examples\n 7. Rate quality on a 0-1 scale\n \n Code and context are in the provided file.\n \n Output a detailed review with quality score and improvements.`,\n \n improve: `You are an Improvement Subagent enhancing code based on reviews.\n \n Task: ${request.task}\n \n Instructions:\n 1. Implement all suggested improvements\n 2. Refactor for better architecture\n 3. Optimize performance bottlenecks\n 4. Enhance error handling\n 5. Improve code clarity and documentation\n 6. Add missing test cases\n 7. Ensure backward compatibility\n \n Review feedback and code are in the context file.\n \n Output the improved code.`,\n \n context: `You are a Context Retrieval Subagent finding relevant information.\n \n Task: ${request.task}\n \n Instructions:\n 1. Search project codebase for relevant code\n 2. Find similar implementations\n 3. Locate relevant documentation\n 4. Identify dependencies and patterns\n 5. Retrieve best practices\n \n Search parameters are in the context file.\n \n Output relevant context snippets.`,\n \n publish: `You are a Publishing Subagent handling releases and deployments.\n \n Task: ${request.task}\n \n Instructions:\n 1. Prepare package for publishing\n 2. Update version numbers\n 3. Generate changelog\n 4. Create GitHub release\n 5. Publish to NPM if applicable\n 6. Update documentation\n \n Release details are in the context file.\n \n Output the release plan and commands.`,\n };\n \n return request.systemPrompt || prompts[request.type] || prompts.planning;\n }\n \n /**\n * Build Task tool command\n * This creates a command that Claude Code's Task tool can execute\n */\n private buildTaskCommand(\n request: SubagentRequest,\n prompt: string,\n contextFile: string,\n resultFile: string\n ): string {\n // Create a script that the subagent will execute\n const scriptContent = `\n#!/bin/bash\n# Subagent execution script for ${request.type}\n\n# Read context\nCONTEXT=$(cat \"${contextFile}\")\n\n# Execute task based on type\ncase \"${request.type}\" in\n \"testing\")\n # For testing subagent, actually run tests\n echo \"Generating and running tests...\"\n # The subagent will generate test files and run them\n ;;\n \"linting\")\n # For linting subagent, run actual linters\n echo \"Running linters...\"\n npm run lint || true\n ;;\n \"code\")\n # For code generation, create implementation files\n echo \"Generating implementation...\"\n ;;\n *)\n # Default behavior\n echo \"Executing ${request.type} task...\"\n ;;\nesac\n\n# Write result\necho '{\"status\": \"completed\", \"type\": \"${request.type}\"}' > \"${resultFile}\"\n`;\n \n const scriptFile = path.join(this.tempDir, `${request.type}-script.sh`);\n fs.writeFileSync(scriptFile, scriptContent);\n fs.chmodSync(scriptFile, '755');\n \n // Return the command that Task tool will execute\n // In practice, this would trigger Claude Code's Task tool\n return scriptFile;\n }\n \n /**\n * Execute via Task tool (simulated for now)\n * In production, this would use Claude Code's actual Task tool API\n */\n private async executeTaskTool(\n command: string,\n timeout?: number\n ): Promise<{ stdout: string; stderr: string }> {\n try {\n // In production, this would call Claude Code's Task tool\n // For now, we simulate with a subprocess\n const result = await execAsync(command, {\n timeout: timeout || 300000, // 5 minutes default\n maxBuffer: 10 * 1024 * 1024, // 10MB buffer\n });\n \n return result;\n } catch (error: any) {\n if (error.killed || error.signal === 'SIGTERM') {\n throw new Error(`Subagent timeout after ${timeout}ms`);\n }\n throw error;\n }\n }\n \n /**\n * Estimate token usage\n */\n private estimateTokens(text: string): number {\n // Rough estimation: 1 token \u2248 4 characters\n return Math.ceil(text.length / 4);\n }\n \n /**\n * Cleanup temporary files\n */\n private cleanup(subagentId: string): void {\n const patterns = [\n `${subagentId}-context.json`,\n `${subagentId}-result.json`,\n `${subagentId}-script.sh`,\n ];\n \n for (const pattern of patterns) {\n const filePath = path.join(this.tempDir, pattern);\n if (fs.existsSync(filePath)) {\n try {\n fs.unlinkSync(filePath);\n } catch (e) {\n // Ignore cleanup errors\n }\n }\n }\n }\n \n /**\n * Create a mock Task tool response for development\n * This simulates what Claude Code's Task tool would return\n */\n async mockTaskToolExecution(request: SubagentRequest): Promise<SubagentResponse> {\n const startTime = Date.now();\n \n // Simulate processing delay\n await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 2000));\n \n // Generate mock responses based on subagent type\n const mockResponses: Record<string, any> = {\n planning: {\n tasks: [\n { id: '1', type: 'analyze', description: 'Analyze requirements' },\n { id: '2', type: 'implement', description: 'Implement solution' },\n { id: '3', type: 'test', description: 'Test implementation' },\n { id: '4', type: 'review', description: 'Review and improve' },\n ],\n dependencies: { '2': ['1'], '3': ['2'], '4': ['3'] },\n },\n code: {\n implementation: `\nexport class Solution {\n constructor(private config: any) {}\n \n async execute(input: string): Promise<string> {\n // Implementation generated by Code subagent\n return this.process(input);\n }\n \n private process(input: string): string {\n return \\`Processed: \\${input}\\`;\n }\n}`,\n files: ['src/solution.ts'],\n },\n testing: {\n tests: `\ndescribe('Solution', () => {\n it('should process input correctly', () => {\n const solution = new Solution({});\n const result = solution.execute('test');\n expect(result).toBe('Processed: test');\n });\n \n it('should handle edge cases', () => {\n // Edge case tests\n });\n});`,\n coverage: { lines: 95, branches: 88, functions: 100 },\n },\n review: {\n quality: 0.82,\n issues: [\n { severity: 'high', message: 'Missing error handling' },\n { severity: 'medium', message: 'Could improve type safety' },\n ],\n suggestions: [\n 'Add try-catch blocks',\n 'Use stricter TypeScript types',\n 'Add input validation',\n ],\n },\n };\n \n return {\n success: true,\n result: mockResponses[request.type] || { status: 'completed' },\n output: `Mock ${request.type} subagent completed successfully`,\n duration: Date.now() - startTime,\n subagentType: request.type,\n tokens: Math.floor(Math.random() * 5000) + 1000,\n };\n }\n \n /**\n * Get active subagent statistics\n */\n getStats() {\n return {\n activeSubagents: this.activeSubagents.size,\n tempDir: this.tempDir,\n };\n }\n \n /**\n * Cleanup all resources\n */\n async cleanupAll(): Promise<void> {\n // Abort all active subagents\n for (const [id, controller] of this.activeSubagents) {\n controller.abort();\n }\n this.activeSubagents.clear();\n \n // Clean temp directory\n if (fs.existsSync(this.tempDir)) {\n const files = await fs.promises.readdir(this.tempDir);\n for (const file of files) {\n await fs.promises.unlink(path.join(this.tempDir, file));\n }\n }\n \n logger.info('Claude Code Subagent Client cleaned up');\n }\n}"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LinearClient } from "./client.js";
|
|
2
2
|
import { ContextService } from "../../services/context-service.js";
|
|
3
3
|
import { ConfigService } from "../../services/config-service.js";
|
|
4
|
-
import {
|
|
4
|
+
import { logger } from "../../core/monitoring/logger.js";
|
|
5
5
|
function getEnv(key, defaultValue) {
|
|
6
6
|
const value = process.env[key];
|
|
7
7
|
if (value === void 0) {
|
|
@@ -17,9 +17,8 @@ class LinearSyncService {
|
|
|
17
17
|
linearClient;
|
|
18
18
|
contextService;
|
|
19
19
|
configService;
|
|
20
|
-
logger
|
|
20
|
+
// Using singleton logger from monitoring
|
|
21
21
|
constructor() {
|
|
22
|
-
this.logger = new Logger("LinearSync");
|
|
23
22
|
this.configService = new ConfigService();
|
|
24
23
|
this.contextService = new ContextService();
|
|
25
24
|
const apiKey = process.env["LINEAR_API_KEY"];
|
|
@@ -53,11 +52,11 @@ class LinearSyncService {
|
|
|
53
52
|
result.errors.push(`Failed to sync ${issue.identifier}: ${message}`);
|
|
54
53
|
}
|
|
55
54
|
}
|
|
56
|
-
|
|
55
|
+
logger.info(
|
|
57
56
|
`Sync complete: ${result.created} created, ${result.updated} updated`
|
|
58
57
|
);
|
|
59
58
|
} catch (error) {
|
|
60
|
-
|
|
59
|
+
logger.error("Sync failed:", error);
|
|
61
60
|
const message = error instanceof Error ? error.message : String(error);
|
|
62
61
|
result.errors.push(message);
|
|
63
62
|
}
|
|
@@ -72,17 +71,17 @@ class LinearSyncService {
|
|
|
72
71
|
if (existingTask) {
|
|
73
72
|
if (this.hasChanges(existingTask, task)) {
|
|
74
73
|
await this.contextService.updateTask(existingTask.id, task);
|
|
75
|
-
|
|
74
|
+
logger.debug(`Updated task: ${issue.identifier}`);
|
|
76
75
|
return "updated";
|
|
77
76
|
}
|
|
78
77
|
return "skipped";
|
|
79
78
|
} else {
|
|
80
79
|
await this.contextService.createTask(task);
|
|
81
|
-
|
|
80
|
+
logger.debug(`Created task: ${issue.identifier}`);
|
|
82
81
|
return "created";
|
|
83
82
|
}
|
|
84
83
|
} catch (error) {
|
|
85
|
-
|
|
84
|
+
logger.error(`Failed to sync issue ${issue.identifier}:`, error);
|
|
86
85
|
throw error;
|
|
87
86
|
}
|
|
88
87
|
}
|
|
@@ -98,7 +97,7 @@ class LinearSyncService {
|
|
|
98
97
|
task.externalId,
|
|
99
98
|
updateData
|
|
100
99
|
);
|
|
101
|
-
|
|
100
|
+
logger.debug(`Updated Linear issue: ${updated.identifier}`);
|
|
102
101
|
return updated;
|
|
103
102
|
} else {
|
|
104
103
|
const config = await this.configService.getConfig();
|
|
@@ -116,11 +115,11 @@ class LinearSyncService {
|
|
|
116
115
|
await this.contextService.updateTask(taskId, {
|
|
117
116
|
externalId: created.id
|
|
118
117
|
});
|
|
119
|
-
|
|
118
|
+
logger.debug(`Created Linear issue: ${created.identifier}`);
|
|
120
119
|
return created;
|
|
121
120
|
}
|
|
122
121
|
} catch (error) {
|
|
123
|
-
|
|
122
|
+
logger.error(`Failed to sync task ${taskId} to Linear:`, error);
|
|
124
123
|
throw error;
|
|
125
124
|
}
|
|
126
125
|
}
|
|
@@ -130,10 +129,10 @@ class LinearSyncService {
|
|
|
130
129
|
const task = tasks.find((t) => t.externalIdentifier === identifier);
|
|
131
130
|
if (task) {
|
|
132
131
|
await this.contextService.deleteTask(task.id);
|
|
133
|
-
|
|
132
|
+
logger.debug(`Removed local task: ${identifier}`);
|
|
134
133
|
}
|
|
135
134
|
} catch (error) {
|
|
136
|
-
|
|
135
|
+
logger.error(`Failed to remove task ${identifier}:`, error);
|
|
137
136
|
throw error;
|
|
138
137
|
}
|
|
139
138
|
}
|
|
@@ -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 {
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAyD;AAClE,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,cAAc;AAGvB,SAAS,OAAO,KAAa,cAA+B;AAC1D,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,UAAU,QAAW;AACvB,QAAI,iBAAiB,OAAW,QAAO;AACvC,UAAM,IAAI,MAAM,wBAAwB,GAAG,cAAc;AAAA,EAC3D;AACA,SAAO;AACT;AAEA,SAAS,eAAe,KAAiC;AACvD,SAAO,QAAQ,IAAI,GAAG;AACxB;AAyBO,MAAM,kBAAkB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA
|
|
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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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,18 +2,6 @@ import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { logger } from "../../core/monitoring/logger.js";
|
|
4
4
|
import { LinearClient } from "./client.js";
|
|
5
|
-
import { LinearDuplicateDetector } from "./sync-enhanced.js";
|
|
6
|
-
function getEnv(key, defaultValue) {
|
|
7
|
-
const value = process.env[key];
|
|
8
|
-
if (value === void 0) {
|
|
9
|
-
if (defaultValue !== void 0) return defaultValue;
|
|
10
|
-
throw new Error(`Environment variable ${key} is required`);
|
|
11
|
-
}
|
|
12
|
-
return value;
|
|
13
|
-
}
|
|
14
|
-
function getOptionalEnv(key) {
|
|
15
|
-
return process.env[key];
|
|
16
|
-
}
|
|
17
5
|
class LinearSyncEngine {
|
|
18
6
|
taskStore;
|
|
19
7
|
linearClient;
|
|
@@ -601,8 +589,182 @@ const DEFAULT_SYNC_CONFIG = {
|
|
|
601
589
|
rateLimitDelay: 500
|
|
602
590
|
// 500ms between API calls
|
|
603
591
|
};
|
|
592
|
+
class LinearDuplicateDetector {
|
|
593
|
+
linearClient;
|
|
594
|
+
titleCache = /* @__PURE__ */ new Map();
|
|
595
|
+
cacheExpiry = 5 * 60 * 1e3;
|
|
596
|
+
// 5 minutes
|
|
597
|
+
lastCacheRefresh = 0;
|
|
598
|
+
constructor(linearClient) {
|
|
599
|
+
this.linearClient = linearClient;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Search for existing Linear issues with similar titles
|
|
603
|
+
*/
|
|
604
|
+
async searchByTitle(title, teamId) {
|
|
605
|
+
const normalizedTitle = this.normalizeTitle(title);
|
|
606
|
+
if (this.isCacheValid()) {
|
|
607
|
+
const cached = this.titleCache.get(normalizedTitle);
|
|
608
|
+
if (cached) return cached;
|
|
609
|
+
}
|
|
610
|
+
try {
|
|
611
|
+
const allIssues = await this.linearClient.getIssues({
|
|
612
|
+
teamId,
|
|
613
|
+
limit: 100
|
|
614
|
+
// Use smaller limit to avoid API errors
|
|
615
|
+
});
|
|
616
|
+
const matchingIssues = allIssues.filter((issue) => {
|
|
617
|
+
const issueNormalized = this.normalizeTitle(issue.title);
|
|
618
|
+
if (issueNormalized === normalizedTitle) return true;
|
|
619
|
+
const similarity = this.calculateSimilarity(normalizedTitle, issueNormalized);
|
|
620
|
+
return similarity > 0.85;
|
|
621
|
+
});
|
|
622
|
+
this.titleCache.set(normalizedTitle, matchingIssues);
|
|
623
|
+
this.lastCacheRefresh = Date.now();
|
|
624
|
+
return matchingIssues;
|
|
625
|
+
} catch (error) {
|
|
626
|
+
logger.error("Failed to search Linear issues by title:", error);
|
|
627
|
+
return [];
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Check if a task title would create a duplicate in Linear
|
|
632
|
+
*/
|
|
633
|
+
async checkForDuplicate(title, teamId) {
|
|
634
|
+
const existingIssues = await this.searchByTitle(title, teamId);
|
|
635
|
+
if (existingIssues.length === 0) {
|
|
636
|
+
return { isDuplicate: false };
|
|
637
|
+
}
|
|
638
|
+
let bestMatch;
|
|
639
|
+
let bestSimilarity = 0;
|
|
640
|
+
for (const issue of existingIssues) {
|
|
641
|
+
const similarity = this.calculateSimilarity(
|
|
642
|
+
this.normalizeTitle(title),
|
|
643
|
+
this.normalizeTitle(issue.title)
|
|
644
|
+
);
|
|
645
|
+
if (similarity > bestSimilarity) {
|
|
646
|
+
bestSimilarity = similarity;
|
|
647
|
+
bestMatch = issue;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return {
|
|
651
|
+
isDuplicate: true,
|
|
652
|
+
existingIssue: bestMatch,
|
|
653
|
+
similarity: bestSimilarity
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Merge task content into existing Linear issue
|
|
658
|
+
*/
|
|
659
|
+
async mergeIntoExisting(existingIssue, newTitle, newDescription, additionalContext) {
|
|
660
|
+
try {
|
|
661
|
+
let mergedDescription = existingIssue.description || "";
|
|
662
|
+
if (newDescription && !mergedDescription.includes(newDescription)) {
|
|
663
|
+
mergedDescription += `
|
|
664
|
+
|
|
665
|
+
## Additional Context (${(/* @__PURE__ */ new Date()).toISOString()})
|
|
666
|
+
`;
|
|
667
|
+
mergedDescription += newDescription;
|
|
668
|
+
}
|
|
669
|
+
if (additionalContext) {
|
|
670
|
+
mergedDescription += `
|
|
671
|
+
|
|
672
|
+
---
|
|
673
|
+
${additionalContext}`;
|
|
674
|
+
}
|
|
675
|
+
const updateQuery = `
|
|
676
|
+
mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
|
|
677
|
+
issueUpdate(id: $id, input: $input) {
|
|
678
|
+
issue {
|
|
679
|
+
id
|
|
680
|
+
identifier
|
|
681
|
+
title
|
|
682
|
+
description
|
|
683
|
+
updatedAt
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
`;
|
|
688
|
+
const variables = {
|
|
689
|
+
id: existingIssue.id,
|
|
690
|
+
input: {
|
|
691
|
+
description: mergedDescription
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
const response = await this.linearClient.graphql(updateQuery, variables);
|
|
695
|
+
const updatedIssue = response.issueUpdate?.issue;
|
|
696
|
+
if (updatedIssue) {
|
|
697
|
+
logger.info(
|
|
698
|
+
`Merged content into existing Linear issue ${existingIssue.identifier}: ${existingIssue.title}`
|
|
699
|
+
);
|
|
700
|
+
return updatedIssue;
|
|
701
|
+
}
|
|
702
|
+
return existingIssue;
|
|
703
|
+
} catch (error) {
|
|
704
|
+
logger.error("Failed to merge into existing Linear issue:", error);
|
|
705
|
+
return existingIssue;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Normalize title for comparison
|
|
710
|
+
*/
|
|
711
|
+
normalizeTitle(title) {
|
|
712
|
+
return title.toLowerCase().trim().replace(/\s+/g, " ").replace(/[^\w\s-]/g, "").replace(/^(sta|eng|bug|feat|task|tsk)[-\s]\d+[-\s:]*/, "").trim();
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Calculate similarity between two strings (Levenshtein distance based)
|
|
716
|
+
*/
|
|
717
|
+
calculateSimilarity(str1, str2) {
|
|
718
|
+
if (str1 === str2) return 1;
|
|
719
|
+
if (str1.length === 0 || str2.length === 0) return 0;
|
|
720
|
+
const distance = this.levenshteinDistance(str1, str2);
|
|
721
|
+
const maxLength = Math.max(str1.length, str2.length);
|
|
722
|
+
return 1 - distance / maxLength;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Calculate Levenshtein distance between two strings
|
|
726
|
+
*/
|
|
727
|
+
levenshteinDistance(str1, str2) {
|
|
728
|
+
const m = str1.length;
|
|
729
|
+
const n = str2.length;
|
|
730
|
+
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
731
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
732
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
733
|
+
for (let i = 1; i <= m; i++) {
|
|
734
|
+
for (let j = 1; j <= n; j++) {
|
|
735
|
+
if (str1[i - 1] === str2[j - 1]) {
|
|
736
|
+
dp[i][j] = dp[i - 1][j - 1];
|
|
737
|
+
} else {
|
|
738
|
+
dp[i][j] = 1 + Math.min(
|
|
739
|
+
dp[i - 1][j],
|
|
740
|
+
// deletion
|
|
741
|
+
dp[i][j - 1],
|
|
742
|
+
// insertion
|
|
743
|
+
dp[i - 1][j - 1]
|
|
744
|
+
// substitution
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return dp[m][n];
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Check if cache is still valid
|
|
753
|
+
*/
|
|
754
|
+
isCacheValid() {
|
|
755
|
+
return Date.now() - this.lastCacheRefresh < this.cacheExpiry;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Clear the title cache
|
|
759
|
+
*/
|
|
760
|
+
clearCache() {
|
|
761
|
+
this.titleCache.clear();
|
|
762
|
+
this.lastCacheRefresh = 0;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
604
765
|
export {
|
|
605
766
|
DEFAULT_SYNC_CONFIG,
|
|
767
|
+
LinearDuplicateDetector,
|
|
606
768
|
LinearSyncEngine
|
|
607
769
|
};
|
|
608
770
|
//# sourceMappingURL=sync.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/integrations/linear/sync.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Linear Bi-directional Sync Engine\n * Handles syncing tasks between StackMemory and Linear\n */\n\nimport { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport { logger } from '../../core/monitoring/logger.js';\nimport {\n PebblesTask,\n PebblesTaskStore,\n TaskStatus,\n TaskPriority,\n} from '../../features/tasks/pebbles-task-store.js';\nimport { LinearClient, LinearIssue, LinearCreateIssueInput } from './client.js';\nimport { LinearAuthManager } from './auth.js';\nimport { LinearDuplicateDetector } from './sync-enhanced.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\nexport interface SyncConfig {\n enabled: boolean;\n direction: 'bidirectional' | 'to_linear' | 'from_linear';\n defaultTeamId?: string;\n autoSync: boolean;\n conflictResolution:\n | 'linear_wins'\n | 'stackmemory_wins'\n | 'manual'\n | 'newest_wins';\n syncInterval?: number; // minutes\n maxBatchSize?: number; // max tasks to sync per batch\n rateLimitDelay?: number; // ms delay between API calls\n}\n\nexport interface SyncResult {\n success: boolean;\n synced: {\n toLinear: number;\n fromLinear: number;\n updated: number;\n };\n conflicts: Array<{\n taskId: string;\n linearId: string;\n reason: string;\n }>;\n errors: string[];\n}\n\nexport interface TaskMapping {\n stackmemoryId: string;\n linearId: string;\n linearIdentifier: string;\n lastSyncTimestamp: number;\n lastLinearUpdate: string;\n lastStackMemoryUpdate: number;\n}\n\nexport class LinearSyncEngine {\n private taskStore: PebblesTaskStore;\n private linearClient: LinearClient;\n private authManager: LinearAuthManager;\n private config: SyncConfig;\n private mappings: Map<string, TaskMapping> = new Map();\n private projectRoot: string;\n private mappingsPath: string;\n\n constructor(\n taskStore: PebblesTaskStore,\n authManager: LinearAuthManager,\n config: SyncConfig,\n projectRoot?: string\n ) {\n this.taskStore = taskStore;\n this.authManager = authManager;\n this.config = config;\n this.projectRoot = projectRoot || process.cwd();\n this.mappingsPath = join(\n this.projectRoot,\n '.stackmemory',\n 'linear-mappings.json'\n );\n\n // Check for API key from environment variable first\n const apiKey = process.env['LINEAR_API_KEY'];\n\n if (apiKey) {\n // Use API key from environment\n this.linearClient = new LinearClient({\n apiKey: apiKey,\n });\n } else {\n // Fall back to OAuth tokens\n const tokens = this.authManager.loadTokens();\n if (!tokens) {\n throw new Error(\n 'Linear API key or authentication tokens not found. Set LINEAR_API_KEY environment variable or run \"stackmemory linear setup\" first.'\n );\n }\n\n this.linearClient = new LinearClient({\n apiKey: tokens.accessToken,\n useBearer: true,\n onUnauthorized: async () => {\n const refreshed = await this.authManager.refreshAccessToken();\n return refreshed.accessToken;\n },\n });\n }\n\n this.loadMappings();\n }\n\n /**\n * Update sync configuration\n */\n updateConfig(newConfig: Partial<SyncConfig>): void {\n this.config = { ...this.config, ...newConfig };\n }\n\n /**\n * Perform bi-directional sync\n */\n async sync(): Promise<SyncResult> {\n if (!this.config.enabled) {\n return {\n success: false,\n synced: { toLinear: 0, fromLinear: 0, updated: 0 },\n conflicts: [],\n errors: ['Sync is disabled'],\n };\n }\n\n const result: SyncResult = {\n success: true,\n synced: { toLinear: 0, fromLinear: 0, updated: 0 },\n conflicts: [],\n errors: [],\n };\n\n try {\n // Update client with valid token if not using environment API key\n const apiKey = process.env['LINEAR_API_KEY'];\n if (!apiKey) {\n const token = await this.authManager.getValidToken();\n this.linearClient = new LinearClient({\n apiKey: token,\n useBearer: true,\n onUnauthorized: async () => {\n const refreshed = await this.authManager.refreshAccessToken();\n return refreshed.accessToken;\n },\n });\n }\n\n // Get team info if not configured\n if (!this.config.defaultTeamId) {\n const team = await this.linearClient.getTeam();\n this.config.defaultTeamId = team.id;\n logger.info(`Using Linear team: ${team.name} (${team.key})`);\n }\n\n // Sync in both directions based on configuration\n if (\n this.config.direction === 'bidirectional' ||\n this.config.direction === 'to_linear'\n ) {\n const toLinearResult = await this.syncToLinear();\n result.synced.toLinear = toLinearResult.created;\n result.synced.updated += toLinearResult.updated;\n result.errors.push(...toLinearResult.errors);\n }\n\n if (\n this.config.direction === 'bidirectional' ||\n this.config.direction === 'from_linear'\n ) {\n const fromLinearResult = await this.syncFromLinear();\n result.synced.fromLinear = fromLinearResult.created;\n result.synced.updated += fromLinearResult.updated;\n result.conflicts.push(...fromLinearResult.conflicts);\n result.errors.push(...fromLinearResult.errors);\n }\n\n this.saveMappings();\n } catch (error: unknown) {\n result.success = false;\n result.errors.push(`Sync failed: ${String(error)}`);\n logger.error('Linear sync failed:', error as Error);\n }\n\n return result;\n }\n\n /**\n * Delay helper for rate limiting\n */\n private async delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Sync tasks from StackMemory to Linear\n */\n private async syncToLinear(): Promise<{\n created: number;\n updated: number;\n errors: string[];\n }> {\n const result = { created: 0, updated: 0, errors: [] as string[] };\n const maxBatchSize = this.config.maxBatchSize || 10;\n const rateLimitDelay = this.config.rateLimitDelay || 500;\n\n // Initialize duplicate detector\n const duplicateDetector = new LinearDuplicateDetector(this.linearClient);\n\n // Get unsynced tasks from StackMemory\n const unsyncedTasks = this.getUnsyncedTasks();\n\n // Limit batch size to avoid rate limits\n const tasksToSync = unsyncedTasks.slice(0, maxBatchSize);\n\n if (unsyncedTasks.length > maxBatchSize) {\n logger.info(\n `Syncing ${tasksToSync.length} of ${unsyncedTasks.length} unsynced tasks (batch limit)`\n );\n }\n\n for (const task of tasksToSync) {\n try {\n // Check for duplicates before creating\n const duplicateCheck = await duplicateDetector.checkForDuplicate(\n task.title,\n this.config.defaultTeamId\n );\n\n let linearIssue: LinearIssue;\n \n if (duplicateCheck.isDuplicate && duplicateCheck.existingIssue) {\n // Found duplicate - merge instead of creating\n logger.info(\n `Found existing Linear issue for \"${task.title}\": ${duplicateCheck.existingIssue.identifier} (${Math.round((duplicateCheck.similarity || 0) * 100)}% match)`\n );\n \n // Merge task content into existing issue\n linearIssue = await duplicateDetector.mergeIntoExisting(\n duplicateCheck.existingIssue,\n task.title,\n this.formatDescriptionForLinear(task),\n `StackMemory Task ID: ${task.id}\\nFrame: ${task.frame_id}`\n );\n } else {\n // No duplicate found, create new issue\n linearIssue = await this.createLinearIssueFromTask(task);\n }\n\n // Create mapping\n const mapping: TaskMapping = {\n stackmemoryId: task.id,\n linearId: linearIssue.id,\n linearIdentifier: linearIssue.identifier,\n lastSyncTimestamp: Date.now(),\n lastLinearUpdate: linearIssue.updatedAt,\n lastStackMemoryUpdate: task.timestamp * 1000,\n };\n\n this.mappings.set(task.id, mapping);\n\n // Update task with Linear reference\n this.updateTaskWithLinearRef(task.id, linearIssue);\n\n result.created++;\n logger.info(\n `Synced task to Linear: ${task.title} \u2192 ${linearIssue.identifier}`\n );\n\n // Rate limit delay between creates\n await this.delay(rateLimitDelay);\n } catch (error: unknown) {\n const errorMsg = String(error);\n // Stop syncing on rate limit errors\n if (\n errorMsg.includes('rate limit') ||\n errorMsg.includes('usage limit')\n ) {\n logger.warn('Rate limit hit, stopping sync batch');\n result.errors.push('Rate limit reached - sync paused');\n break;\n }\n result.errors.push(`Failed to sync task ${task.id}: ${errorMsg}`);\n logger.error(\n `Failed to sync task ${task.id} to Linear:`,\n error as Error\n );\n }\n }\n\n // Update existing Linear issues for modified StackMemory tasks\n const modifiedTasks = this.getModifiedTasks();\n\n for (const task of modifiedTasks) {\n try {\n const mapping = this.mappings.get(task.id);\n if (!mapping) continue;\n\n await this.updateLinearIssueFromTask(task, mapping);\n\n mapping.lastSyncTimestamp = Date.now();\n mapping.lastStackMemoryUpdate = task.timestamp * 1000;\n\n result.updated++;\n logger.info(`Updated Linear issue: ${mapping.linearIdentifier}`);\n } catch (error: unknown) {\n result.errors.push(\n `Failed to update Linear issue for task ${task.id}: ${String(error)}`\n );\n logger.error(\n `Failed to update Linear issue for task ${task.id}:`,\n error as Error\n );\n }\n }\n\n return result;\n }\n\n /**\n * Sync tasks from Linear to StackMemory\n */\n private async syncFromLinear(): Promise<{\n created: number;\n updated: number;\n conflicts: Array<{ taskId: string; linearId: string; reason: string }>;\n errors: string[];\n }> {\n const result = {\n created: 0,\n updated: 0,\n conflicts: [] as Array<{\n taskId: string;\n linearId: string;\n reason: string;\n }>,\n errors: [] as string[],\n };\n\n // First, import any new issues from Linear that aren't mapped yet\n const importResult = await this.importFromLinear();\n result.created = importResult.imported;\n result.errors.push(...importResult.errors);\n\n // Then update existing mapped tasks\n for (const [taskId, mapping] of this.mappings) {\n try {\n const linearIssue = await this.linearClient.getIssue(mapping.linearId);\n\n if (!linearIssue) {\n result.errors.push(`Linear issue ${mapping.linearId} not found`);\n continue;\n }\n\n // Check if Linear issue was updated since last sync\n const linearUpdateTime = new Date(linearIssue.updatedAt).getTime();\n if (linearUpdateTime <= mapping.lastSyncTimestamp) {\n continue; // No changes in Linear\n }\n\n // Check for conflicts\n const task = this.taskStore.getTask(taskId);\n if (!task) {\n result.errors.push(`StackMemory task ${taskId} not found`);\n continue;\n }\n\n const stackMemoryUpdateTime = task.timestamp * 1000;\n\n if (\n stackMemoryUpdateTime > mapping.lastSyncTimestamp &&\n linearUpdateTime > mapping.lastSyncTimestamp\n ) {\n // Conflict: both sides updated since last sync\n result.conflicts.push({\n taskId,\n linearId: mapping.linearId,\n reason: 'Both StackMemory and Linear were updated since last sync',\n });\n\n if (this.config.conflictResolution === 'manual') {\n continue; // Skip, let user resolve manually\n }\n }\n\n // Apply conflict resolution\n const shouldUpdateFromLinear = this.shouldUpdateFromLinear(\n task,\n linearIssue,\n mapping,\n stackMemoryUpdateTime,\n linearUpdateTime\n );\n\n if (shouldUpdateFromLinear) {\n this.updateTaskFromLinearIssue(task, linearIssue);\n\n mapping.lastSyncTimestamp = Date.now();\n mapping.lastLinearUpdate = linearIssue.updatedAt;\n\n result.updated++;\n logger.info(`Updated StackMemory task from Linear: ${task.title}`);\n }\n } catch (error: unknown) {\n result.errors.push(\n `Failed to sync from Linear for task ${taskId}: ${String(error)}`\n );\n logger.error(\n `Failed to sync from Linear for task ${taskId}:`,\n error as Error\n );\n }\n }\n\n return result;\n }\n\n /**\n * Create Linear issue from StackMemory task\n */\n private async createLinearIssueFromTask(\n task: PebblesTask\n ): Promise<LinearIssue> {\n const input: LinearCreateIssueInput = {\n title: task.title,\n description: this.formatDescriptionForLinear(task),\n teamId: this.config.defaultTeamId!,\n priority: this.mapPriorityToLinear(task.priority),\n estimate: task.estimated_effort\n ? Math.ceil(task.estimated_effort / 60)\n : undefined, // Convert minutes to hours\n labelIds: this.mapTagsToLinear(task.tags),\n };\n\n return await this.linearClient.createIssue(input);\n }\n\n /**\n * Update Linear issue from StackMemory task\n */\n private async updateLinearIssueFromTask(\n task: PebblesTask,\n mapping: TaskMapping\n ): Promise<void> {\n const updates: Partial<LinearCreateIssueInput> & { stateId?: string } = {\n title: task.title,\n description: this.formatDescriptionForLinear(task),\n priority: this.mapPriorityToLinear(task.priority),\n estimate: task.estimated_effort\n ? Math.ceil(task.estimated_effort / 60)\n : undefined,\n stateId: await this.mapStatusToLinearState(task.status),\n };\n\n await this.linearClient.updateIssue(mapping.linearId, updates);\n }\n\n /**\n * Update StackMemory task from Linear issue\n */\n private updateTaskFromLinearIssue(\n task: PebblesTask,\n linearIssue: LinearIssue\n ): void {\n // Map Linear state to StackMemory status\n const newStatus = this.mapLinearStateToStatus(linearIssue.state.type);\n\n if (newStatus !== task.status) {\n this.taskStore.updateTaskStatus(\n task.id,\n newStatus,\n 'Updated from Linear'\n );\n }\n\n // Note: Other fields like title, description could be updated here\n // but require careful consideration of conflict resolution\n }\n\n /**\n * Check if task should be updated from Linear based on conflict resolution strategy\n */\n private shouldUpdateFromLinear(\n task: PebblesTask,\n linearIssue: LinearIssue,\n mapping: TaskMapping,\n stackMemoryUpdateTime: number,\n linearUpdateTime: number\n ): boolean {\n switch (this.config.conflictResolution) {\n case 'linear_wins':\n return true;\n case 'stackmemory_wins':\n return false;\n case 'newest_wins':\n return linearUpdateTime > stackMemoryUpdateTime;\n case 'manual':\n return false;\n default:\n return false;\n }\n }\n\n /**\n * Get tasks that haven't been synced to Linear yet\n */\n private getUnsyncedTasks(): PebblesTask[] {\n const activeTasks = this.taskStore.getActiveTasks();\n return activeTasks.filter(\n (task) => !this.mappings.has(task.id) && !task.external_refs?.linear\n );\n }\n\n /**\n * Get tasks that have been modified since last sync\n */\n private getModifiedTasks(): PebblesTask[] {\n const tasks: PebblesTask[] = [];\n\n for (const [taskId, mapping] of this.mappings) {\n const task = this.taskStore.getTask(taskId);\n if (task && task.timestamp * 1000 > mapping.lastSyncTimestamp) {\n tasks.push(task);\n }\n }\n\n return tasks;\n }\n\n /**\n * Update task with Linear reference\n */\n private updateTaskWithLinearRef(\n taskId: string,\n linearIssue: LinearIssue\n ): void {\n const task = this.taskStore.getTask(taskId);\n if (!task) return;\n\n // This would need a method in PebblesTaskStore to update external_refs\n // For now, we'll track this in our mappings\n logger.info(`Task ${taskId} mapped to Linear ${linearIssue.identifier}`);\n }\n\n // Mapping utilities\n\n private formatDescriptionForLinear(task: PebblesTask): string {\n let description = task.description || '';\n\n description += `\\n\\n---\\n**StackMemory Context:**\\n`;\n description += `- Task ID: ${task.id}\\n`;\n description += `- Frame: ${task.frame_id}\\n`;\n description += `- Created: ${new Date(task.created_at * 1000).toISOString()}\\n`;\n\n if (task.tags.length > 0) {\n description += `- Tags: ${task.tags.join(', ')}\\n`;\n }\n\n if (task.depends_on.length > 0) {\n description += `- Dependencies: ${task.depends_on.join(', ')}\\n`;\n }\n\n return description;\n }\n\n private mapPriorityToLinear(priority: TaskPriority): number {\n const map: Record<TaskPriority, number> = {\n low: 1, // Low priority in Linear\n medium: 2, // Medium priority in Linear\n high: 3, // High priority in Linear\n urgent: 4, // Urgent priority in Linear\n };\n return map[priority] || 2;\n }\n\n private mapTagsToLinear(_tags: string[]): string[] | undefined {\n // In a full implementation, this would map StackMemory tags to Linear label IDs\n // For now, return undefined to skip label assignment\n return undefined;\n }\n\n private mapLinearStateToStatus(linearStateType: string): TaskStatus {\n switch (linearStateType) {\n case 'backlog':\n case 'unstarted':\n return 'pending';\n case 'started':\n return 'in_progress';\n case 'completed':\n return 'completed';\n case 'cancelled':\n return 'cancelled';\n default:\n return 'pending';\n }\n }\n\n private async mapStatusToLinearState(\n status: TaskStatus\n ): Promise<string | undefined> {\n // Get available states for the team\n try {\n const team = await this.linearClient.getTeam();\n const states = await this.linearClient.getWorkflowStates(team.id);\n\n // Map StackMemory status to Linear state types\n const targetStateType = this.getLinearStateTypeFromStatus(status);\n\n // Find the first state that matches the target type\n const matchingState = states.find(\n (state) => state.type === targetStateType\n );\n return matchingState?.id;\n } catch (error: unknown) {\n logger.warn(\n 'Failed to map status to Linear state:',\n error instanceof Error ? { error } : undefined\n );\n return undefined;\n }\n }\n\n private getLinearStateTypeFromStatus(status: TaskStatus): string {\n switch (status) {\n case 'pending':\n return 'unstarted';\n case 'in_progress':\n return 'started';\n case 'completed':\n return 'completed';\n case 'cancelled':\n return 'cancelled';\n case 'blocked':\n return 'unstarted'; // Map blocked to unstarted in Linear\n default:\n return 'unstarted';\n }\n }\n\n // Persistence for mappings\n\n private loadMappings(): void {\n this.mappings.clear();\n\n if (existsSync(this.mappingsPath)) {\n try {\n const data = readFileSync(this.mappingsPath, 'utf8');\n const mappingsArray: TaskMapping[] = JSON.parse(data);\n for (const mapping of mappingsArray) {\n this.mappings.set(mapping.stackmemoryId, mapping);\n }\n logger.info(`Loaded ${this.mappings.size} task mappings from disk`);\n } catch (error: unknown) {\n logger.warn('Failed to load mappings, starting fresh');\n }\n }\n }\n\n private saveMappings(): void {\n try {\n const mappingsArray = Array.from(this.mappings.values());\n writeFileSync(this.mappingsPath, JSON.stringify(mappingsArray, null, 2));\n logger.info(`Saved ${this.mappings.size} task mappings to disk`);\n } catch (error: unknown) {\n logger.error('Failed to save mappings:', error as Error);\n }\n }\n\n /**\n * Import all issues from Linear to local task store\n */\n async importFromLinear(): Promise<{\n imported: number;\n skipped: number;\n errors: string[];\n }> {\n const result = { imported: 0, skipped: 0, errors: [] as string[] };\n\n try {\n // Get team info\n if (!this.config.defaultTeamId) {\n const team = await this.linearClient.getTeam();\n this.config.defaultTeamId = team.id;\n logger.info(`Using Linear team: ${team.name} (${team.key})`);\n }\n\n // Fetch all issues from Linear (excluding completed/cancelled)\n const issues = await this.linearClient.getIssues({\n teamId: this.config.defaultTeamId,\n limit: 100,\n });\n\n logger.info(`Found ${issues.length} issues in Linear`);\n\n // Build reverse mapping (linearId -> stackmemoryId)\n const linearIdToTaskId = new Map<string, string>();\n for (const [taskId, mapping] of this.mappings) {\n linearIdToTaskId.set(mapping.linearId, taskId);\n }\n\n for (const issue of issues) {\n try {\n // Skip if already mapped\n if (linearIdToTaskId.has(issue.id)) {\n result.skipped++;\n continue;\n }\n\n // Create local task from Linear issue\n const taskId = await this.createTaskFromLinearIssue(issue);\n\n if (taskId) {\n // Create mapping\n const mapping: TaskMapping = {\n stackmemoryId: taskId,\n linearId: issue.id,\n linearIdentifier: issue.identifier,\n lastSyncTimestamp: Date.now(),\n lastLinearUpdate: issue.updatedAt,\n lastStackMemoryUpdate: Date.now(),\n };\n this.mappings.set(taskId, mapping);\n result.imported++;\n logger.info(`Imported ${issue.identifier}: ${issue.title}`);\n }\n } catch (error: unknown) {\n result.errors.push(\n `Failed to import ${issue.identifier}: ${String(error)}`\n );\n logger.error(`Failed to import ${issue.identifier}:`, error as Error);\n }\n }\n\n this.saveMappings();\n } catch (error: unknown) {\n result.errors.push(`Import failed: ${String(error)}`);\n logger.error('Linear import failed:', error as Error);\n }\n\n return result;\n }\n\n /**\n * Create a local task from a Linear issue\n */\n private async createTaskFromLinearIssue(\n issue: LinearIssue\n ): Promise<string | null> {\n try {\n const priority = this.mapLinearPriorityToLocal(issue.priority);\n\n // Build description with Linear context\n let description = issue.description || '';\n description += `\\n\\n---\\n**Linear:** ${issue.identifier} | ${issue.url}`;\n\n // Extract labels (handle both array and {nodes: [...]} formats)\n const labels = Array.isArray(issue.labels)\n ? issue.labels\n : (issue.labels as unknown as { nodes: Array<{ name: string }> })\n ?.nodes || [];\n const tags = labels.map((l) => l.name);\n if (tags.length === 0) tags.push('linear');\n\n // Create the task via the task store\n const taskId = this.taskStore.createTask({\n title: `[${issue.identifier}] ${issue.title}`,\n description,\n priority,\n frameId: 'linear-import',\n tags,\n estimatedEffort: issue.estimate ? issue.estimate * 60 : undefined,\n });\n\n // Update status if not pending\n const status = this.mapLinearStateToStatus(issue.state.type);\n if (status !== 'pending') {\n this.taskStore.updateTaskStatus(\n taskId,\n status,\n `Imported from Linear as ${status}`\n );\n }\n\n return taskId;\n } catch (error: unknown) {\n logger.error(\n `Failed to create task from Linear issue ${issue.identifier}: ${String(error)}`\n );\n return null;\n }\n }\n\n /**\n * Map Linear priority (0-4) to local TaskPriority\n */\n private mapLinearPriorityToLocal(priority: number): TaskPriority {\n switch (priority) {\n case 1:\n return 'urgent';\n case 2:\n return 'high';\n case 3:\n return 'medium';\n case 4:\n return 'low';\n default:\n return 'medium';\n }\n }\n}\n\n/**\n * Default sync configuration\n */\nexport const DEFAULT_SYNC_CONFIG: SyncConfig = {\n enabled: false,\n direction: 'bidirectional',\n autoSync: true,\n conflictResolution: 'newest_wins',\n syncInterval: 15, // minutes\n maxBatchSize: 10, // max tasks per sync batch\n rateLimitDelay: 500, // 500ms between API calls\n};\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Linear Bi-directional Sync Engine\n * Handles syncing tasks between StackMemory and Linear\n */\n\nimport { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport { logger } from '../../core/monitoring/logger.js';\nimport {\n PebblesTask,\n PebblesTaskStore,\n TaskStatus,\n TaskPriority,\n} from '../../features/tasks/pebbles-task-store.js';\nimport { LinearClient, LinearIssue, LinearCreateIssueInput } from './client.js';\nimport { LinearAuthManager } from './auth.js';\nimport { getEnv, getOptionalEnv } from '../../utils/env.js';\n\n\nexport interface SyncConfig {\n enabled: boolean;\n direction: 'bidirectional' | 'to_linear' | 'from_linear';\n defaultTeamId?: string;\n autoSync: boolean;\n conflictResolution:\n | 'linear_wins'\n | 'stackmemory_wins'\n | 'manual'\n | 'newest_wins';\n syncInterval?: number; // minutes\n maxBatchSize?: number; // max tasks to sync per batch\n rateLimitDelay?: number; // ms delay between API calls\n}\n\nexport interface SyncResult {\n success: boolean;\n synced: {\n toLinear: number;\n fromLinear: number;\n updated: number;\n };\n conflicts: Array<{\n taskId: string;\n linearId: string;\n reason: string;\n }>;\n errors: string[];\n}\n\nexport interface TaskMapping {\n stackmemoryId: string;\n linearId: string;\n linearIdentifier: string;\n lastSyncTimestamp: number;\n lastLinearUpdate: string;\n lastStackMemoryUpdate: number;\n}\n\nexport class LinearSyncEngine {\n private taskStore: PebblesTaskStore;\n private linearClient: LinearClient;\n private authManager: LinearAuthManager;\n private config: SyncConfig;\n private mappings: Map<string, TaskMapping> = new Map();\n private projectRoot: string;\n private mappingsPath: string;\n\n constructor(\n taskStore: PebblesTaskStore,\n authManager: LinearAuthManager,\n config: SyncConfig,\n projectRoot?: string\n ) {\n this.taskStore = taskStore;\n this.authManager = authManager;\n this.config = config;\n this.projectRoot = projectRoot || process.cwd();\n this.mappingsPath = join(\n this.projectRoot,\n '.stackmemory',\n 'linear-mappings.json'\n );\n\n // Check for API key from environment variable first\n const apiKey = process.env['LINEAR_API_KEY'];\n\n if (apiKey) {\n // Use API key from environment\n this.linearClient = new LinearClient({\n apiKey: apiKey,\n });\n } else {\n // Fall back to OAuth tokens\n const tokens = this.authManager.loadTokens();\n if (!tokens) {\n throw new Error(\n 'Linear API key or authentication tokens not found. Set LINEAR_API_KEY environment variable or run \"stackmemory linear setup\" first.'\n );\n }\n\n this.linearClient = new LinearClient({\n apiKey: tokens.accessToken,\n useBearer: true,\n onUnauthorized: async () => {\n const refreshed = await this.authManager.refreshAccessToken();\n return refreshed.accessToken;\n },\n });\n }\n\n this.loadMappings();\n }\n\n /**\n * Update sync configuration\n */\n updateConfig(newConfig: Partial<SyncConfig>): void {\n this.config = { ...this.config, ...newConfig };\n }\n\n /**\n * Perform bi-directional sync\n */\n async sync(): Promise<SyncResult> {\n if (!this.config.enabled) {\n return {\n success: false,\n synced: { toLinear: 0, fromLinear: 0, updated: 0 },\n conflicts: [],\n errors: ['Sync is disabled'],\n };\n }\n\n const result: SyncResult = {\n success: true,\n synced: { toLinear: 0, fromLinear: 0, updated: 0 },\n conflicts: [],\n errors: [],\n };\n\n try {\n // Update client with valid token if not using environment API key\n const apiKey = process.env['LINEAR_API_KEY'];\n if (!apiKey) {\n const token = await this.authManager.getValidToken();\n this.linearClient = new LinearClient({\n apiKey: token,\n useBearer: true,\n onUnauthorized: async () => {\n const refreshed = await this.authManager.refreshAccessToken();\n return refreshed.accessToken;\n },\n });\n }\n\n // Get team info if not configured\n if (!this.config.defaultTeamId) {\n const team = await this.linearClient.getTeam();\n this.config.defaultTeamId = team.id;\n logger.info(`Using Linear team: ${team.name} (${team.key})`);\n }\n\n // Sync in both directions based on configuration\n if (\n this.config.direction === 'bidirectional' ||\n this.config.direction === 'to_linear'\n ) {\n const toLinearResult = await this.syncToLinear();\n result.synced.toLinear = toLinearResult.created;\n result.synced.updated += toLinearResult.updated;\n result.errors.push(...toLinearResult.errors);\n }\n\n if (\n this.config.direction === 'bidirectional' ||\n this.config.direction === 'from_linear'\n ) {\n const fromLinearResult = await this.syncFromLinear();\n result.synced.fromLinear = fromLinearResult.created;\n result.synced.updated += fromLinearResult.updated;\n result.conflicts.push(...fromLinearResult.conflicts);\n result.errors.push(...fromLinearResult.errors);\n }\n\n this.saveMappings();\n } catch (error: unknown) {\n result.success = false;\n result.errors.push(`Sync failed: ${String(error)}`);\n logger.error('Linear sync failed:', error as Error);\n }\n\n return result;\n }\n\n /**\n * Delay helper for rate limiting\n */\n private async delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Sync tasks from StackMemory to Linear\n */\n private async syncToLinear(): Promise<{\n created: number;\n updated: number;\n errors: string[];\n }> {\n const result = { created: 0, updated: 0, errors: [] as string[] };\n const maxBatchSize = this.config.maxBatchSize || 10;\n const rateLimitDelay = this.config.rateLimitDelay || 500;\n\n // Initialize duplicate detector\n const duplicateDetector = new LinearDuplicateDetector(this.linearClient);\n\n // Get unsynced tasks from StackMemory\n const unsyncedTasks = this.getUnsyncedTasks();\n\n // Limit batch size to avoid rate limits\n const tasksToSync = unsyncedTasks.slice(0, maxBatchSize);\n\n if (unsyncedTasks.length > maxBatchSize) {\n logger.info(\n `Syncing ${tasksToSync.length} of ${unsyncedTasks.length} unsynced tasks (batch limit)`\n );\n }\n\n for (const task of tasksToSync) {\n try {\n // Check for duplicates before creating\n const duplicateCheck = await duplicateDetector.checkForDuplicate(\n task.title,\n this.config.defaultTeamId\n );\n\n let linearIssue: LinearIssue;\n \n if (duplicateCheck.isDuplicate && duplicateCheck.existingIssue) {\n // Found duplicate - merge instead of creating\n logger.info(\n `Found existing Linear issue for \"${task.title}\": ${duplicateCheck.existingIssue.identifier} (${Math.round((duplicateCheck.similarity || 0) * 100)}% match)`\n );\n \n // Merge task content into existing issue\n linearIssue = await duplicateDetector.mergeIntoExisting(\n duplicateCheck.existingIssue,\n task.title,\n this.formatDescriptionForLinear(task),\n `StackMemory Task ID: ${task.id}\\nFrame: ${task.frame_id}`\n );\n } else {\n // No duplicate found, create new issue\n linearIssue = await this.createLinearIssueFromTask(task);\n }\n\n // Create mapping\n const mapping: TaskMapping = {\n stackmemoryId: task.id,\n linearId: linearIssue.id,\n linearIdentifier: linearIssue.identifier,\n lastSyncTimestamp: Date.now(),\n lastLinearUpdate: linearIssue.updatedAt,\n lastStackMemoryUpdate: task.timestamp * 1000,\n };\n\n this.mappings.set(task.id, mapping);\n\n // Update task with Linear reference\n this.updateTaskWithLinearRef(task.id, linearIssue);\n\n result.created++;\n logger.info(\n `Synced task to Linear: ${task.title} \u2192 ${linearIssue.identifier}`\n );\n\n // Rate limit delay between creates\n await this.delay(rateLimitDelay);\n } catch (error: unknown) {\n const errorMsg = String(error);\n // Stop syncing on rate limit errors\n if (\n errorMsg.includes('rate limit') ||\n errorMsg.includes('usage limit')\n ) {\n logger.warn('Rate limit hit, stopping sync batch');\n result.errors.push('Rate limit reached - sync paused');\n break;\n }\n result.errors.push(`Failed to sync task ${task.id}: ${errorMsg}`);\n logger.error(\n `Failed to sync task ${task.id} to Linear:`,\n error as Error\n );\n }\n }\n\n // Update existing Linear issues for modified StackMemory tasks\n const modifiedTasks = this.getModifiedTasks();\n\n for (const task of modifiedTasks) {\n try {\n const mapping = this.mappings.get(task.id);\n if (!mapping) continue;\n\n await this.updateLinearIssueFromTask(task, mapping);\n\n mapping.lastSyncTimestamp = Date.now();\n mapping.lastStackMemoryUpdate = task.timestamp * 1000;\n\n result.updated++;\n logger.info(`Updated Linear issue: ${mapping.linearIdentifier}`);\n } catch (error: unknown) {\n result.errors.push(\n `Failed to update Linear issue for task ${task.id}: ${String(error)}`\n );\n logger.error(\n `Failed to update Linear issue for task ${task.id}:`,\n error as Error\n );\n }\n }\n\n return result;\n }\n\n /**\n * Sync tasks from Linear to StackMemory\n */\n private async syncFromLinear(): Promise<{\n created: number;\n updated: number;\n conflicts: Array<{ taskId: string; linearId: string; reason: string }>;\n errors: string[];\n }> {\n const result = {\n created: 0,\n updated: 0,\n conflicts: [] as Array<{\n taskId: string;\n linearId: string;\n reason: string;\n }>,\n errors: [] as string[],\n };\n\n // First, import any new issues from Linear that aren't mapped yet\n const importResult = await this.importFromLinear();\n result.created = importResult.imported;\n result.errors.push(...importResult.errors);\n\n // Then update existing mapped tasks\n for (const [taskId, mapping] of this.mappings) {\n try {\n const linearIssue = await this.linearClient.getIssue(mapping.linearId);\n\n if (!linearIssue) {\n result.errors.push(`Linear issue ${mapping.linearId} not found`);\n continue;\n }\n\n // Check if Linear issue was updated since last sync\n const linearUpdateTime = new Date(linearIssue.updatedAt).getTime();\n if (linearUpdateTime <= mapping.lastSyncTimestamp) {\n continue; // No changes in Linear\n }\n\n // Check for conflicts\n const task = this.taskStore.getTask(taskId);\n if (!task) {\n result.errors.push(`StackMemory task ${taskId} not found`);\n continue;\n }\n\n const stackMemoryUpdateTime = task.timestamp * 1000;\n\n if (\n stackMemoryUpdateTime > mapping.lastSyncTimestamp &&\n linearUpdateTime > mapping.lastSyncTimestamp\n ) {\n // Conflict: both sides updated since last sync\n result.conflicts.push({\n taskId,\n linearId: mapping.linearId,\n reason: 'Both StackMemory and Linear were updated since last sync',\n });\n\n if (this.config.conflictResolution === 'manual') {\n continue; // Skip, let user resolve manually\n }\n }\n\n // Apply conflict resolution\n const shouldUpdateFromLinear = this.shouldUpdateFromLinear(\n task,\n linearIssue,\n mapping,\n stackMemoryUpdateTime,\n linearUpdateTime\n );\n\n if (shouldUpdateFromLinear) {\n this.updateTaskFromLinearIssue(task, linearIssue);\n\n mapping.lastSyncTimestamp = Date.now();\n mapping.lastLinearUpdate = linearIssue.updatedAt;\n\n result.updated++;\n logger.info(`Updated StackMemory task from Linear: ${task.title}`);\n }\n } catch (error: unknown) {\n result.errors.push(\n `Failed to sync from Linear for task ${taskId}: ${String(error)}`\n );\n logger.error(\n `Failed to sync from Linear for task ${taskId}:`,\n error as Error\n );\n }\n }\n\n return result;\n }\n\n /**\n * Create Linear issue from StackMemory task\n */\n private async createLinearIssueFromTask(\n task: PebblesTask\n ): Promise<LinearIssue> {\n const input: LinearCreateIssueInput = {\n title: task.title,\n description: this.formatDescriptionForLinear(task),\n teamId: this.config.defaultTeamId!,\n priority: this.mapPriorityToLinear(task.priority),\n estimate: task.estimated_effort\n ? Math.ceil(task.estimated_effort / 60)\n : undefined, // Convert minutes to hours\n labelIds: this.mapTagsToLinear(task.tags),\n };\n\n return await this.linearClient.createIssue(input);\n }\n\n /**\n * Update Linear issue from StackMemory task\n */\n private async updateLinearIssueFromTask(\n task: PebblesTask,\n mapping: TaskMapping\n ): Promise<void> {\n const updates: Partial<LinearCreateIssueInput> & { stateId?: string } = {\n title: task.title,\n description: this.formatDescriptionForLinear(task),\n priority: this.mapPriorityToLinear(task.priority),\n estimate: task.estimated_effort\n ? Math.ceil(task.estimated_effort / 60)\n : undefined,\n stateId: await this.mapStatusToLinearState(task.status),\n };\n\n await this.linearClient.updateIssue(mapping.linearId, updates);\n }\n\n /**\n * Update StackMemory task from Linear issue\n */\n private updateTaskFromLinearIssue(\n task: PebblesTask,\n linearIssue: LinearIssue\n ): void {\n // Map Linear state to StackMemory status\n const newStatus = this.mapLinearStateToStatus(linearIssue.state.type);\n\n if (newStatus !== task.status) {\n this.taskStore.updateTaskStatus(\n task.id,\n newStatus,\n 'Updated from Linear'\n );\n }\n\n // Note: Other fields like title, description could be updated here\n // but require careful consideration of conflict resolution\n }\n\n /**\n * Check if task should be updated from Linear based on conflict resolution strategy\n */\n private shouldUpdateFromLinear(\n task: PebblesTask,\n linearIssue: LinearIssue,\n mapping: TaskMapping,\n stackMemoryUpdateTime: number,\n linearUpdateTime: number\n ): boolean {\n switch (this.config.conflictResolution) {\n case 'linear_wins':\n return true;\n case 'stackmemory_wins':\n return false;\n case 'newest_wins':\n return linearUpdateTime > stackMemoryUpdateTime;\n case 'manual':\n return false;\n default:\n return false;\n }\n }\n\n /**\n * Get tasks that haven't been synced to Linear yet\n */\n private getUnsyncedTasks(): PebblesTask[] {\n const activeTasks = this.taskStore.getActiveTasks();\n return activeTasks.filter(\n (task) => !this.mappings.has(task.id) && !task.external_refs?.linear\n );\n }\n\n /**\n * Get tasks that have been modified since last sync\n */\n private getModifiedTasks(): PebblesTask[] {\n const tasks: PebblesTask[] = [];\n\n for (const [taskId, mapping] of this.mappings) {\n const task = this.taskStore.getTask(taskId);\n if (task && task.timestamp * 1000 > mapping.lastSyncTimestamp) {\n tasks.push(task);\n }\n }\n\n return tasks;\n }\n\n /**\n * Update task with Linear reference\n */\n private updateTaskWithLinearRef(\n taskId: string,\n linearIssue: LinearIssue\n ): void {\n const task = this.taskStore.getTask(taskId);\n if (!task) return;\n\n // This would need a method in PebblesTaskStore to update external_refs\n // For now, we'll track this in our mappings\n logger.info(`Task ${taskId} mapped to Linear ${linearIssue.identifier}`);\n }\n\n // Mapping utilities\n\n private formatDescriptionForLinear(task: PebblesTask): string {\n let description = task.description || '';\n\n description += `\\n\\n---\\n**StackMemory Context:**\\n`;\n description += `- Task ID: ${task.id}\\n`;\n description += `- Frame: ${task.frame_id}\\n`;\n description += `- Created: ${new Date(task.created_at * 1000).toISOString()}\\n`;\n\n if (task.tags.length > 0) {\n description += `- Tags: ${task.tags.join(', ')}\\n`;\n }\n\n if (task.depends_on.length > 0) {\n description += `- Dependencies: ${task.depends_on.join(', ')}\\n`;\n }\n\n return description;\n }\n\n private mapPriorityToLinear(priority: TaskPriority): number {\n const map: Record<TaskPriority, number> = {\n low: 1, // Low priority in Linear\n medium: 2, // Medium priority in Linear\n high: 3, // High priority in Linear\n urgent: 4, // Urgent priority in Linear\n };\n return map[priority] || 2;\n }\n\n private mapTagsToLinear(_tags: string[]): string[] | undefined {\n // In a full implementation, this would map StackMemory tags to Linear label IDs\n // For now, return undefined to skip label assignment\n return undefined;\n }\n\n private mapLinearStateToStatus(linearStateType: string): TaskStatus {\n switch (linearStateType) {\n case 'backlog':\n case 'unstarted':\n return 'pending';\n case 'started':\n return 'in_progress';\n case 'completed':\n return 'completed';\n case 'cancelled':\n return 'cancelled';\n default:\n return 'pending';\n }\n }\n\n private async mapStatusToLinearState(\n status: TaskStatus\n ): Promise<string | undefined> {\n // Get available states for the team\n try {\n const team = await this.linearClient.getTeam();\n const states = await this.linearClient.getWorkflowStates(team.id);\n\n // Map StackMemory status to Linear state types\n const targetStateType = this.getLinearStateTypeFromStatus(status);\n\n // Find the first state that matches the target type\n const matchingState = states.find(\n (state) => state.type === targetStateType\n );\n return matchingState?.id;\n } catch (error: unknown) {\n logger.warn(\n 'Failed to map status to Linear state:',\n error instanceof Error ? { error } : undefined\n );\n return undefined;\n }\n }\n\n private getLinearStateTypeFromStatus(status: TaskStatus): string {\n switch (status) {\n case 'pending':\n return 'unstarted';\n case 'in_progress':\n return 'started';\n case 'completed':\n return 'completed';\n case 'cancelled':\n return 'cancelled';\n case 'blocked':\n return 'unstarted'; // Map blocked to unstarted in Linear\n default:\n return 'unstarted';\n }\n }\n\n // Persistence for mappings\n\n private loadMappings(): void {\n this.mappings.clear();\n\n if (existsSync(this.mappingsPath)) {\n try {\n const data = readFileSync(this.mappingsPath, 'utf8');\n const mappingsArray: TaskMapping[] = JSON.parse(data);\n for (const mapping of mappingsArray) {\n this.mappings.set(mapping.stackmemoryId, mapping);\n }\n logger.info(`Loaded ${this.mappings.size} task mappings from disk`);\n } catch (error: unknown) {\n logger.warn('Failed to load mappings, starting fresh');\n }\n }\n }\n\n private saveMappings(): void {\n try {\n const mappingsArray = Array.from(this.mappings.values());\n writeFileSync(this.mappingsPath, JSON.stringify(mappingsArray, null, 2));\n logger.info(`Saved ${this.mappings.size} task mappings to disk`);\n } catch (error: unknown) {\n logger.error('Failed to save mappings:', error as Error);\n }\n }\n\n /**\n * Import all issues from Linear to local task store\n */\n async importFromLinear(): Promise<{\n imported: number;\n skipped: number;\n errors: string[];\n }> {\n const result = { imported: 0, skipped: 0, errors: [] as string[] };\n\n try {\n // Get team info\n if (!this.config.defaultTeamId) {\n const team = await this.linearClient.getTeam();\n this.config.defaultTeamId = team.id;\n logger.info(`Using Linear team: ${team.name} (${team.key})`);\n }\n\n // Fetch all issues from Linear (excluding completed/cancelled)\n const issues = await this.linearClient.getIssues({\n teamId: this.config.defaultTeamId,\n limit: 100,\n });\n\n logger.info(`Found ${issues.length} issues in Linear`);\n\n // Build reverse mapping (linearId -> stackmemoryId)\n const linearIdToTaskId = new Map<string, string>();\n for (const [taskId, mapping] of this.mappings) {\n linearIdToTaskId.set(mapping.linearId, taskId);\n }\n\n for (const issue of issues) {\n try {\n // Skip if already mapped\n if (linearIdToTaskId.has(issue.id)) {\n result.skipped++;\n continue;\n }\n\n // Create local task from Linear issue\n const taskId = await this.createTaskFromLinearIssue(issue);\n\n if (taskId) {\n // Create mapping\n const mapping: TaskMapping = {\n stackmemoryId: taskId,\n linearId: issue.id,\n linearIdentifier: issue.identifier,\n lastSyncTimestamp: Date.now(),\n lastLinearUpdate: issue.updatedAt,\n lastStackMemoryUpdate: Date.now(),\n };\n this.mappings.set(taskId, mapping);\n result.imported++;\n logger.info(`Imported ${issue.identifier}: ${issue.title}`);\n }\n } catch (error: unknown) {\n result.errors.push(\n `Failed to import ${issue.identifier}: ${String(error)}`\n );\n logger.error(`Failed to import ${issue.identifier}:`, error as Error);\n }\n }\n\n this.saveMappings();\n } catch (error: unknown) {\n result.errors.push(`Import failed: ${String(error)}`);\n logger.error('Linear import failed:', error as Error);\n }\n\n return result;\n }\n\n /**\n * Create a local task from a Linear issue\n */\n private async createTaskFromLinearIssue(\n issue: LinearIssue\n ): Promise<string | null> {\n try {\n const priority = this.mapLinearPriorityToLocal(issue.priority);\n\n // Build description with Linear context\n let description = issue.description || '';\n description += `\\n\\n---\\n**Linear:** ${issue.identifier} | ${issue.url}`;\n\n // Extract labels (handle both array and {nodes: [...]} formats)\n const labels = Array.isArray(issue.labels)\n ? issue.labels\n : (issue.labels as unknown as { nodes: Array<{ name: string }> })\n ?.nodes || [];\n const tags = labels.map((l) => l.name);\n if (tags.length === 0) tags.push('linear');\n\n // Create the task via the task store\n const taskId = this.taskStore.createTask({\n title: `[${issue.identifier}] ${issue.title}`,\n description,\n priority,\n frameId: 'linear-import',\n tags,\n estimatedEffort: issue.estimate ? issue.estimate * 60 : undefined,\n });\n\n // Update status if not pending\n const status = this.mapLinearStateToStatus(issue.state.type);\n if (status !== 'pending') {\n this.taskStore.updateTaskStatus(\n taskId,\n status,\n `Imported from Linear as ${status}`\n );\n }\n\n return taskId;\n } catch (error: unknown) {\n logger.error(\n `Failed to create task from Linear issue ${issue.identifier}: ${String(error)}`\n );\n return null;\n }\n }\n\n /**\n * Map Linear priority (0-4) to local TaskPriority\n */\n private mapLinearPriorityToLocal(priority: number): TaskPriority {\n switch (priority) {\n case 1:\n return 'urgent';\n case 2:\n return 'high';\n case 3:\n return 'medium';\n case 4:\n return 'low';\n default:\n return 'medium';\n }\n }\n}\n\n/**\n * Default sync configuration\n */\nexport const DEFAULT_SYNC_CONFIG: SyncConfig = {\n enabled: false,\n direction: 'bidirectional',\n autoSync: true,\n conflictResolution: 'newest_wins',\n syncInterval: 15, // minutes\n maxBatchSize: 10, // max tasks per sync batch\n rateLimitDelay: 500, // 500ms between API calls\n};\n\n/**\n * Duplicate Detection for Linear Issues\n */\nexport interface DuplicateCheckResult {\n isDuplicate: boolean;\n existingIssue?: LinearIssue;\n similarity?: number;\n}\n\nexport class LinearDuplicateDetector {\n private linearClient: LinearClient;\n private titleCache: Map<string, LinearIssue[]> = new Map();\n private cacheExpiry: number = 5 * 60 * 1000; // 5 minutes\n private lastCacheRefresh: number = 0;\n\n constructor(linearClient: LinearClient) {\n this.linearClient = linearClient;\n }\n\n /**\n * Search for existing Linear issues with similar titles\n */\n async searchByTitle(title: string, teamId?: string): Promise<LinearIssue[]> {\n const normalizedTitle = this.normalizeTitle(title);\n \n // Check cache first\n if (this.isCacheValid()) {\n const cached = this.titleCache.get(normalizedTitle);\n if (cached) return cached;\n }\n\n try {\n // Get all issues from the team (Linear API limit is 250)\n const allIssues = await this.linearClient.getIssues({\n teamId,\n limit: 100, // Use smaller limit to avoid API errors\n });\n\n // Filter for matching titles (exact and fuzzy)\n const matchingIssues = allIssues.filter(issue => {\n const issueNormalized = this.normalizeTitle(issue.title);\n \n // Exact match\n if (issueNormalized === normalizedTitle) return true;\n \n // Fuzzy match - check if titles are very similar\n const similarity = this.calculateSimilarity(normalizedTitle, issueNormalized);\n return similarity > 0.85; // 85% similarity threshold\n });\n\n // Update cache\n this.titleCache.set(normalizedTitle, matchingIssues);\n this.lastCacheRefresh = Date.now();\n\n return matchingIssues;\n } catch (error) {\n logger.error('Failed to search Linear issues by title:', error as Error);\n return [];\n }\n }\n\n /**\n * Check if a task title would create a duplicate in Linear\n */\n async checkForDuplicate(\n title: string,\n teamId?: string\n ): Promise<DuplicateCheckResult> {\n const existingIssues = await this.searchByTitle(title, teamId);\n \n if (existingIssues.length === 0) {\n return { isDuplicate: false };\n }\n\n // Find the best match\n let bestMatch: LinearIssue | undefined;\n let bestSimilarity = 0;\n\n for (const issue of existingIssues) {\n const similarity = this.calculateSimilarity(\n this.normalizeTitle(title),\n this.normalizeTitle(issue.title)\n );\n \n if (similarity > bestSimilarity) {\n bestSimilarity = similarity;\n bestMatch = issue;\n }\n }\n\n return {\n isDuplicate: true,\n existingIssue: bestMatch,\n similarity: bestSimilarity,\n };\n }\n\n /**\n * Merge task content into existing Linear issue\n */\n async mergeIntoExisting(\n existingIssue: LinearIssue,\n newTitle: string,\n newDescription?: string,\n additionalContext?: string\n ): Promise<LinearIssue> {\n try {\n // Build merged description\n let mergedDescription = existingIssue.description || '';\n \n if (newDescription && !mergedDescription.includes(newDescription)) {\n mergedDescription += `\\n\\n## Additional Context (${new Date().toISOString()})\\n`;\n mergedDescription += newDescription;\n }\n\n if (additionalContext) {\n mergedDescription += `\\n\\n---\\n${additionalContext}`;\n }\n\n // Update the existing issue\n const updateQuery = `\n mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {\n issueUpdate(id: $id, input: $input) {\n issue {\n id\n identifier\n title\n description\n updatedAt\n }\n }\n }\n `;\n\n const variables = {\n id: existingIssue.id,\n input: {\n description: mergedDescription,\n },\n };\n\n const response = await this.linearClient.graphql(updateQuery, variables);\n const updatedIssue = response.issueUpdate?.issue;\n\n if (updatedIssue) {\n logger.info(\n `Merged content into existing Linear issue ${existingIssue.identifier}: ${existingIssue.title}`\n );\n return updatedIssue;\n }\n\n return existingIssue;\n } catch (error) {\n logger.error('Failed to merge into existing Linear issue:', error as Error);\n return existingIssue;\n }\n }\n\n /**\n * Normalize title for comparison\n */\n private normalizeTitle(title: string): string {\n return title\n .toLowerCase()\n .trim()\n .replace(/\\s+/g, ' ') // Normalize whitespace\n .replace(/[^\\w\\s-]/g, '') // Remove special characters except hyphens\n .replace(/^(sta|eng|bug|feat|task|tsk)[-\\s]\\d+[-\\s:]*/, '') // Remove issue prefixes\n .trim();\n }\n\n /**\n * Calculate similarity between two strings (Levenshtein distance based)\n */\n private calculateSimilarity(str1: string, str2: string): number {\n if (str1 === str2) return 1;\n if (str1.length === 0 || str2.length === 0) return 0;\n\n // Use Levenshtein distance for similarity calculation\n const distance = this.levenshteinDistance(str1, str2);\n const maxLength = Math.max(str1.length, str2.length);\n \n return 1 - (distance / maxLength);\n }\n\n /**\n * Calculate Levenshtein distance between two strings\n */\n private levenshteinDistance(str1: string, str2: string): number {\n const m = str1.length;\n const n = str2.length;\n const dp: number[][] = Array(m + 1)\n .fill(null)\n .map(() => Array(n + 1).fill(0));\n\n for (let i = 0; i <= m; i++) dp[i][0] = i;\n for (let j = 0; j <= n; j++) dp[0][j] = j;\n\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n if (str1[i - 1] === str2[j - 1]) {\n dp[i][j] = dp[i - 1][j - 1];\n } else {\n dp[i][j] = 1 + Math.min(\n dp[i - 1][j], // deletion\n dp[i][j - 1], // insertion\n dp[i - 1][j - 1] // substitution\n );\n }\n }\n }\n\n return dp[m][n];\n }\n\n /**\n * Check if cache is still valid\n */\n private isCacheValid(): boolean {\n return Date.now() - this.lastCacheRefresh < this.cacheExpiry;\n }\n\n /**\n * Clear the title cache\n */\n clearCache(): void {\n this.titleCache.clear();\n this.lastCacheRefresh = 0;\n }\n}\n"],
|
|
5
|
+
"mappings": "AAKA,SAAS,cAAc,eAAe,kBAAkB;AACxD,SAAS,YAAY;AACrB,SAAS,cAAc;AAOvB,SAAS,oBAAyD;AA4C3D,MAAM,iBAAiB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAqC,oBAAI,IAAI;AAAA,EAC7C;AAAA,EACA;AAAA,EAER,YACE,WACA,aACA,QACA,aACA;AACA,SAAK,YAAY;AACjB,SAAK,cAAc;AACnB,SAAK,SAAS;AACd,SAAK,cAAc,eAAe,QAAQ,IAAI;AAC9C,SAAK,eAAe;AAAA,MAClB,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAGA,UAAM,SAAS,QAAQ,IAAI,gBAAgB;AAE3C,QAAI,QAAQ;AAEV,WAAK,eAAe,IAAI,aAAa;AAAA,QACnC;AAAA,MACF,CAAC;AAAA,IACH,OAAO;AAEL,YAAM,SAAS,KAAK,YAAY,WAAW;AAC3C,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,WAAK,eAAe,IAAI,aAAa;AAAA,QACnC,QAAQ,OAAO;AAAA,QACf,WAAW;AAAA,QACX,gBAAgB,YAAY;AAC1B,gBAAM,YAAY,MAAM,KAAK,YAAY,mBAAmB;AAC5D,iBAAO,UAAU;AAAA,QACnB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,WAAsC;AACjD,SAAK,SAAS,EAAE,GAAG,KAAK,QAAQ,GAAG,UAAU;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAA4B;AAChC,QAAI,CAAC,KAAK,OAAO,SAAS;AACxB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,EAAE,UAAU,GAAG,YAAY,GAAG,SAAS,EAAE;AAAA,QACjD,WAAW,CAAC;AAAA,QACZ,QAAQ,CAAC,kBAAkB;AAAA,MAC7B;AAAA,IACF;AAEA,UAAM,SAAqB;AAAA,MACzB,SAAS;AAAA,MACT,QAAQ,EAAE,UAAU,GAAG,YAAY,GAAG,SAAS,EAAE;AAAA,MACjD,WAAW,CAAC;AAAA,MACZ,QAAQ,CAAC;AAAA,IACX;AAEA,QAAI;AAEF,YAAM,SAAS,QAAQ,IAAI,gBAAgB;AAC3C,UAAI,CAAC,QAAQ;AACX,cAAM,QAAQ,MAAM,KAAK,YAAY,cAAc;AACnD,aAAK,eAAe,IAAI,aAAa;AAAA,UACnC,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,gBAAgB,YAAY;AAC1B,kBAAM,YAAY,MAAM,KAAK,YAAY,mBAAmB;AAC5D,mBAAO,UAAU;AAAA,UACnB;AAAA,QACF,CAAC;AAAA,MACH;AAGA,UAAI,CAAC,KAAK,OAAO,eAAe;AAC9B,cAAM,OAAO,MAAM,KAAK,aAAa,QAAQ;AAC7C,aAAK,OAAO,gBAAgB,KAAK;AACjC,eAAO,KAAK,sBAAsB,KAAK,IAAI,KAAK,KAAK,GAAG,GAAG;AAAA,MAC7D;AAGA,UACE,KAAK,OAAO,cAAc,mBAC1B,KAAK,OAAO,cAAc,aAC1B;AACA,cAAM,iBAAiB,MAAM,KAAK,aAAa;AAC/C,eAAO,OAAO,WAAW,eAAe;AACxC,eAAO,OAAO,WAAW,eAAe;AACxC,eAAO,OAAO,KAAK,GAAG,eAAe,MAAM;AAAA,MAC7C;AAEA,UACE,KAAK,OAAO,cAAc,mBAC1B,KAAK,OAAO,cAAc,eAC1B;AACA,cAAM,mBAAmB,MAAM,KAAK,eAAe;AACnD,eAAO,OAAO,aAAa,iBAAiB;AAC5C,eAAO,OAAO,WAAW,iBAAiB;AAC1C,eAAO,UAAU,KAAK,GAAG,iBAAiB,SAAS;AACnD,eAAO,OAAO,KAAK,GAAG,iBAAiB,MAAM;AAAA,MAC/C;AAEA,WAAK,aAAa;AAAA,IACpB,SAAS,OAAgB;AACvB,aAAO,UAAU;AACjB,aAAO,OAAO,KAAK,gBAAgB,OAAO,KAAK,CAAC,EAAE;AAClD,aAAO,MAAM,uBAAuB,KAAc;AAAA,IACpD;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,MAAM,IAA2B;AAC7C,WAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,eAIX;AACD,UAAM,SAAS,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC,EAAc;AAChE,UAAM,eAAe,KAAK,OAAO,gBAAgB;AACjD,UAAM,iBAAiB,KAAK,OAAO,kBAAkB;AAGrD,UAAM,oBAAoB,IAAI,wBAAwB,KAAK,YAAY;AAGvE,UAAM,gBAAgB,KAAK,iBAAiB;AAG5C,UAAM,cAAc,cAAc,MAAM,GAAG,YAAY;AAEvD,QAAI,cAAc,SAAS,cAAc;AACvC,aAAO;AAAA,QACL,WAAW,YAAY,MAAM,OAAO,cAAc,MAAM;AAAA,MAC1D;AAAA,IACF;AAEA,eAAW,QAAQ,aAAa;AAC9B,UAAI;AAEF,cAAM,iBAAiB,MAAM,kBAAkB;AAAA,UAC7C,KAAK;AAAA,UACL,KAAK,OAAO;AAAA,QACd;AAEA,YAAI;AAEJ,YAAI,eAAe,eAAe,eAAe,eAAe;AAE9D,iBAAO;AAAA,YACL,oCAAoC,KAAK,KAAK,MAAM,eAAe,cAAc,UAAU,KAAK,KAAK,OAAO,eAAe,cAAc,KAAK,GAAG,CAAC;AAAA,UACpJ;AAGA,wBAAc,MAAM,kBAAkB;AAAA,YACpC,eAAe;AAAA,YACf,KAAK;AAAA,YACL,KAAK,2BAA2B,IAAI;AAAA,YACpC,wBAAwB,KAAK,EAAE;AAAA,SAAY,KAAK,QAAQ;AAAA,UAC1D;AAAA,QACF,OAAO;AAEL,wBAAc,MAAM,KAAK,0BAA0B,IAAI;AAAA,QACzD;AAGA,cAAM,UAAuB;AAAA,UAC3B,eAAe,KAAK;AAAA,UACpB,UAAU,YAAY;AAAA,UACtB,kBAAkB,YAAY;AAAA,UAC9B,mBAAmB,KAAK,IAAI;AAAA,UAC5B,kBAAkB,YAAY;AAAA,UAC9B,uBAAuB,KAAK,YAAY;AAAA,QAC1C;AAEA,aAAK,SAAS,IAAI,KAAK,IAAI,OAAO;AAGlC,aAAK,wBAAwB,KAAK,IAAI,WAAW;AAEjD,eAAO;AACP,eAAO;AAAA,UACL,0BAA0B,KAAK,KAAK,WAAM,YAAY,UAAU;AAAA,QAClE;AAGA,cAAM,KAAK,MAAM,cAAc;AAAA,MACjC,SAAS,OAAgB;AACvB,cAAM,WAAW,OAAO,KAAK;AAE7B,YACE,SAAS,SAAS,YAAY,KAC9B,SAAS,SAAS,aAAa,GAC/B;AACA,iBAAO,KAAK,qCAAqC;AACjD,iBAAO,OAAO,KAAK,kCAAkC;AACrD;AAAA,QACF;AACA,eAAO,OAAO,KAAK,uBAAuB,KAAK,EAAE,KAAK,QAAQ,EAAE;AAChE,eAAO;AAAA,UACL,uBAAuB,KAAK,EAAE;AAAA,UAC9B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,UAAM,gBAAgB,KAAK,iBAAiB;AAE5C,eAAW,QAAQ,eAAe;AAChC,UAAI;AACF,cAAM,UAAU,KAAK,SAAS,IAAI,KAAK,EAAE;AACzC,YAAI,CAAC,QAAS;AAEd,cAAM,KAAK,0BAA0B,MAAM,OAAO;AAElD,gBAAQ,oBAAoB,KAAK,IAAI;AACrC,gBAAQ,wBAAwB,KAAK,YAAY;AAEjD,eAAO;AACP,eAAO,KAAK,yBAAyB,QAAQ,gBAAgB,EAAE;AAAA,MACjE,SAAS,OAAgB;AACvB,eAAO,OAAO;AAAA,UACZ,0CAA0C,KAAK,EAAE,KAAK,OAAO,KAAK,CAAC;AAAA,QACrE;AACA,eAAO;AAAA,UACL,0CAA0C,KAAK,EAAE;AAAA,UACjD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,iBAKX;AACD,UAAM,SAAS;AAAA,MACb,SAAS;AAAA,MACT,SAAS;AAAA,MACT,WAAW,CAAC;AAAA,MAKZ,QAAQ,CAAC;AAAA,IACX;AAGA,UAAM,eAAe,MAAM,KAAK,iBAAiB;AACjD,WAAO,UAAU,aAAa;AAC9B,WAAO,OAAO,KAAK,GAAG,aAAa,MAAM;AAGzC,eAAW,CAAC,QAAQ,OAAO,KAAK,KAAK,UAAU;AAC7C,UAAI;AACF,cAAM,cAAc,MAAM,KAAK,aAAa,SAAS,QAAQ,QAAQ;AAErE,YAAI,CAAC,aAAa;AAChB,iBAAO,OAAO,KAAK,gBAAgB,QAAQ,QAAQ,YAAY;AAC/D;AAAA,QACF;AAGA,cAAM,mBAAmB,IAAI,KAAK,YAAY,SAAS,EAAE,QAAQ;AACjE,YAAI,oBAAoB,QAAQ,mBAAmB;AACjD;AAAA,QACF;AAGA,cAAM,OAAO,KAAK,UAAU,QAAQ,MAAM;AAC1C,YAAI,CAAC,MAAM;AACT,iBAAO,OAAO,KAAK,oBAAoB,MAAM,YAAY;AACzD;AAAA,QACF;AAEA,cAAM,wBAAwB,KAAK,YAAY;AAE/C,YACE,wBAAwB,QAAQ,qBAChC,mBAAmB,QAAQ,mBAC3B;AAEA,iBAAO,UAAU,KAAK;AAAA,YACpB;AAAA,YACA,UAAU,QAAQ;AAAA,YAClB,QAAQ;AAAA,UACV,CAAC;AAED,cAAI,KAAK,OAAO,uBAAuB,UAAU;AAC/C;AAAA,UACF;AAAA,QACF;AAGA,cAAM,yBAAyB,KAAK;AAAA,UAClC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAEA,YAAI,wBAAwB;AAC1B,eAAK,0BAA0B,MAAM,WAAW;AAEhD,kBAAQ,oBAAoB,KAAK,IAAI;AACrC,kBAAQ,mBAAmB,YAAY;AAEvC,iBAAO;AACP,iBAAO,KAAK,yCAAyC,KAAK,KAAK,EAAE;AAAA,QACnE;AAAA,MACF,SAAS,OAAgB;AACvB,eAAO,OAAO;AAAA,UACZ,uCAAuC,MAAM,KAAK,OAAO,KAAK,CAAC;AAAA,QACjE;AACA,eAAO;AAAA,UACL,uCAAuC,MAAM;AAAA,UAC7C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,0BACZ,MACsB;AACtB,UAAM,QAAgC;AAAA,MACpC,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK,2BAA2B,IAAI;AAAA,MACjD,QAAQ,KAAK,OAAO;AAAA,MACpB,UAAU,KAAK,oBAAoB,KAAK,QAAQ;AAAA,MAChD,UAAU,KAAK,mBACX,KAAK,KAAK,KAAK,mBAAmB,EAAE,IACpC;AAAA;AAAA,MACJ,UAAU,KAAK,gBAAgB,KAAK,IAAI;AAAA,IAC1C;AAEA,WAAO,MAAM,KAAK,aAAa,YAAY,KAAK;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,0BACZ,MACA,SACe;AACf,UAAM,UAAkE;AAAA,MACtE,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK,2BAA2B,IAAI;AAAA,MACjD,UAAU,KAAK,oBAAoB,KAAK,QAAQ;AAAA,MAChD,UAAU,KAAK,mBACX,KAAK,KAAK,KAAK,mBAAmB,EAAE,IACpC;AAAA,MACJ,SAAS,MAAM,KAAK,uBAAuB,KAAK,MAAM;AAAA,IACxD;AAEA,UAAM,KAAK,aAAa,YAAY,QAAQ,UAAU,OAAO;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKQ,0BACN,MACA,aACM;AAEN,UAAM,YAAY,KAAK,uBAAuB,YAAY,MAAM,IAAI;AAEpE,QAAI,cAAc,KAAK,QAAQ;AAC7B,WAAK,UAAU;AAAA,QACb,KAAK;AAAA,QACL;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EAIF;AAAA;AAAA;AAAA;AAAA,EAKQ,uBACN,MACA,aACA,SACA,uBACA,kBACS;AACT,YAAQ,KAAK,OAAO,oBAAoB;AAAA,MACtC,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO,mBAAmB;AAAA,MAC5B,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAkC;AACxC,UAAM,cAAc,KAAK,UAAU,eAAe;AAClD,WAAO,YAAY;AAAA,MACjB,CAAC,SAAS,CAAC,KAAK,SAAS,IAAI,KAAK,EAAE,KAAK,CAAC,KAAK,eAAe;AAAA,IAChE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAkC;AACxC,UAAM,QAAuB,CAAC;AAE9B,eAAW,CAAC,QAAQ,OAAO,KAAK,KAAK,UAAU;AAC7C,YAAM,OAAO,KAAK,UAAU,QAAQ,MAAM;AAC1C,UAAI,QAAQ,KAAK,YAAY,MAAO,QAAQ,mBAAmB;AAC7D,cAAM,KAAK,IAAI;AAAA,MACjB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,wBACN,QACA,aACM;AACN,UAAM,OAAO,KAAK,UAAU,QAAQ,MAAM;AAC1C,QAAI,CAAC,KAAM;AAIX,WAAO,KAAK,QAAQ,MAAM,qBAAqB,YAAY,UAAU,EAAE;AAAA,EACzE;AAAA;AAAA,EAIQ,2BAA2B,MAA2B;AAC5D,QAAI,cAAc,KAAK,eAAe;AAEtC,mBAAe;AAAA;AAAA;AAAA;AAAA;AACf,mBAAe,cAAc,KAAK,EAAE;AAAA;AACpC,mBAAe,YAAY,KAAK,QAAQ;AAAA;AACxC,mBAAe,cAAc,IAAI,KAAK,KAAK,aAAa,GAAI,EAAE,YAAY,CAAC;AAAA;AAE3E,QAAI,KAAK,KAAK,SAAS,GAAG;AACxB,qBAAe,WAAW,KAAK,KAAK,KAAK,IAAI,CAAC;AAAA;AAAA,IAChD;AAEA,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,qBAAe,mBAAmB,KAAK,WAAW,KAAK,IAAI,CAAC;AAAA;AAAA,IAC9D;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,oBAAoB,UAAgC;AAC1D,UAAM,MAAoC;AAAA,MACxC,KAAK;AAAA;AAAA,MACL,QAAQ;AAAA;AAAA,MACR,MAAM;AAAA;AAAA,MACN,QAAQ;AAAA;AAAA,IACV;AACA,WAAO,IAAI,QAAQ,KAAK;AAAA,EAC1B;AAAA,EAEQ,gBAAgB,OAAuC;AAG7D,WAAO;AAAA,EACT;AAAA,EAEQ,uBAAuB,iBAAqC;AAClE,YAAQ,iBAAiB;AAAA,MACvB,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,EAEA,MAAc,uBACZ,QAC6B;AAE7B,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,aAAa,QAAQ;AAC7C,YAAM,SAAS,MAAM,KAAK,aAAa,kBAAkB,KAAK,EAAE;AAGhE,YAAM,kBAAkB,KAAK,6BAA6B,MAAM;AAGhE,YAAM,gBAAgB,OAAO;AAAA,QAC3B,CAAC,UAAU,MAAM,SAAS;AAAA,MAC5B;AACA,aAAO,eAAe;AAAA,IACxB,SAAS,OAAgB;AACvB,aAAO;AAAA,QACL;AAAA,QACA,iBAAiB,QAAQ,EAAE,MAAM,IAAI;AAAA,MACvC;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,6BAA6B,QAA4B;AAC/D,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA;AAAA,EAIQ,eAAqB;AAC3B,SAAK,SAAS,MAAM;AAEpB,QAAI,WAAW,KAAK,YAAY,GAAG;AACjC,UAAI;AACF,cAAM,OAAO,aAAa,KAAK,cAAc,MAAM;AACnD,cAAM,gBAA+B,KAAK,MAAM,IAAI;AACpD,mBAAW,WAAW,eAAe;AACnC,eAAK,SAAS,IAAI,QAAQ,eAAe,OAAO;AAAA,QAClD;AACA,eAAO,KAAK,UAAU,KAAK,SAAS,IAAI,0BAA0B;AAAA,MACpE,SAAS,OAAgB;AACvB,eAAO,KAAK,yCAAyC;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,eAAqB;AAC3B,QAAI;AACF,YAAM,gBAAgB,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC;AACvD,oBAAc,KAAK,cAAc,KAAK,UAAU,eAAe,MAAM,CAAC,CAAC;AACvE,aAAO,KAAK,SAAS,KAAK,SAAS,IAAI,wBAAwB;AAAA,IACjE,SAAS,OAAgB;AACvB,aAAO,MAAM,4BAA4B,KAAc;AAAA,IACzD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBAIH;AACD,UAAM,SAAS,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC,EAAc;AAEjE,QAAI;AAEF,UAAI,CAAC,KAAK,OAAO,eAAe;AAC9B,cAAM,OAAO,MAAM,KAAK,aAAa,QAAQ;AAC7C,aAAK,OAAO,gBAAgB,KAAK;AACjC,eAAO,KAAK,sBAAsB,KAAK,IAAI,KAAK,KAAK,GAAG,GAAG;AAAA,MAC7D;AAGA,YAAM,SAAS,MAAM,KAAK,aAAa,UAAU;AAAA,QAC/C,QAAQ,KAAK,OAAO;AAAA,QACpB,OAAO;AAAA,MACT,CAAC;AAED,aAAO,KAAK,SAAS,OAAO,MAAM,mBAAmB;AAGrD,YAAM,mBAAmB,oBAAI,IAAoB;AACjD,iBAAW,CAAC,QAAQ,OAAO,KAAK,KAAK,UAAU;AAC7C,yBAAiB,IAAI,QAAQ,UAAU,MAAM;AAAA,MAC/C;AAEA,iBAAW,SAAS,QAAQ;AAC1B,YAAI;AAEF,cAAI,iBAAiB,IAAI,MAAM,EAAE,GAAG;AAClC,mBAAO;AACP;AAAA,UACF;AAGA,gBAAM,SAAS,MAAM,KAAK,0BAA0B,KAAK;AAEzD,cAAI,QAAQ;AAEV,kBAAM,UAAuB;AAAA,cAC3B,eAAe;AAAA,cACf,UAAU,MAAM;AAAA,cAChB,kBAAkB,MAAM;AAAA,cACxB,mBAAmB,KAAK,IAAI;AAAA,cAC5B,kBAAkB,MAAM;AAAA,cACxB,uBAAuB,KAAK,IAAI;AAAA,YAClC;AACA,iBAAK,SAAS,IAAI,QAAQ,OAAO;AACjC,mBAAO;AACP,mBAAO,KAAK,YAAY,MAAM,UAAU,KAAK,MAAM,KAAK,EAAE;AAAA,UAC5D;AAAA,QACF,SAAS,OAAgB;AACvB,iBAAO,OAAO;AAAA,YACZ,oBAAoB,MAAM,UAAU,KAAK,OAAO,KAAK,CAAC;AAAA,UACxD;AACA,iBAAO,MAAM,oBAAoB,MAAM,UAAU,KAAK,KAAc;AAAA,QACtE;AAAA,MACF;AAEA,WAAK,aAAa;AAAA,IACpB,SAAS,OAAgB;AACvB,aAAO,OAAO,KAAK,kBAAkB,OAAO,KAAK,CAAC,EAAE;AACpD,aAAO,MAAM,yBAAyB,KAAc;AAAA,IACtD;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,0BACZ,OACwB;AACxB,QAAI;AACF,YAAM,WAAW,KAAK,yBAAyB,MAAM,QAAQ;AAG7D,UAAI,cAAc,MAAM,eAAe;AACvC,qBAAe;AAAA;AAAA;AAAA,cAAwB,MAAM,UAAU,MAAM,MAAM,GAAG;AAGtE,YAAM,SAAS,MAAM,QAAQ,MAAM,MAAM,IACrC,MAAM,SACL,MAAM,QACH,SAAS,CAAC;AAClB,YAAM,OAAO,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI;AACrC,UAAI,KAAK,WAAW,EAAG,MAAK,KAAK,QAAQ;AAGzC,YAAM,SAAS,KAAK,UAAU,WAAW;AAAA,QACvC,OAAO,IAAI,MAAM,UAAU,KAAK,MAAM,KAAK;AAAA,QAC3C;AAAA,QACA;AAAA,QACA,SAAS;AAAA,QACT;AAAA,QACA,iBAAiB,MAAM,WAAW,MAAM,WAAW,KAAK;AAAA,MAC1D,CAAC;AAGD,YAAM,SAAS,KAAK,uBAAuB,MAAM,MAAM,IAAI;AAC3D,UAAI,WAAW,WAAW;AACxB,aAAK,UAAU;AAAA,UACb;AAAA,UACA;AAAA,UACA,2BAA2B,MAAM;AAAA,QACnC;AAAA,MACF;AAEA,aAAO;AAAA,IACT,SAAS,OAAgB;AACvB,aAAO;AAAA,QACL,2CAA2C,MAAM,UAAU,KAAK,OAAO,KAAK,CAAC;AAAA,MAC/E;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,yBAAyB,UAAgC;AAC/D,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;AACF;AAKO,MAAM,sBAAkC;AAAA,EAC7C,SAAS;AAAA,EACT,WAAW;AAAA,EACX,UAAU;AAAA,EACV,oBAAoB;AAAA,EACpB,cAAc;AAAA;AAAA,EACd,cAAc;AAAA;AAAA,EACd,gBAAgB;AAAA;AAClB;AAWO,MAAM,wBAAwB;AAAA,EAC3B;AAAA,EACA,aAAyC,oBAAI,IAAI;AAAA,EACjD,cAAsB,IAAI,KAAK;AAAA;AAAA,EAC/B,mBAA2B;AAAA,EAEnC,YAAY,cAA4B;AACtC,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,OAAe,QAAyC;AAC1E,UAAM,kBAAkB,KAAK,eAAe,KAAK;AAGjD,QAAI,KAAK,aAAa,GAAG;AACvB,YAAM,SAAS,KAAK,WAAW,IAAI,eAAe;AAClD,UAAI,OAAQ,QAAO;AAAA,IACrB;AAEA,QAAI;AAEF,YAAM,YAAY,MAAM,KAAK,aAAa,UAAU;AAAA,QAClD;AAAA,QACA,OAAO;AAAA;AAAA,MACT,CAAC;AAGD,YAAM,iBAAiB,UAAU,OAAO,WAAS;AAC/C,cAAM,kBAAkB,KAAK,eAAe,MAAM,KAAK;AAGvD,YAAI,oBAAoB,gBAAiB,QAAO;AAGhD,cAAM,aAAa,KAAK,oBAAoB,iBAAiB,eAAe;AAC5E,eAAO,aAAa;AAAA,MACtB,CAAC;AAGD,WAAK,WAAW,IAAI,iBAAiB,cAAc;AACnD,WAAK,mBAAmB,KAAK,IAAI;AAEjC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,aAAO,MAAM,4CAA4C,KAAc;AACvE,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBACJ,OACA,QAC+B;AAC/B,UAAM,iBAAiB,MAAM,KAAK,cAAc,OAAO,MAAM;AAE7D,QAAI,eAAe,WAAW,GAAG;AAC/B,aAAO,EAAE,aAAa,MAAM;AAAA,IAC9B;AAGA,QAAI;AACJ,QAAI,iBAAiB;AAErB,eAAW,SAAS,gBAAgB;AAClC,YAAM,aAAa,KAAK;AAAA,QACtB,KAAK,eAAe,KAAK;AAAA,QACzB,KAAK,eAAe,MAAM,KAAK;AAAA,MACjC;AAEA,UAAI,aAAa,gBAAgB;AAC/B,yBAAiB;AACjB,oBAAY;AAAA,MACd;AAAA,IACF;AAEA,WAAO;AAAA,MACL,aAAa;AAAA,MACb,eAAe;AAAA,MACf,YAAY;AAAA,IACd;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBACJ,eACA,UACA,gBACA,mBACsB;AACtB,QAAI;AAEF,UAAI,oBAAoB,cAAc,eAAe;AAErD,UAAI,kBAAkB,CAAC,kBAAkB,SAAS,cAAc,GAAG;AACjE,6BAAqB;AAAA;AAAA,0BAA8B,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA;AAC3E,6BAAqB;AAAA,MACvB;AAEA,UAAI,mBAAmB;AACrB,6BAAqB;AAAA;AAAA;AAAA,EAAY,iBAAiB;AAAA,MACpD;AAGA,YAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcpB,YAAM,YAAY;AAAA,QAChB,IAAI,cAAc;AAAA,QAClB,OAAO;AAAA,UACL,aAAa;AAAA,QACf;AAAA,MACF;AAEA,YAAM,WAAW,MAAM,KAAK,aAAa,QAAQ,aAAa,SAAS;AACvE,YAAM,eAAe,SAAS,aAAa;AAE3C,UAAI,cAAc;AAChB,eAAO;AAAA,UACL,6CAA6C,cAAc,UAAU,KAAK,cAAc,KAAK;AAAA,QAC/F;AACA,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,aAAO,MAAM,+CAA+C,KAAc;AAC1E,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,OAAuB;AAC5C,WAAO,MACJ,YAAY,EACZ,KAAK,EACL,QAAQ,QAAQ,GAAG,EACnB,QAAQ,aAAa,EAAE,EACvB,QAAQ,+CAA+C,EAAE,EACzD,KAAK;AAAA,EACV;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoB,MAAc,MAAsB;AAC9D,QAAI,SAAS,KAAM,QAAO;AAC1B,QAAI,KAAK,WAAW,KAAK,KAAK,WAAW,EAAG,QAAO;AAGnD,UAAM,WAAW,KAAK,oBAAoB,MAAM,IAAI;AACpD,UAAM,YAAY,KAAK,IAAI,KAAK,QAAQ,KAAK,MAAM;AAEnD,WAAO,IAAK,WAAW;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoB,MAAc,MAAsB;AAC9D,UAAM,IAAI,KAAK;AACf,UAAM,IAAI,KAAK;AACf,UAAM,KAAiB,MAAM,IAAI,CAAC,EAC/B,KAAK,IAAI,EACT,IAAI,MAAM,MAAM,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;AAEjC,aAAS,IAAI,GAAG,KAAK,GAAG,IAAK,IAAG,CAAC,EAAE,CAAC,IAAI;AACxC,aAAS,IAAI,GAAG,KAAK,GAAG,IAAK,IAAG,CAAC,EAAE,CAAC,IAAI;AAExC,aAAS,IAAI,GAAG,KAAK,GAAG,KAAK;AAC3B,eAAS,IAAI,GAAG,KAAK,GAAG,KAAK;AAC3B,YAAI,KAAK,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,GAAG;AAC/B,aAAG,CAAC,EAAE,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,QAC5B,OAAO;AACL,aAAG,CAAC,EAAE,CAAC,IAAI,IAAI,KAAK;AAAA,YAClB,GAAG,IAAI,CAAC,EAAE,CAAC;AAAA;AAAA,YACX,GAAG,CAAC,EAAE,IAAI,CAAC;AAAA;AAAA,YACX,GAAG,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA;AAAA,UACjB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO,GAAG,CAAC,EAAE,CAAC;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAwB;AAC9B,WAAO,KAAK,IAAI,IAAI,KAAK,mBAAmB,KAAK;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,SAAK,WAAW,MAAM;AACtB,SAAK,mBAAmB;AAAA,EAC1B;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|