@endlessblink/like-i-said-v2
Version:
Task Management & Memory for Claude - Track tasks, remember context, and maintain continuity across sessions with 27 powerful tools. Works with Claude Desktop and Claude Code.
416 lines (355 loc) • 12.7 kB
text/typescript
// Smart Memory Content Analyzer
export interface ContentAnalysis {
contentType: 'text' | 'code' | 'structured'
suggestedCategory: string | null
suggestedTags: string[]
suggestedPriority: 'low' | 'medium' | 'high'
title: string | null
summary: string | null
language?: string // for code content
confidence: number // 0-1 score
}
export interface CategoryPattern {
category: string
keywords: string[]
patterns: RegExp[]
weight: number
}
export interface TagPattern {
tag: string
patterns: RegExp[]
weight: number
}
// Category detection patterns
const CATEGORY_PATTERNS: CategoryPattern[] = [
{
category: 'work',
keywords: ['meeting', 'client', 'project', 'deadline', 'sprint', 'standup', 'review', 'business'],
patterns: [
/\b(meeting|attendees|action items?|agenda|client|deadline|sprint|standup|scrum)\b/i,
/\b(quarterly|q[1-4]|revenue|budget|roi|kpi|metrics)\b/i
],
weight: 1.0
},
{
category: 'code',
keywords: ['function', 'class', 'bug', 'fix', 'error', 'debug', 'api', 'database'],
patterns: [
/\b(function|class|const|let|var|import|export|if|else|for|while)\b/,
/\b(bug|error|exception|debug|fix|issue|patch)\b/i,
/\b(api|endpoint|database|query|schema|migration)\b/i,
/```[\s\S]*?```/,
/`[^`]+`/
],
weight: 1.2
},
{
category: 'research',
keywords: ['study', 'research', 'analysis', 'findings', 'hypothesis', 'experiment'],
patterns: [
/\b(research|study|analysis|findings|hypothesis|experiment|survey|data)\b/i,
/\b(conclusion|methodology|results|abstract|references?)\b/i,
/\b(according to|studies show|research indicates)\b/i
],
weight: 1.0
},
{
category: 'personal',
keywords: ['personal', 'reminder', 'todo', 'note', 'idea', 'thought'],
patterns: [
/\b(personal|private|reminder|todo|note|idea|thought|journal)\b/i,
/\b(remember to|don't forget|need to)\b/i
],
weight: 0.8
},
{
category: 'conversations',
keywords: ['conversation', 'discussion', 'chat', 'call', 'talk'],
patterns: [
/\b(conversation|discussion|chat|call|talk|spoke with|talked to)\b/i,
/\b(said|mentioned|suggested|agreed|disagreed)\b/i,
/^[A-Za-z\s]+:\s/m // Speaker format "John: Hello"
],
weight: 1.0
}
]
// Tag detection patterns
const TAG_PATTERNS: TagPattern[] = [
{ tag: 'urgent', patterns: [/\b(urgent|asap|critical|emergency|immediate)\b/i], weight: 1.0 },
{ tag: 'important', patterns: [/\b(important|crucial|vital|key|essential)\b/i], weight: 0.8 },
{ tag: 'todo', patterns: [/\b(todo|task|action item|need to|should)\b/i, /- \[ \]/], weight: 0.9 },
{ tag: 'meeting', patterns: [/\b(meeting|standup|scrum|review|sync)\b/i], weight: 1.0 },
{ tag: 'bug', patterns: [/\b(bug|error|issue|problem|broken)\b/i], weight: 1.0 },
{ tag: 'feature', patterns: [/\b(feature|enhancement|improvement|new)\b/i], weight: 0.8 },
{ tag: 'api', patterns: [/\b(api|endpoint|rest|graphql|webhook)\b/i], weight: 1.0 },
{ tag: 'database', patterns: [/\b(database|db|sql|query|table|schema)\b/i], weight: 1.0 },
{ tag: 'frontend', patterns: [/\b(frontend|ui|ux|react|vue|angular|css|html)\b/i], weight: 0.9 },
{ tag: 'backend', patterns: [/\b(backend|server|node|python|java|api)\b/i], weight: 0.9 },
{ tag: 'documentation', patterns: [/\b(docs|documentation|readme|guide|tutorial)\b/i], weight: 0.8 },
{ tag: 'security', patterns: [/\b(security|auth|authentication|authorization|vulnerability)\b/i], weight: 1.0 },
{ tag: 'performance', patterns: [/\b(performance|optimization|speed|slow|fast|cache)\b/i], weight: 0.9 },
{ tag: 'testing', patterns: [/\b(test|testing|spec|unit|integration|e2e)\b/i], weight: 0.9 }
]
// Programming language detection
const LANGUAGE_PATTERNS: { [key: string]: RegExp[] } = {
javascript: [
/\b(const|let|var|function|=>|import|export|require)\b/,
/\.(js|jsx|ts|tsx)$/,
/console\.log|document\.|window\./
],
python: [
/\b(def|class|import|from|if __name__|print\()\b/,
/\.py$/,
/^\s*(def|class|import|from)\s/m
],
java: [
/\b(public|private|class|interface|extends|implements)\b/,
/\.java$/,
/System\.out\.println/
],
go: [
/\b(func|package|import|var|const|type|interface)\b/,
/\.go$/,
/fmt\.Print/
],
rust: [
/\b(fn|let|mut|struct|enum|impl|trait)\b/,
/\.rs$/,
/println!/
],
sql: [
/\b(SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP)\b/i,
/\.sql$/,
/FROM|WHERE|JOIN|GROUP BY|ORDER BY/i
]
}
export function analyzeContent(content: string): ContentAnalysis {
const text = content.trim()
if (!text) {
return {
contentType: 'text',
suggestedCategory: null,
suggestedTags: [],
suggestedPriority: 'medium',
title: null,
summary: null,
confidence: 0
}
}
const analysis: ContentAnalysis = {
contentType: detectContentType(text),
suggestedCategory: detectCategory(text),
suggestedTags: detectTags(text),
suggestedPriority: detectPriority(text),
title: extractTitle(text),
summary: extractSummary(text),
confidence: 0
}
// Detect programming language for code content
if (analysis.contentType === 'code') {
analysis.language = detectLanguage(text)
}
// Calculate confidence score
analysis.confidence = calculateConfidence(text, analysis)
return analysis
}
function detectContentType(text: string): 'text' | 'code' | 'structured' {
// Check for code blocks
if (text.includes('```') || text.match(/^```/m)) {
return 'code'
}
// Check for inline code
const inlineCodeCount = (text.match(/`[^`]+`/g) || []).length
if (inlineCodeCount > 2) {
return 'code'
}
// Check for programming keywords
const codeKeywords = ['function', 'const', 'let', 'var', 'class', 'import', 'export', 'def', 'public', 'private']
const codeKeywordCount = codeKeywords.filter(keyword =>
new RegExp(`\\b${keyword}\\b`, 'i').test(text)
).length
if (codeKeywordCount > 2) {
return 'code'
}
// Check for structured content
const structuredIndicators = [
/^#{1,6}\s/m, // Markdown headers
/^[-*+]\s/m, // Lists
/^(\d+\.)\s/m, // Numbered lists
/^- \[[ x]\]/m, // Checkboxes
/^\|.*\|/m, // Tables
/^>.*$/m, // Blockquotes
]
const structuredCount = structuredIndicators.filter(pattern => pattern.test(text)).length
if (structuredCount > 1) {
return 'structured'
}
// Check for multiple sections/paragraphs
const paragraphs = text.split('\n\n').filter(p => p.trim().length > 20)
if (paragraphs.length > 2 && text.includes('\n\n')) {
return 'structured'
}
return 'text'
}
function detectCategory(text: string): string | null {
const scores: { [category: string]: number } = {}
const lowerText = text.toLowerCase()
for (const pattern of CATEGORY_PATTERNS) {
let score = 0
// Check keywords
for (const keyword of pattern.keywords) {
const regex = new RegExp(`\\b${keyword}\\b`, 'gi')
const matches = lowerText.match(regex) || []
score += matches.length * 0.5
}
// Check patterns
for (const regex of pattern.patterns) {
const matches = text.match(regex) || []
score += matches.length * 1.0
}
// Apply category weight
score *= pattern.weight
if (score > 0) {
scores[pattern.category] = (scores[pattern.category] || 0) + score
}
}
// Return category with highest score, if above threshold
const entries = Object.entries(scores).sort(([,a], [,b]) => b - a)
const topScore = entries[0]?.[1] || 0
return topScore > 1.0 ? entries[0][0] : null
}
function detectTags(text: string): string[] {
const tags = new Set<string>()
const lowerText = text.toLowerCase()
// Extract hashtags
const hashtagMatches = text.match(/#(\w+)/g) || []
hashtagMatches.forEach(match => tags.add(match.substring(1).toLowerCase()))
// Pattern-based tag detection
for (const tagPattern of TAG_PATTERNS) {
let score = 0
for (const pattern of tagPattern.patterns) {
const matches = text.match(pattern) || []
score += matches.length * tagPattern.weight
}
if (score >= 1.0) {
tags.add(tagPattern.tag)
}
}
// Content-specific tags
if (text.includes('TODO') || text.includes('FIXME')) {
tags.add('todo')
}
if (text.match(/\b(note|reminder|remember)\b/i)) {
tags.add('note')
}
// Priority indicators
if (text.match(/\b(urgent|asap|critical)\b/i)) {
tags.add('urgent')
}
return Array.from(tags).slice(0, 8) // Limit to 8 tags
}
function detectPriority(text: string): 'low' | 'medium' | 'high' {
const lowerText = text.toLowerCase()
// High priority indicators
if (lowerText.match(/\b(urgent|asap|critical|emergency|immediate|deadline|tomorrow)\b/)) {
return 'high'
}
// Low priority indicators
if (lowerText.match(/\b(someday|maybe|nice to have|optional|when time permits)\b/)) {
return 'low'
}
// Medium priority indicators or default
return 'medium'
}
function detectLanguage(text: string): string | undefined {
for (const [language, patterns] of Object.entries(LANGUAGE_PATTERNS)) {
let score = 0
for (const pattern of patterns) {
if (pattern.test(text)) {
score++
}
}
if (score >= 2) {
return language
}
}
return undefined
}
function extractTitle(text: string): string | null {
const lines = text.split('\n').filter(line => line.trim())
// Check for markdown headers
const headerMatch = text.match(/^#{1,6}\s+(.+)$/m)
if (headerMatch) {
return headerMatch[1].trim()
}
// Check for first non-empty line
const firstLine = lines[0]?.trim()
if (firstLine && firstLine.length > 5 && firstLine.length < 100) {
// Avoid using lines that look like code or have special formatting
if (!firstLine.match(/^[\[\{]/) && !firstLine.includes('()') && !firstLine.startsWith('//')) {
return firstLine
}
}
return null
}
function extractSummary(text: string): string | null {
// Remove markdown headers and code blocks
const cleanText = text
.replace(/^#{1,6}\s+.*$/gm, '') // Headers
.replace(/```[\s\S]*?```/g, '') // Code blocks
.replace(/`[^`]+`/g, '') // Inline code
.trim()
if (!cleanText) return null
// Get first few sentences
const sentences = cleanText.split(/[.!?]+/).filter(s => s.trim().length > 10)
if (sentences.length === 0) return null
const summary = sentences.slice(0, 2).join('. ').trim()
if (summary.length < 20) {
// If too short, take more content
const words = cleanText.split(/\s+/).slice(0, 25)
return words.join(' ') + (cleanText.split(/\s+/).length > 25 ? '...' : '')
}
return summary.length > 150 ? summary.substring(0, 147) + '...' : summary
}
function calculateConfidence(text: string, analysis: ContentAnalysis): number {
let confidence = 0.5 // Base confidence
// Content type confidence
if (analysis.contentType === 'code' && (text.includes('```') || text.includes('function'))) {
confidence += 0.2
} else if (analysis.contentType === 'structured' && text.includes('#')) {
confidence += 0.15
}
// Category confidence
if (analysis.suggestedCategory) {
confidence += 0.15
}
// Tags confidence
if (analysis.suggestedTags.length > 0) {
confidence += Math.min(analysis.suggestedTags.length * 0.05, 0.2)
}
// Title extraction confidence
if (analysis.title) {
confidence += 0.1
}
return Math.min(confidence, 1.0)
}
// Utility function for template variable replacement
export function applyTemplate(template: string, variables: Record<string, string>): string {
let result = template
for (const [key, value] of Object.entries(variables)) {
const placeholder = `{{${key}}}`
result = result.replaceAll(placeholder, value)
}
// Replace any remaining placeholders with defaults
result = result.replace(/{{(\w+)}}/g, (match, key) => {
const defaults: Record<string, string> = {
date: new Date().toLocaleDateString(),
time: new Date().toLocaleTimeString(),
title: 'Title',
topic: 'Topic',
language: 'javascript'
}
return defaults[key] || match
})
return result
}