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

179 lines (178 loc) 5.56 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); function extractFilePaths(prompt) { const paths = /* @__PURE__ */ new Set(); const extensionPattern = /(?:^|\s|["'`])([\w\-./]+\.(?:[tj]sx?|json|gql|ya?ml|md|sh))\b/gi; let match; while ((match = extensionPattern.exec(prompt)) !== null) { paths.add(match[1]); } const dirPattern = /(?:^|\s|["'`])((?:src|app|components|screens|hooks|utils|services|navigation|graphql|localization|\.claude|\.github|\.maestro)\/[\w\-./]+)/gi; while ((match = dirPattern.exec(prompt)) !== null) { paths.add(match[1]); } const quotedPattern = /["'`]([\w\-./]+\/[\w\-./]+)["'`]/g; while ((match = quotedPattern.exec(prompt)) !== null) { paths.add(match[1]); } return Array.from(paths); } function matchesPattern(text, pattern, flags = "i") { try { return new RegExp(pattern, flags).test(text); } catch { return false; } } function matchesGlob(filePath, globPattern) { const regexPattern = globPattern.replace(/\./g, "\\.").replace(/\?/g, "<<<QUESTION>>>").replace(/\*\*\//g, "<<<DOUBLESTARSLASH>>>").replace(/\*\*/g, "<<<DOUBLESTAR>>>").replace(/\*/g, "[^/]*").replace(/<<<DOUBLESTARSLASH>>>/g, "(?:.*\\/)?").replace(/<<<DOUBLESTAR>>>/g, ".*").replace(/<<<QUESTION>>>/g, "."); try { return new RegExp(`^${regexPattern}$`, "i").test(filePath); } catch { return false; } } function matchDirectoryMapping(filePath, mappings) { for (const [dir, skillName] of Object.entries(mappings)) { if (filePath === dir || filePath.startsWith(dir + "/")) { return skillName; } } return void 0; } function evaluateSkill(skillName, skill, prompt, promptLower, filePaths, scoring, directoryMappings) { const { triggers, excludePatterns = [], priority = 5 } = skill; let score = 0; const reasons = []; for (const excludePattern of excludePatterns) { if (matchesPattern(promptLower, excludePattern)) { return void 0; } } if (triggers.keywords) { for (const keyword of triggers.keywords) { if (promptLower.includes(keyword.toLowerCase())) { score += scoring.keyword; reasons.push(`keyword "${keyword}"`); } } } if (triggers.keywordPatterns) { for (const pattern of triggers.keywordPatterns) { if (matchesPattern(promptLower, pattern)) { score += scoring.keywordPattern; reasons.push(`pattern /${pattern}/`); } } } if (triggers.intentPatterns) { for (const pattern of triggers.intentPatterns) { if (matchesPattern(promptLower, pattern)) { score += scoring.intentPattern; reasons.push("intent detected"); break; } } } if (triggers.contextPatterns) { for (const pattern of triggers.contextPatterns) { if (promptLower.includes(pattern.toLowerCase())) { score += scoring.contextPattern; reasons.push(`context "${pattern}"`); } } } if (triggers.pathPatterns && filePaths.length > 0) { for (const filePath of filePaths) { for (const pattern of triggers.pathPatterns) { if (matchesGlob(filePath, pattern)) { score += scoring.pathPattern; reasons.push(`path "${filePath}"`); break; } } } } if (filePaths.length > 0) { for (const filePath of filePaths) { const mappedSkill = matchDirectoryMapping(filePath, directoryMappings); if (mappedSkill === skillName) { score += scoring.directoryMatch; reasons.push("directory mapping"); break; } } } if (triggers.contentPatterns) { for (const pattern of triggers.contentPatterns) { if (matchesPattern(prompt, pattern)) { score += scoring.contentPattern; reasons.push("code pattern detected"); break; } } } if (score > 0) { return { name: skillName, score, reasons: [...new Set(reasons)], priority }; } return void 0; } function getRelatedSkills(matches, rules) { const matchedNames = new Set(matches.map((m) => m.name)); const related = /* @__PURE__ */ new Set(); for (const m of matches) { const skill = rules[m.name]; if (skill?.relatedSkills) { for (const relatedName of skill.relatedSkills) { if (!matchedNames.has(relatedName)) { related.add(relatedName); } } } } return Array.from(related); } function formatConfidence(score, minScore) { if (score >= minScore * 3) return "HIGH"; if (score >= minScore * 2) return "MEDIUM"; return "LOW"; } function matchPrompt(prompt, rules, config, scoring, directoryMappings = {}) { const promptLower = prompt.toLowerCase(); const filePaths = extractFilePaths(prompt); const matches = []; for (const [name, skill] of Object.entries(rules)) { const m = evaluateSkill( name, skill, prompt, promptLower, filePaths, scoring, directoryMappings ); if (m && m.score >= config.minConfidenceScore) { matches.push(m); } } matches.sort((a, b) => { if (b.score !== a.score) return b.score - a.score; return b.priority - a.priority; }); const topMatches = matches.slice(0, config.maxSkillsToShow); const relatedSkills = getRelatedSkills(topMatches, rules); return { matches: topMatches, filePaths, relatedSkills }; } export { extractFilePaths, formatConfidence, matchPrompt, matchesGlob };