UNPKG

@stackmemoryai/stackmemory

Version:

Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.

214 lines (213 loc) 5.62 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { logger } from "../../core/monitoring/logger.js"; import { IntegrationError, ErrorCode } from "../../core/errors/index.js"; class LinearRestClient { apiKey; baseUrl = "https://api.linear.app/graphql"; taskCache = /* @__PURE__ */ new Map(); lastSync = 0; cacheTTL = 5 * 60 * 1e3; // 5 minutes constructor(apiKey) { this.apiKey = apiKey; } /** * Get all tasks and store in memory */ async getAllTasks(forceRefresh = false) { const now = Date.now(); if (!forceRefresh && now - this.lastSync < this.cacheTTL && this.taskCache.size > 0) { return Array.from(this.taskCache.values()); } try { const allTasks = []; let hasNextPage = true; let cursor; while (hasNextPage) { const query = ` query($after: String) { issues( filter: { team: { key: { eq: "ENG" } } } first: 100 after: $after ) { nodes { id identifier title description state { name type } priority assignee { id name } estimate createdAt updatedAt url } pageInfo { hasNextPage endCursor } } } `; const variables = cursor ? { after: cursor } : {}; const response = await this.makeRequest( query, variables ); const tasks = response.data.issues.nodes; allTasks.push(...tasks); tasks.forEach((task) => { this.taskCache.set(task.id, task); }); hasNextPage = response.data.issues.pageInfo.hasNextPage; cursor = response.data.issues.pageInfo.endCursor; logger.info(`Fetched ${tasks.length} tasks, total: ${allTasks.length}`); } this.lastSync = now; logger.info(`Cached ${allTasks.length} Linear tasks in memory`); return allTasks; } catch (error) { logger.error("Failed to fetch Linear tasks:", error); return Array.from(this.taskCache.values()); } } /** * Get tasks by status */ async getTasksByStatus(status) { const tasks = await this.getAllTasks(); return tasks.filter((task) => task.state.type === status); } /** * Get tasks assigned to current user */ async getMyTasks() { try { const viewer = await this.getViewer(); const tasks = await this.getAllTasks(); return tasks.filter((task) => task.assignee?.id === viewer.id); } catch (error) { logger.error("Failed to get assigned tasks:", error); return []; } } /** * Get task count by status */ async getTaskCounts() { const tasks = await this.getAllTasks(); const counts = {}; tasks.forEach((task) => { const status = task.state.type; counts[status] = (counts[status] || 0) + 1; }); return counts; } /** * Search tasks by title or description */ async searchTasks(query) { const tasks = await this.getAllTasks(); const searchTerm = query.toLowerCase(); return tasks.filter( (task) => task.title.toLowerCase().includes(searchTerm) || task.description?.toLowerCase().includes(searchTerm) || task.identifier.toLowerCase().includes(searchTerm) ); } /** * Get current viewer info */ async getViewer() { const query = ` query { viewer { id name email } } `; const response = await this.makeRequest(query); return response.data.viewer; } /** * Get team info */ async getTeam() { const query = ` query { teams(filter: { key: { eq: "ENG" } }, first: 1) { nodes { id name key } } } `; const response = await this.makeRequest(query); if (response.data.teams.nodes.length === 0) { throw new IntegrationError( "ENG team not found", ErrorCode.LINEAR_API_ERROR ); } return response.data.teams.nodes[0]; } /** * Get cache stats */ getCacheStats() { const now = Date.now(); return { size: this.taskCache.size, lastSync: this.lastSync, age: now - this.lastSync, fresh: now - this.lastSync < this.cacheTTL }; } /** * Clear cache */ clearCache() { this.taskCache.clear(); this.lastSync = 0; logger.info("Linear task cache cleared"); } /** * Make GraphQL request */ async makeRequest(query, variables = {}) { const response = await fetch(this.baseUrl, { method: "POST", headers: { Authorization: this.apiKey, "Content-Type": "application/json" }, body: JSON.stringify({ query, variables }) }); const result = await response.json(); if (!response.ok || result.errors) { const errorMsg = result.errors?.[0]?.message || `${response.status} ${response.statusText}`; throw new IntegrationError( `Linear API error: ${errorMsg}`, ErrorCode.LINEAR_API_ERROR ); } return result; } } export { LinearRestClient }; //# sourceMappingURL=rest-client.js.map