UNPKG

@stackmemoryai/stackmemory

Version:

Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a

429 lines (428 loc) 13.3 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 { getSkillRegistry, matchPromptFromRegistry } from "../../../core/skills/index.js"; class SkillHandlers { constructor(dbPath) { this.dbPath = dbPath; } registry = null; getRegistry() { if (!this.registry) { this.registry = getSkillRegistry(this.dbPath); } return this.registry; } isAvailable() { return !!(process.env["STACKMEMORY_SKILLS"] || process.env["SM_SKILLS"]); } // ============================================================ // SKILL OPERATIONS // ============================================================ async recordSkill(args, context) { try { const registry = this.getRegistry(); const skill = registry.createSkill({ content: args.content, category: args.category, priority: args.priority || "medium", tool: args.tool, tags: args.tags || [], source: args.source || "observation", sessionId: context.sessionId }); return { success: true, skill }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; logger.error("Failed to record skill", { error: message }); return { success: false, error: message }; } } async getRelevantSkills(args) { try { const registry = this.getRegistry(); const skills = registry.getRelevantSkills({ tool: args.tool, language: args.language, framework: args.framework, tags: args.tags }); const limited = args.limit ? skills.slice(0, args.limit) : skills; return { success: true, skills: limited }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; logger.error("Failed to get relevant skills", { error: message }); return { success: false, error: message }; } } async querySkills(args) { try { const registry = this.getRegistry(); const query = { categories: args.categories, priorities: args.priorities, tool: args.tool, tags: args.tags, minValidatedCount: args.minValidatedCount, limit: args.limit || 50, offset: 0, sortBy: args.sortBy || "priority", sortOrder: "desc" }; const skills = registry.querySkills(query); return { success: true, skills, total: skills.length }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; logger.error("Failed to query skills", { error: message }); return { success: false, error: message }; } } async validateSkill(args) { try { const registry = this.getRegistry(); const skill = registry.validateSkill(args.skill_id); if (!skill) return { success: false, error: "Skill not found" }; return { success: true, skill }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; logger.error("Failed to validate skill", { error: message }); return { success: false, error: message }; } } async updateSkill(args) { try { const registry = this.getRegistry(); const skill = registry.updateSkill({ id: args.skill_id, content: args.content, priority: args.priority, tags: args.tags }); if (!skill) return { success: false, error: "Skill not found" }; return { success: true, skill }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; logger.error("Failed to update skill", { error: message }); return { success: false, error: message }; } } async deleteSkill(args) { try { const registry = this.getRegistry(); const deleted = registry.deleteSkill(args.skill_id); if (!deleted) return { success: false, error: "Skill not found" }; return { success: true }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; logger.error("Failed to delete skill", { error: message }); return { success: false, error: message }; } } // ============================================================ // MATCH PROMPT (NEW) // ============================================================ async matchPrompt(args) { try { const result = matchPromptFromRegistry(args.prompt); return { success: true, result }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; logger.error("Failed to match prompt", { error: message }); return { success: false, error: message }; } } // ============================================================ // SESSION JOURNAL OPERATIONS // ============================================================ async recordJournalEntry(args, context) { try { const registry = this.getRegistry(); const sessionId = context.sessionId || "default"; const entry = registry.createJournalEntry( sessionId, args.type, args.title, args.content, { tool: args.tool, file: args.file } ); return { success: true, entryId: entry.id }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; logger.error("Failed to record journal entry", { error: message }); return { success: false, error: message }; } } async getSessionJournal(args, context) { try { const registry = this.getRegistry(); const sessionId = args.session_id || context.sessionId || "default"; const entries = registry.getSessionJournal(sessionId); return { success: true, entries }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; logger.error("Failed to get session journal", { error: message }); return { success: false, error: message }; } } async promoteToSkill(args) { try { const registry = this.getRegistry(); const skill = registry.promoteToSkill( args.entry_id, args.category, args.priority || "medium" ); if (!skill) return { success: false, error: "Journal entry not found" }; return { success: true, skill }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; logger.error("Failed to promote journal entry", { error: message }); return { success: false, error: message }; } } // ============================================================ // SESSION MANAGEMENT // ============================================================ async startSession(args) { try { const registry = this.getRegistry(); registry.startSession(args.session_id); return { success: true }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; logger.error("Failed to start session", { error: message }); return { success: false, error: message }; } } async endSession(args) { try { const registry = this.getRegistry(); const summary = registry.endSession(args.session_id); if (!summary) return { success: false, error: "Session not found" }; return { success: true, summary }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; logger.error("Failed to end session", { error: message }); return { success: false, error: message }; } } // ============================================================ // METRICS // ============================================================ async getSkillMetrics() { try { const registry = this.getRegistry(); const metrics = registry.getMetrics(); return { success: true, metrics }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; logger.error("Failed to get skill metrics", { error: message }); return { success: false, error: message }; } } } const SKILL_TOOL_DEFINITIONS = [ { name: "record_skill", description: "Record a new learning, pattern, or skill to remember across sessions", inputSchema: { type: "object", properties: { content: { type: "string", description: "The skill/learning content to remember" }, category: { type: "string", enum: [ "tool", "workflow", "correction", "pattern", "preference", "pitfall", "optimization" ], description: "Category of the skill" }, priority: { type: "string", enum: ["critical", "high", "medium", "low"], default: "medium", description: "How important this skill is" }, tool: { type: "string", description: "Related tool name (if applicable)" }, tags: { type: "array", items: { type: "string" }, description: "Tags for categorization" } }, required: ["content", "category"] } }, { name: "get_relevant_skills", description: "Get skills relevant to current context (tool, language, etc.)", inputSchema: { type: "object", properties: { tool: { type: "string", description: "Filter by tool name" }, language: { type: "string", description: "Filter by programming language" }, framework: { type: "string", description: "Filter by framework" }, limit: { type: "number", default: 20, description: "Maximum skills to return" } } } }, { name: "validate_skill", description: "Mark a skill as validated/reinforced (increases its importance)", inputSchema: { type: "object", properties: { skill_id: { type: "string", description: "ID of the skill to validate" } }, required: ["skill_id"] } }, { name: "record_correction", description: "Record a user correction to remember and apply in future sessions", inputSchema: { type: "object", properties: { title: { type: "string", description: "Brief title of the correction" }, content: { type: "string", description: "What was corrected and how to do it correctly" }, tool: { type: "string", description: "Related tool (if applicable)" } }, required: ["title", "content"] } }, { name: "record_decision", description: "Record an important decision made during this session", inputSchema: { type: "object", properties: { title: { type: "string", description: "Brief title of the decision" }, content: { type: "string", description: "The decision and its reasoning" }, file: { type: "string", description: "Related file path (if applicable)" } }, required: ["title", "content"] } }, { name: "get_session_learnings", description: "Get all learnings and corrections from current or specified session", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "Session ID (defaults to current)" } } } }, { name: "promote_learning", description: "Promote a session learning to a permanent skill", inputSchema: { type: "object", properties: { entry_id: { type: "string", description: "Journal entry ID to promote" }, category: { type: "string", enum: [ "tool", "workflow", "correction", "pattern", "preference", "pitfall", "optimization" ], description: "Skill category" }, priority: { type: "string", enum: ["critical", "high", "medium", "low"], default: "medium", description: "Skill priority" } }, required: ["entry_id", "category"] } }, { name: "get_skill_metrics", description: "Get metrics about stored skills and learnings", inputSchema: { type: "object", properties: {} } }, { name: "match_prompt", description: "Match a prompt against skill rules and return scored skill suggestions", inputSchema: { type: "object", properties: { prompt: { type: "string", description: "The prompt text to match against skill rules" } }, required: ["prompt"] } } ]; export { SKILL_TOOL_DEFINITIONS, SkillHandlers };