@colbymchenry/cmem 0.2.37 → 0.5.2

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/embeddings/index.ts","../../src/utils/config.ts","../../src/db/lessons.ts","../../src/db/index.ts","../../src/parser/index.ts","../../src/learning/LessonManager.ts","../../src/learning/types.ts","../../src/learning/LessonConsultant.ts","../../src/hooks/consult.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport { join } from 'path';\nimport {\n EMBEDDING_MODEL,\n MODELS_DIR,\n MAX_EMBEDDING_CHARS,\n MAX_MESSAGE_PREVIEW_CHARS,\n ensureModelsDir,\n} from '../utils/config.js';\n\n// Lazy load transformers.js\nlet transformersModule: typeof import('@xenova/transformers') | null = null;\nlet pipeline: any = null;\nlet initialized = false;\n\nasync function getTransformers() {\n if (!transformersModule) {\n transformersModule = await import('@xenova/transformers');\n }\n return transformersModule;\n}\n\nexport interface InitProgress {\n status: 'checking' | 'downloading' | 'loading' | 'ready';\n file?: string;\n progress?: number;\n}\n\n/**\n * Check if the model is already cached locally\n */\nexport function isModelCached(): boolean {\n // Transformers.js stores models at models/org/model-name/\n const modelCachePath = join(MODELS_DIR, EMBEDDING_MODEL);\n return existsSync(modelCachePath);\n}\n\n/**\n * Initialize the embedding pipeline\n * Downloads the model on first use with progress callback\n */\nexport async function initializeEmbeddings(\n onProgress?: (progress: InitProgress) => void\n): Promise<void> {\n if (initialized && pipeline) {\n return;\n }\n\n ensureModelsDir();\n\n const { pipeline: createPipeline, env } = await getTransformers();\n\n // Configure cache directory\n env.cacheDir = MODELS_DIR;\n\n // Check if model is cached\n const cached = isModelCached();\n if (cached) {\n env.allowRemoteModels = false; // Use local cache only\n onProgress?.({ status: 'loading' });\n } else {\n onProgress?.({ status: 'downloading' });\n }\n\n // Create the pipeline with progress callback\n pipeline = await createPipeline('feature-extraction', EMBEDDING_MODEL, {\n progress_callback: onProgress\n ? (progress: { status: string; file?: string; progress?: number }) => {\n if (progress.status === 'progress' && progress.file && progress.progress !== undefined) {\n onProgress({\n status: 'downloading',\n file: progress.file,\n progress: progress.progress,\n });\n }\n }\n : undefined,\n });\n\n initialized = true;\n onProgress?.({ status: 'ready' });\n}\n\n/**\n * Check if embeddings are ready to use\n */\nexport function isReady(): boolean {\n return initialized && pipeline !== null;\n}\n\n/**\n * Generate an embedding vector for text\n */\nexport async function getEmbedding(text: string): Promise<number[]> {\n // Initialize if needed (will use cached model if available)\n if (!initialized) {\n await initializeEmbeddings();\n }\n\n // Truncate to max chars\n const truncated = text.slice(0, MAX_EMBEDDING_CHARS);\n\n // Generate embedding\n const output = await pipeline(truncated, {\n pooling: 'mean',\n normalize: true,\n });\n\n // Convert to regular array\n return Array.from(output.data);\n}\n\n/**\n * Create embedding text from session data\n * Combines title, summary, and key messages for semantic search\n */\nexport function createEmbeddingText(\n title: string,\n summary: string | undefined,\n messages: Array<{ role: string; content: string }>\n): string {\n const parts: string[] = [];\n\n // Add title\n parts.push(`Title: ${title}`);\n\n // Add summary if available\n if (summary) {\n parts.push(`Summary: ${summary}`);\n }\n\n // Add first 5 user messages (truncated)\n const userMessages = messages\n .filter((m) => m.role === 'user')\n .slice(0, 5)\n .map((m) => m.content.slice(0, MAX_MESSAGE_PREVIEW_CHARS));\n\n if (userMessages.length > 0) {\n parts.push('User messages:');\n parts.push(...userMessages);\n }\n\n // Add first 3 assistant responses (truncated) for context\n const assistantMessages = messages\n .filter((m) => m.role === 'assistant')\n .slice(0, 3)\n .map((m) => m.content.slice(0, MAX_MESSAGE_PREVIEW_CHARS));\n\n if (assistantMessages.length > 0) {\n parts.push('Assistant responses:');\n parts.push(...assistantMessages);\n }\n\n return parts.join('\\n\\n');\n}\n","import { homedir } from 'os';\nimport { join, basename, dirname } from 'path';\nimport { mkdirSync, existsSync, copyFileSync } from 'fs';\n\n// Data storage paths\nexport const CMEM_DIR = join(homedir(), '.cmem');\nexport const DB_PATH = join(CMEM_DIR, 'sessions.db');\nexport const MODELS_DIR = join(CMEM_DIR, 'models');\nexport const BACKUPS_DIR = join(CMEM_DIR, 'backups');\n\n// Claude Code paths\nexport const CLAUDE_DIR = join(homedir(), '.claude');\nexport const CLAUDE_PROJECTS_DIR = join(CLAUDE_DIR, 'projects');\nexport const CLAUDE_SESSIONS_DIR = join(CLAUDE_DIR, 'sessions');\n\n// Embedding model settings (transformers.js)\nexport const EMBEDDING_MODEL = 'nomic-ai/nomic-embed-text-v1.5';\nexport const EMBEDDING_DIMENSIONS = 768;\n\n// Text limits\nexport const MAX_EMBEDDING_CHARS = 8000;\nexport const MAX_MESSAGE_PREVIEW_CHARS = 500;\nexport const MAX_MESSAGES_FOR_CONTEXT = 20;\n\n// Database maintenance thresholds\nexport const DB_SIZE_ALERT_THRESHOLD = 5 * 1024 * 1024 * 1024; // 5GB\nexport const DEFAULT_PURGE_DAYS = 30;\n\n// Ensure cmem directory exists\nexport function ensureCmemDir(): void {\n if (!existsSync(CMEM_DIR)) {\n mkdirSync(CMEM_DIR, { recursive: true });\n }\n}\n\n// Ensure models directory exists\nexport function ensureModelsDir(): void {\n ensureCmemDir();\n if (!existsSync(MODELS_DIR)) {\n mkdirSync(MODELS_DIR, { recursive: true });\n }\n}\n\n// Ensure backups directory exists\nexport function ensureBackupsDir(): void {\n ensureCmemDir();\n if (!existsSync(BACKUPS_DIR)) {\n mkdirSync(BACKUPS_DIR, { recursive: true });\n }\n}\n\n/**\n * Get the backup path for a Claude session file\n * Mirrors the Claude directory structure: ~/.cmem/backups/<project-dir>/<session>.jsonl\n */\nexport function getBackupPath(sourceFile: string): string {\n const projectDir = basename(dirname(sourceFile));\n const sessionFile = basename(sourceFile);\n return join(BACKUPS_DIR, projectDir, sessionFile);\n}\n\n/**\n * Backup a session JSONL file\n * Creates the project directory in backups if needed\n */\nexport function backupSessionFile(sourceFile: string): boolean {\n try {\n if (!existsSync(sourceFile)) return false;\n\n ensureBackupsDir();\n const backupPath = getBackupPath(sourceFile);\n const backupDir = dirname(backupPath);\n\n if (!existsSync(backupDir)) {\n mkdirSync(backupDir, { recursive: true });\n }\n\n copyFileSync(sourceFile, backupPath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Check if a backup exists for a session\n */\nexport function hasBackup(sourceFile: string): boolean {\n const backupPath = getBackupPath(sourceFile);\n return existsSync(backupPath);\n}\n\n/**\n * Restore a session from backup to Claude's directory\n * Returns true if restored successfully\n */\nexport function restoreFromBackup(sourceFile: string): boolean {\n try {\n const backupPath = getBackupPath(sourceFile);\n if (!existsSync(backupPath)) return false;\n\n const sourceDir = dirname(sourceFile);\n if (!existsSync(sourceDir)) {\n mkdirSync(sourceDir, { recursive: true });\n }\n\n copyFileSync(backupPath, sourceFile);\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Lessons Database Operations\n *\n * CRUD operations for lessons and synthesis queue.\n */\n\nimport { randomUUID } from 'crypto';\nimport { getDatabase } from './index.js';\nimport type {\n Lesson,\n CreateLessonInput,\n UpdateLessonInput,\n LessonCategory,\n LessonSearchOptions,\n LessonFeedback,\n SynthesisQueueItem,\n} from '../learning/types.js';\n\n// ==================== LESSON ROW MAPPING ====================\n\ninterface LessonRow {\n id: string;\n project_path: string;\n category: LessonCategory;\n title: string;\n trigger_context: string;\n insight: string;\n reasoning: string | null;\n confidence: number;\n times_applied: number;\n times_validated: number;\n times_rejected: number;\n source_session_id: string | null;\n source_type: string;\n archived: number;\n created_at: string;\n updated_at: string;\n last_applied_at: string | null;\n}\n\nfunction mapLessonRow(row: LessonRow): Lesson {\n return {\n id: row.id,\n projectPath: row.project_path,\n category: row.category,\n title: row.title,\n triggerContext: row.trigger_context,\n insight: row.insight,\n reasoning: row.reasoning ?? undefined,\n confidence: row.confidence,\n timesApplied: row.times_applied,\n timesValidated: row.times_validated,\n timesRejected: row.times_rejected,\n sourceSessionId: row.source_session_id ?? undefined,\n sourceType: row.source_type as Lesson['sourceType'],\n archived: row.archived === 1,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n lastAppliedAt: row.last_applied_at ?? undefined,\n };\n}\n\n// ==================== LESSON CRUD ====================\n\n/**\n * Create a new lesson\n */\nexport function createLesson(input: CreateLessonInput): Lesson {\n const db = getDatabase();\n const id = randomUUID();\n const now = new Date().toISOString();\n\n db.prepare(`\n INSERT INTO lessons (\n id, project_path, category, title, trigger_context, insight,\n reasoning, confidence, source_session_id, source_type,\n created_at, updated_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id,\n input.projectPath,\n input.category,\n input.title,\n input.triggerContext,\n input.insight,\n input.reasoning ?? null,\n input.confidence ?? 0.5,\n input.sourceSessionId ?? null,\n input.sourceType ?? 'synthesized',\n now,\n now\n );\n\n return getLesson(id)!;\n}\n\n/**\n * Update an existing lesson\n */\nexport function updateLesson(id: string, updates: UpdateLessonInput): Lesson | null {\n const db = getDatabase();\n const now = new Date().toISOString();\n\n const fields: string[] = ['updated_at = ?'];\n const values: unknown[] = [now];\n\n if (updates.category !== undefined) {\n fields.push('category = ?');\n values.push(updates.category);\n }\n if (updates.title !== undefined) {\n fields.push('title = ?');\n values.push(updates.title);\n }\n if (updates.triggerContext !== undefined) {\n fields.push('trigger_context = ?');\n values.push(updates.triggerContext);\n }\n if (updates.insight !== undefined) {\n fields.push('insight = ?');\n values.push(updates.insight);\n }\n if (updates.reasoning !== undefined) {\n fields.push('reasoning = ?');\n values.push(updates.reasoning);\n }\n if (updates.confidence !== undefined) {\n fields.push('confidence = ?');\n values.push(updates.confidence);\n }\n if (updates.archived !== undefined) {\n fields.push('archived = ?');\n values.push(updates.archived ? 1 : 0);\n }\n\n values.push(id);\n\n db.prepare(`\n UPDATE lessons SET ${fields.join(', ')} WHERE id = ?\n `).run(...values);\n\n return getLesson(id);\n}\n\n/**\n * Delete a lesson\n */\nexport function deleteLesson(id: string): boolean {\n const db = getDatabase();\n\n const transaction = db.transaction(() => {\n // Delete embedding\n db.prepare('DELETE FROM lesson_embeddings WHERE lesson_id = ?').run(id);\n // Delete feedback\n db.prepare('DELETE FROM lesson_feedback WHERE lesson_id = ?').run(id);\n // Delete lesson\n const result = db.prepare('DELETE FROM lessons WHERE id = ?').run(id);\n return result.changes > 0;\n });\n\n return transaction();\n}\n\n/**\n * Archive a lesson (soft delete)\n */\nexport function archiveLesson(id: string): void {\n const db = getDatabase();\n db.prepare(`\n UPDATE lessons SET archived = 1, updated_at = ? WHERE id = ?\n `).run(new Date().toISOString(), id);\n}\n\n/**\n * Unarchive a lesson\n */\nexport function unarchiveLesson(id: string): void {\n const db = getDatabase();\n db.prepare(`\n UPDATE lessons SET archived = 0, updated_at = ? WHERE id = ?\n `).run(new Date().toISOString(), id);\n}\n\n/**\n * Get a lesson by ID\n */\nexport function getLesson(id: string): Lesson | null {\n const db = getDatabase();\n const row = db.prepare(`\n SELECT * FROM lessons WHERE id = ?\n `).get(id) as LessonRow | undefined;\n\n return row ? mapLessonRow(row) : null;\n}\n\n/**\n * Get lessons by project path with filtering options\n */\nexport function getLessonsByProject(\n projectPath: string,\n options: LessonSearchOptions = {}\n): Lesson[] {\n const db = getDatabase();\n\n let query = 'SELECT * FROM lessons WHERE project_path = ?';\n const params: unknown[] = [projectPath];\n\n if (options.category) {\n query += ' AND category = ?';\n params.push(options.category);\n }\n\n if (options.archived !== undefined) {\n query += ' AND archived = ?';\n params.push(options.archived ? 1 : 0);\n } else {\n // Default to non-archived\n query += ' AND archived = 0';\n }\n\n if (options.minConfidence !== undefined) {\n query += ' AND confidence >= ?';\n params.push(options.minConfidence);\n }\n\n query += ' ORDER BY confidence DESC, times_applied DESC';\n\n if (options.limit) {\n query += ' LIMIT ?';\n params.push(options.limit);\n }\n\n const rows = db.prepare(query).all(...params) as LessonRow[];\n return rows.map(mapLessonRow);\n}\n\n/**\n * Get core lessons - high confidence, frequently validated\n */\nexport function getCoreLessons(projectPath: string, limit: number = 3): Lesson[] {\n const db = getDatabase();\n\n const rows = db.prepare(`\n SELECT * FROM lessons\n WHERE project_path = ?\n AND archived = 0\n AND confidence >= 0.7\n ORDER BY\n times_validated DESC,\n confidence DESC,\n times_applied DESC\n LIMIT ?\n `).all(projectPath, limit) as LessonRow[];\n\n return rows.map(mapLessonRow);\n}\n\n/**\n * Get all lessons (for admin/debugging)\n */\nexport function getAllLessons(options: LessonSearchOptions = {}): Lesson[] {\n const db = getDatabase();\n\n let query = 'SELECT * FROM lessons WHERE 1=1';\n const params: unknown[] = [];\n\n if (options.category) {\n query += ' AND category = ?';\n params.push(options.category);\n }\n\n if (options.archived !== undefined) {\n query += ' AND archived = ?';\n params.push(options.archived ? 1 : 0);\n }\n\n if (options.minConfidence !== undefined) {\n query += ' AND confidence >= ?';\n params.push(options.minConfidence);\n }\n\n query += ' ORDER BY updated_at DESC';\n\n if (options.limit) {\n query += ' LIMIT ?';\n params.push(options.limit);\n }\n\n const rows = db.prepare(query).all(...params) as LessonRow[];\n return rows.map(mapLessonRow);\n}\n\n// ==================== LESSON APPLICATION/FEEDBACK ====================\n\n/**\n * Record that a lesson was applied (shown to user)\n */\nexport function recordLessonApplication(id: string): void {\n const db = getDatabase();\n const now = new Date().toISOString();\n\n db.prepare(`\n UPDATE lessons\n SET times_applied = times_applied + 1,\n last_applied_at = ?,\n updated_at = ?\n WHERE id = ?\n `).run(now, now, id);\n}\n\n/**\n * Record validation feedback for a lesson\n */\nexport function recordLessonValidation(id: string, sessionId?: string, comment?: string): void {\n const db = getDatabase();\n const now = new Date().toISOString();\n\n const transaction = db.transaction(() => {\n // Update lesson counter\n db.prepare(`\n UPDATE lessons\n SET times_validated = times_validated + 1, updated_at = ?\n WHERE id = ?\n `).run(now, id);\n\n // Record feedback\n db.prepare(`\n INSERT INTO lesson_feedback (lesson_id, session_id, feedback_type, comment)\n VALUES (?, ?, 'validated', ?)\n `).run(id, sessionId ?? null, comment ?? null);\n });\n\n transaction();\n}\n\n/**\n * Record rejection feedback for a lesson\n */\nexport function recordLessonRejection(id: string, sessionId?: string, comment?: string): void {\n const db = getDatabase();\n const now = new Date().toISOString();\n\n const transaction = db.transaction(() => {\n // Update lesson counter\n db.prepare(`\n UPDATE lessons\n SET times_rejected = times_rejected + 1, updated_at = ?\n WHERE id = ?\n `).run(now, id);\n\n // Record feedback\n db.prepare(`\n INSERT INTO lesson_feedback (lesson_id, session_id, feedback_type, comment)\n VALUES (?, ?, 'rejected', ?)\n `).run(id, sessionId ?? null, comment ?? null);\n });\n\n transaction();\n}\n\n/**\n * Get feedback for a lesson\n */\nexport function getLessonFeedback(lessonId: string): LessonFeedback[] {\n const db = getDatabase();\n\n const rows = db.prepare(`\n SELECT\n id,\n lesson_id as lessonId,\n session_id as sessionId,\n feedback_type as feedbackType,\n comment,\n created_at as createdAt\n FROM lesson_feedback\n WHERE lesson_id = ?\n ORDER BY created_at DESC\n `).all(lessonId) as LessonFeedback[];\n\n return rows;\n}\n\n// ==================== LESSON EMBEDDINGS ====================\n\n/**\n * Store embedding for a lesson\n */\nexport function storeLessonEmbedding(lessonId: string, embedding: number[]): void {\n const db = getDatabase();\n\n // Delete existing embedding if any\n db.prepare('DELETE FROM lesson_embeddings WHERE lesson_id = ?').run(lessonId);\n\n // Insert new embedding\n db.prepare(`\n INSERT INTO lesson_embeddings (lesson_id, embedding)\n VALUES (?, ?)\n `).run(lessonId, JSON.stringify(embedding));\n}\n\n/**\n * Lesson with similarity distance (lower is more similar)\n */\nexport interface LessonWithDistance {\n lesson: Lesson;\n distance: number;\n}\n\n/**\n * Search lessons by embedding similarity\n */\nexport function searchLessonsByEmbedding(\n embedding: number[],\n projectPath: string,\n limit: number = 5\n): Lesson[] {\n return searchLessonsByEmbeddingWithDistance(embedding, projectPath, limit)\n .map(r => r.lesson);\n}\n\n/**\n * Search lessons by embedding similarity, returning distance scores\n * Distance is L2 (Euclidean) - lower is more similar\n */\nexport function searchLessonsByEmbeddingWithDistance(\n embedding: number[],\n projectPath: string,\n limit: number = 5\n): LessonWithDistance[] {\n const db = getDatabase();\n\n // Use sqlite-vec for vector similarity search\n const rows = db.prepare(`\n SELECT\n l.*,\n le.distance\n FROM lesson_embeddings le\n JOIN lessons l ON l.id = le.lesson_id\n WHERE l.project_path = ?\n AND l.archived = 0\n AND le.embedding MATCH ?\n ORDER BY le.distance ASC\n LIMIT ?\n `).all(projectPath, JSON.stringify(embedding), limit) as (LessonRow & { distance: number })[];\n\n return rows.map(row => ({\n lesson: mapLessonRow(row),\n distance: row.distance,\n }));\n}\n\n/**\n * Search lessons globally by embedding (across all projects)\n */\nexport function searchAllLessonsByEmbedding(\n embedding: number[],\n limit: number = 10\n): Lesson[] {\n const db = getDatabase();\n\n const rows = db.prepare(`\n SELECT\n l.*,\n le.distance\n FROM lesson_embeddings le\n JOIN lessons l ON l.id = le.lesson_id\n WHERE l.archived = 0\n AND le.embedding MATCH ?\n ORDER BY le.distance ASC\n LIMIT ?\n `).all(JSON.stringify(embedding), limit) as (LessonRow & { distance: number })[];\n\n return rows.map(row => mapLessonRow(row));\n}\n\n// ==================== SYNTHESIS QUEUE ====================\n\n/**\n * Queue a session for synthesis\n */\nexport function queueForSynthesis(sessionId: string, projectPath: string): void {\n const db = getDatabase();\n const now = new Date().toISOString();\n\n db.prepare(`\n INSERT OR REPLACE INTO synthesis_queue (session_id, project_path, queued_at, status)\n VALUES (?, ?, ?, 'pending')\n `).run(sessionId, projectPath, now);\n}\n\n/**\n * Get pending synthesis queue items\n */\nexport function getPendingSynthesis(limit: number = 5): SynthesisQueueItem[] {\n const db = getDatabase();\n\n const rows = db.prepare(`\n SELECT\n id,\n session_id as sessionId,\n project_path as projectPath,\n queued_at as queuedAt,\n status,\n processed_at as processedAt,\n lessons_created as lessonsCreated,\n error\n FROM synthesis_queue\n WHERE status = 'pending'\n ORDER BY queued_at ASC\n LIMIT ?\n `).all(limit) as SynthesisQueueItem[];\n\n return rows;\n}\n\n/**\n * Mark synthesis as in progress\n */\nexport function markSynthesisProcessing(id: number): void {\n const db = getDatabase();\n db.prepare(`\n UPDATE synthesis_queue SET status = 'processing' WHERE id = ?\n `).run(id);\n}\n\n/**\n * Mark synthesis as complete\n */\nexport function markSynthesisComplete(id: number, lessonsCreated: number): void {\n const db = getDatabase();\n const now = new Date().toISOString();\n\n db.prepare(`\n UPDATE synthesis_queue\n SET status = 'completed', processed_at = ?, lessons_created = ?\n WHERE id = ?\n `).run(now, lessonsCreated, id);\n}\n\n/**\n * Mark synthesis as failed\n */\nexport function markSynthesisFailed(id: number, error: string): void {\n const db = getDatabase();\n const now = new Date().toISOString();\n\n db.prepare(`\n UPDATE synthesis_queue\n SET status = 'failed', processed_at = ?, error = ?\n WHERE id = ?\n `).run(now, error, id);\n}\n\n/**\n * Get synthesis queue stats\n */\nexport function getSynthesisStats(): {\n pending: number;\n processing: number;\n completed: number;\n failed: number;\n totalLessonsCreated: number;\n} {\n const db = getDatabase();\n\n const pending = (db.prepare(`\n SELECT COUNT(*) as count FROM synthesis_queue WHERE status = 'pending'\n `).get() as { count: number }).count;\n\n const processing = (db.prepare(`\n SELECT COUNT(*) as count FROM synthesis_queue WHERE status = 'processing'\n `).get() as { count: number }).count;\n\n const completed = (db.prepare(`\n SELECT COUNT(*) as count FROM synthesis_queue WHERE status = 'completed'\n `).get() as { count: number }).count;\n\n const failed = (db.prepare(`\n SELECT COUNT(*) as count FROM synthesis_queue WHERE status = 'failed'\n `).get() as { count: number }).count;\n\n const totalLessonsCreated = (db.prepare(`\n SELECT COALESCE(SUM(lessons_created), 0) as total FROM synthesis_queue WHERE status = 'completed'\n `).get() as { total: number }).total;\n\n return { pending, processing, completed, failed, totalLessonsCreated };\n}\n\n// ==================== LESSON STATS ====================\n\n/**\n * Get lesson statistics\n */\nexport function getLessonStats(): {\n totalLessons: number;\n activeLessons: number;\n archivedLessons: number;\n byCategory: Record<LessonCategory, number>;\n avgConfidence: number;\n} {\n const db = getDatabase();\n\n const totalLessons = (db.prepare(`\n SELECT COUNT(*) as count FROM lessons\n `).get() as { count: number }).count;\n\n const activeLessons = (db.prepare(`\n SELECT COUNT(*) as count FROM lessons WHERE archived = 0\n `).get() as { count: number }).count;\n\n const archivedLessons = totalLessons - activeLessons;\n\n const categoryRows = db.prepare(`\n SELECT category, COUNT(*) as count\n FROM lessons\n WHERE archived = 0\n GROUP BY category\n `).all() as { category: LessonCategory; count: number }[];\n\n const byCategory: Record<LessonCategory, number> = {\n architecture_decision: 0,\n anti_pattern: 0,\n bug_pattern: 0,\n project_convention: 0,\n dependency_knowledge: 0,\n domain_knowledge: 0,\n workflow: 0,\n other: 0,\n };\n\n for (const row of categoryRows) {\n byCategory[row.category] = row.count;\n }\n\n const avgConfidence = (db.prepare(`\n SELECT COALESCE(AVG(confidence), 0) as avg FROM lessons WHERE archived = 0\n `).get() as { avg: number }).avg;\n\n return {\n totalLessons,\n activeLessons,\n archivedLessons,\n byCategory,\n avgConfidence,\n };\n}\n\n/**\n * Get queue status (alias for getSynthesisStats)\n */\nexport function getQueueStatus() {\n return getSynthesisStats();\n}\n\n// ==================== SESSION INJECTION CACHE ====================\n\n/**\n * Get lesson IDs that have already been injected in this session\n */\nexport function getInjectedLessonIds(sessionId: string): Set<string> {\n const db = getDatabase();\n\n const rows = db.prepare(`\n SELECT lesson_id FROM session_injections WHERE session_id = ?\n `).all(sessionId) as { lesson_id: string }[];\n\n return new Set(rows.map(row => row.lesson_id));\n}\n\n/**\n * Record that lessons were injected in a session\n */\nexport function recordInjectedLessons(sessionId: string, lessonIds: string[]): void {\n if (lessonIds.length === 0) return;\n\n const db = getDatabase();\n const now = new Date().toISOString();\n\n const stmt = db.prepare(`\n INSERT OR IGNORE INTO session_injections (session_id, lesson_id, injected_at)\n VALUES (?, ?, ?)\n `);\n\n const transaction = db.transaction(() => {\n for (const lessonId of lessonIds) {\n stmt.run(sessionId, lessonId, now);\n }\n });\n\n transaction();\n}\n\n/**\n * Clear injection cache for a session (e.g., on compaction)\n */\nexport function clearSessionInjections(sessionId: string): void {\n const db = getDatabase();\n db.prepare('DELETE FROM session_injections WHERE session_id = ?').run(sessionId);\n}\n\n/**\n * Clean up old session injection records (sessions older than N days)\n */\nexport function cleanupOldInjections(daysOld: number = 7): number {\n const db = getDatabase();\n const cutoff = new Date();\n cutoff.setDate(cutoff.getDate() - daysOld);\n\n const result = db.prepare(`\n DELETE FROM session_injections WHERE injected_at < ?\n `).run(cutoff.toISOString());\n\n return result.changes;\n}\n\n/**\n * Get distinct project paths from lessons\n */\nexport function getDistinctProjects(): string[] {\n const db = getDatabase();\n\n const rows = db.prepare(`\n SELECT DISTINCT project_path\n FROM lessons\n WHERE project_path IS NOT NULL AND project_path != ''\n ORDER BY project_path ASC\n `).all() as { project_path: string }[];\n\n return rows.map(row => row.project_path);\n}\n","import Database from 'better-sqlite3';\nimport * as sqliteVec from 'sqlite-vec';\nimport { existsSync } from 'fs';\nimport { DB_PATH, ensureCmemDir, EMBEDDING_DIMENSIONS } from '../utils/config.js';\nimport { extractSessionMetadata } from '../parser/index.js';\n\nlet db: Database.Database | null = null;\n\n/**\n * Get or create the database connection\n */\nexport function getDatabase(): Database.Database {\n if (db) return db;\n\n ensureCmemDir();\n\n db = new Database(DB_PATH);\n db.pragma('journal_mode = WAL');\n\n // Load sqlite-vec extension\n sqliteVec.load(db);\n\n // Initialize schema\n initSchema(db);\n\n return db;\n}\n\n/**\n * Initialize database schema\n */\nfunction initSchema(database: Database.Database): void {\n // Migrations tracking table\n database.exec(`\n CREATE TABLE IF NOT EXISTS migrations (\n name TEXT PRIMARY KEY,\n applied_at TEXT NOT NULL\n );\n `);\n\n // Sessions table\n database.exec(`\n CREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n summary TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n message_count INTEGER DEFAULT 0,\n project_path TEXT,\n source_file TEXT,\n raw_data TEXT NOT NULL\n );\n `);\n\n // Add source_file column if it doesn't exist (migration)\n try {\n database.exec(`ALTER TABLE sessions ADD COLUMN source_file TEXT`);\n } catch {\n // Column already exists\n }\n\n // Add is_sidechain column if it doesn't exist (migration)\n try {\n database.exec(`ALTER TABLE sessions ADD COLUMN is_sidechain INTEGER DEFAULT 0`);\n } catch {\n // Column already exists\n }\n\n // Add is_automated column if it doesn't exist (migration)\n try {\n database.exec(`ALTER TABLE sessions ADD COLUMN is_automated INTEGER DEFAULT 0`);\n } catch {\n // Column already exists\n }\n\n // Add custom_title column if it doesn't exist (migration)\n // Used for user-renamed sessions\n try {\n database.exec(`ALTER TABLE sessions ADD COLUMN custom_title TEXT`);\n } catch {\n // Column already exists\n }\n\n // Messages table\n database.exec(`\n CREATE TABLE IF NOT EXISTS messages (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),\n content TEXT NOT NULL,\n timestamp TEXT NOT NULL,\n FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE\n );\n `);\n\n // Embedding state tracking for incremental updates\n database.exec(`\n CREATE TABLE IF NOT EXISTS embedding_state (\n session_id TEXT PRIMARY KEY,\n content_length INTEGER NOT NULL,\n file_mtime TEXT,\n last_embedded_at TEXT NOT NULL,\n FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE\n );\n `);\n\n // Vector embeddings table (768 dimensions for nomic-embed-text)\n database.exec(`\n CREATE VIRTUAL TABLE IF NOT EXISTS session_embeddings USING vec0(\n session_id TEXT PRIMARY KEY,\n embedding FLOAT[${EMBEDDING_DIMENSIONS}]\n );\n `);\n\n // Favorites table for starred sessions\n database.exec(`\n CREATE TABLE IF NOT EXISTS favorites (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n type TEXT NOT NULL CHECK (type IN ('session', 'folder')),\n value TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE(type, value)\n );\n `);\n\n // Project order table for manual sorting\n database.exec(`\n CREATE TABLE IF NOT EXISTS project_order (\n path TEXT PRIMARY KEY,\n sort_order INTEGER NOT NULL,\n updated_at TEXT NOT NULL\n );\n `);\n\n // Indexes for sessions\n database.exec(`\n CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);\n `);\n database.exec(`\n CREATE INDEX IF NOT EXISTS idx_sessions_updated ON sessions(updated_at DESC);\n `);\n database.exec(`\n CREATE INDEX IF NOT EXISTS idx_sessions_source ON sessions(source_file);\n `);\n database.exec(`\n CREATE INDEX IF NOT EXISTS idx_favorites_type ON favorites(type);\n `);\n\n // ==================== LESSONS TABLES ====================\n\n // Lessons table\n database.exec(`\n CREATE TABLE IF NOT EXISTS lessons (\n id TEXT PRIMARY KEY,\n project_path TEXT NOT NULL,\n category TEXT NOT NULL CHECK (category IN (\n 'architecture_decision', 'anti_pattern', 'bug_pattern',\n 'project_convention', 'dependency_knowledge', 'domain_knowledge',\n 'workflow', 'other'\n )),\n title TEXT NOT NULL,\n trigger_context TEXT NOT NULL,\n insight TEXT NOT NULL,\n reasoning TEXT,\n confidence REAL DEFAULT 0.5,\n times_applied INTEGER DEFAULT 0,\n times_validated INTEGER DEFAULT 0,\n times_rejected INTEGER DEFAULT 0,\n source_session_id TEXT,\n source_type TEXT DEFAULT 'synthesized',\n archived INTEGER DEFAULT 0,\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now')),\n last_applied_at TEXT\n );\n `);\n\n // Lesson embeddings\n database.exec(`\n CREATE VIRTUAL TABLE IF NOT EXISTS lesson_embeddings USING vec0(\n lesson_id TEXT PRIMARY KEY,\n embedding FLOAT[${EMBEDDING_DIMENSIONS}]\n );\n `);\n\n // Lesson feedback\n database.exec(`\n CREATE TABLE IF NOT EXISTS lesson_feedback (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n lesson_id TEXT NOT NULL,\n session_id TEXT,\n feedback_type TEXT NOT NULL CHECK (feedback_type IN (\n 'validated', 'rejected', 'modified'\n )),\n comment TEXT,\n created_at TEXT DEFAULT (datetime('now')),\n FOREIGN KEY (lesson_id) REFERENCES lessons(id) ON DELETE CASCADE\n );\n `);\n\n // Synthesis queue\n database.exec(`\n CREATE TABLE IF NOT EXISTS synthesis_queue (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL UNIQUE,\n project_path TEXT NOT NULL,\n queued_at TEXT DEFAULT (datetime('now')),\n status TEXT DEFAULT 'pending',\n processed_at TEXT,\n lessons_created INTEGER DEFAULT 0,\n error TEXT\n );\n `);\n\n // Session injection cache - tracks which lessons have been injected per Claude session\n // Prevents re-injecting the same lessons in a conversation\n database.exec(`\n CREATE TABLE IF NOT EXISTS session_injections (\n session_id TEXT NOT NULL,\n lesson_id TEXT NOT NULL,\n injected_at TEXT DEFAULT (datetime('now')),\n PRIMARY KEY (session_id, lesson_id)\n );\n `);\n\n // Indexes for lessons\n database.exec(`\n CREATE INDEX IF NOT EXISTS idx_lessons_project ON lessons(project_path);\n `);\n database.exec(`\n CREATE INDEX IF NOT EXISTS idx_lessons_category ON lessons(category);\n `);\n database.exec(`\n CREATE INDEX IF NOT EXISTS idx_lessons_confidence ON lessons(confidence DESC);\n `);\n database.exec(`\n CREATE INDEX IF NOT EXISTS idx_lessons_archived ON lessons(archived);\n `);\n database.exec(`\n CREATE INDEX IF NOT EXISTS idx_synthesis_queue_status ON synthesis_queue(status);\n `);\n database.exec(`\n CREATE INDEX IF NOT EXISTS idx_session_injections_session ON session_injections(session_id);\n `);\n\n // Run data migrations\n runMigrations(database);\n}\n\n/**\n * Run data migrations (separate from schema migrations)\n */\nfunction runMigrations(database: Database.Database): void {\n // Migration: Populate is_sidechain and is_automated from JSONL metadata\n const migrationName = 'populate_session_metadata_v1';\n\n const existing = database.prepare(\n 'SELECT 1 FROM migrations WHERE name = ?'\n ).get(migrationName);\n\n if (existing) return; // Already applied\n\n // Get all sessions with source files\n const sessions = database.prepare(`\n SELECT id, source_file FROM sessions WHERE source_file IS NOT NULL\n `).all() as { id: string; source_file: string }[];\n\n const updateStmt = database.prepare(`\n UPDATE sessions SET is_sidechain = ?, is_automated = ? WHERE id = ?\n `);\n\n const transaction = database.transaction(() => {\n for (const session of sessions) {\n if (!existsSync(session.source_file)) continue;\n\n try {\n const metadata = extractSessionMetadata(session.source_file);\n const isAutomated = metadata.isSidechain || metadata.isMeta;\n updateStmt.run(\n metadata.isSidechain ? 1 : 0,\n isAutomated ? 1 : 0,\n session.id\n );\n } catch {\n // Skip files that can't be parsed\n }\n }\n\n // Mark migration as complete\n database.prepare(\n 'INSERT INTO migrations (name, applied_at) VALUES (?, ?)'\n ).run(migrationName, new Date().toISOString());\n });\n\n transaction();\n}\n\n/**\n * Close the database connection\n */\nexport function closeDatabase(): void {\n if (db) {\n db.close();\n db = null;\n }\n}\n","import { readFileSync, readdirSync, existsSync, statSync } from 'fs';\nimport { join, basename, dirname } from 'path';\nimport { CLAUDE_PROJECTS_DIR, CLAUDE_SESSIONS_DIR } from '../utils/config.js';\n\nexport interface ParsedMessage {\n role: 'user' | 'assistant';\n content: string;\n timestamp: string;\n}\n\nexport interface SessionMetadata {\n isSidechain: boolean;\n isMeta: boolean;\n}\n\n// Patterns that indicate automated/system sessions based on title\nconst AUTOMATED_TITLE_PATTERNS = [\n /^<[a-z-]+>/i, // XML-like tags: <project-instructions>, <command-message>, etc.\n /^<[A-Z_]+>/, // Uppercase tags: <SYSTEM>, <TOOL_USE>, etc.\n /^\\[system\\]/i, // [system] prefix\n /^\\/[a-z]+$/i, // Slash commands: /init, /help, etc.\n];\n\n/**\n * Check if a title/first message indicates an automated session\n */\nexport function isAutomatedByContent(title: string): boolean {\n for (const pattern of AUTOMATED_TITLE_PATTERNS) {\n if (pattern.test(title.trim())) {\n return true;\n }\n }\n return false;\n}\n\nexport interface ParsedSession {\n filePath: string;\n projectPath: string | null;\n messages: ParsedMessage[];\n rawData: string;\n modifiedAt: Date;\n agentMessages?: ParsedMessage[]; // Messages from subagents linked to this session\n}\n\ninterface SessionIndexEntry {\n sessionId: string;\n fullPath: string;\n fileMtime: number;\n firstPrompt: string;\n messageCount: number;\n created: string;\n modified: string;\n gitBranch?: string;\n projectPath: string;\n isSidechain: boolean;\n}\n\ninterface SessionsIndex {\n version: number;\n entries: SessionIndexEntry[];\n}\n\n/**\n * Extract session metadata from JSONL file (isSidechain, agentId, isMeta, etc.)\n * Only uses reliable JSONL metadata fields, not content-based heuristics\n */\nexport function extractSessionMetadata(filepath: string): SessionMetadata {\n const content = readFileSync(filepath, 'utf-8');\n\n const metadata: SessionMetadata = {\n isSidechain: false,\n isMeta: false,\n };\n\n // Find the first user message to check metadata\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n\n try {\n const parsed = JSON.parse(line);\n\n // Look for the first user message\n if (parsed.type === 'user' && parsed.message) {\n // Check isSidechain field (present in all Claude Code JSONL entries)\n // This is true for subagent/automated sessions\n if (parsed.isSidechain === true) {\n metadata.isSidechain = true;\n }\n\n // Check if this has an agentId (subagent session)\n if (parsed.agentId) {\n metadata.isSidechain = true;\n }\n\n // Check isMeta field - true for local command sessions\n if (parsed.isMeta === true) {\n metadata.isMeta = true;\n }\n\n break; // Only need first user message\n }\n } catch {\n // Skip malformed lines\n }\n }\n\n return metadata;\n}\n\n/**\n * Parse a Claude Code session JSONL file\n */\nexport function parseSessionFile(filepath: string): ParsedMessage[] {\n const content = readFileSync(filepath, 'utf-8');\n const messages: ParsedMessage[] = [];\n\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n\n try {\n const parsed = JSON.parse(line);\n\n // Handle different Claude Code JSONL formats\n if ((parsed.type === 'user' || parsed.type === 'assistant') && parsed.message) {\n // Claude Code format: type is 'user'/'assistant', message contains the actual content\n const msg = parsed.message;\n if (msg.role && msg.content) {\n const content = Array.isArray(msg.content)\n ? msg.content\n .filter((c: { type: string }) => c.type === 'text')\n .map((c: { text: string }) => c.text)\n .join('\\n')\n : typeof msg.content === 'string'\n ? msg.content\n : JSON.stringify(msg.content);\n\n if (content) {\n messages.push({\n role: msg.role as 'user' | 'assistant',\n content,\n timestamp: parsed.timestamp || new Date().toISOString(),\n });\n }\n }\n } else if (parsed.type === 'message' && parsed.role && parsed.content) {\n // Standard message format\n messages.push({\n role: parsed.role as 'user' | 'assistant',\n content: typeof parsed.content === 'string'\n ? parsed.content\n : JSON.stringify(parsed.content),\n timestamp: parsed.timestamp || new Date().toISOString(),\n });\n } else if (parsed.role && parsed.content && !parsed.type) {\n // Simplified format without type field\n messages.push({\n role: parsed.role as 'user' | 'assistant',\n content: typeof parsed.content === 'string'\n ? parsed.content\n : JSON.stringify(parsed.content),\n timestamp: parsed.timestamp || new Date().toISOString(),\n });\n }\n // Skip tool_use, tool_result, and other non-message entries\n } catch {\n // Skip malformed lines\n }\n }\n\n return messages;\n}\n\n/**\n * Find all Claude Code session files (excluding subagent sessions)\n */\nexport function findSessionFiles(): ParsedSession[] {\n const sessions: ParsedSession[] = [];\n\n // Search in ~/.claude/projects/<hash>/\n if (existsSync(CLAUDE_PROJECTS_DIR)) {\n const projectDirs = readdirSync(CLAUDE_PROJECTS_DIR);\n for (const projectDir of projectDirs) {\n const projectDirPath = join(CLAUDE_PROJECTS_DIR, projectDir);\n const stat = statSync(projectDirPath);\n if (!stat.isDirectory()) continue;\n\n // Try to read sessions-index.json for metadata\n const indexPath = join(projectDirPath, 'sessions-index.json');\n const sessionIndex = loadSessionIndex(indexPath);\n\n // Build a map of session IDs to their metadata\n const indexedSessions = new Map<string, SessionIndexEntry>();\n if (sessionIndex) {\n for (const entry of sessionIndex.entries) {\n indexedSessions.set(entry.sessionId, entry);\n }\n }\n\n // Only load .jsonl files directly in the project directory (not in subfolders)\n const entries = readdirSync(projectDirPath, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith('.jsonl')) continue;\n\n const filePath = join(projectDirPath, entry.name);\n const sessionId = entry.name.replace('.jsonl', '');\n\n try {\n const session = loadSession(filePath);\n if (session) {\n // Use metadata from sessions-index.json if available\n const indexEntry = indexedSessions.get(sessionId);\n if (indexEntry) {\n session.projectPath = indexEntry.projectPath;\n }\n\n // Load any subagent messages for this session\n const agentMessages = loadSubagentMessages(projectDirPath, sessionId);\n if (agentMessages.length > 0) {\n session.agentMessages = agentMessages;\n }\n\n sessions.push(session);\n }\n } catch {\n // Skip unreadable files\n }\n }\n }\n }\n\n // Search in ~/.claude/sessions/ (no subagents here)\n if (existsSync(CLAUDE_SESSIONS_DIR)) {\n const sessionFiles = readdirSync(CLAUDE_SESSIONS_DIR).filter(f => f.endsWith('.jsonl'));\n for (const sessionFile of sessionFiles) {\n const filePath = join(CLAUDE_SESSIONS_DIR, sessionFile);\n try {\n const session = loadSession(filePath);\n if (session) {\n sessions.push(session);\n }\n } catch {\n // Skip unreadable files\n }\n }\n }\n\n // Sort by modification time (newest first)\n sessions.sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime());\n\n return sessions;\n}\n\n/**\n * Load sessions-index.json if it exists\n */\nfunction loadSessionIndex(indexPath: string): SessionsIndex | null {\n if (!existsSync(indexPath)) return null;\n\n try {\n const content = readFileSync(indexPath, 'utf-8');\n return JSON.parse(content) as SessionsIndex;\n } catch {\n return null;\n }\n}\n\n/**\n * Load messages from all subagent sessions for a parent session\n */\nfunction loadSubagentMessages(projectDirPath: string, parentSessionId: string): ParsedMessage[] {\n const subagentsDir = join(projectDirPath, parentSessionId, 'subagents');\n if (!existsSync(subagentsDir)) return [];\n\n const messages: ParsedMessage[] = [];\n\n try {\n const agentFiles = readdirSync(subagentsDir).filter(f => f.endsWith('.jsonl'));\n for (const agentFile of agentFiles) {\n const agentPath = join(subagentsDir, agentFile);\n const agentMessages = parseSessionFile(agentPath);\n messages.push(...agentMessages);\n }\n } catch {\n // Ignore errors reading subagent files\n }\n\n return messages;\n}\n\n/**\n * Load a session from a file path\n */\nfunction loadSession(filePath: string): ParsedSession | null {\n const rawData = readFileSync(filePath, 'utf-8');\n const messages = parseSessionFile(filePath);\n\n // Skip empty sessions\n if (messages.length === 0) {\n return null;\n }\n\n const stats = statSync(filePath);\n\n return {\n filePath,\n projectPath: null,\n messages,\n rawData,\n modifiedAt: stats.mtime,\n };\n}\n\n\n/**\n * Get the most recent session file\n */\nexport function getMostRecentSession(): ParsedSession | null {\n const sessions = findSessionFiles();\n return sessions.length > 0 ? sessions[0] : null;\n}\n\n/**\n * Generate a summary from session messages\n */\nexport function generateSummary(messages: ParsedMessage[]): string {\n // Take first user message as the main topic\n const firstUserMessage = messages.find(m => m.role === 'user');\n if (!firstUserMessage) {\n return 'No user messages found';\n }\n\n // Truncate to reasonable summary length\n const summary = firstUserMessage.content.slice(0, 300);\n return summary.length < firstUserMessage.content.length ? summary + '...' : summary;\n}\n","/**\n * LessonManager\n *\n * Business logic layer for managing lessons.\n * Handles confidence adjustments, archiving, and decay.\n */\n\nimport {\n createLesson,\n updateLesson,\n deleteLesson,\n archiveLesson,\n unarchiveLesson,\n getLesson,\n getLessonsByProject,\n getCoreLessons,\n getAllLessons,\n recordLessonApplication,\n recordLessonValidation,\n recordLessonRejection,\n storeLessonEmbedding,\n} from '../db/lessons.js';\nimport { getEmbedding } from '../embeddings/index.js';\nimport type {\n Lesson,\n CreateLessonInput,\n UpdateLessonInput,\n LessonSearchOptions,\n} from './types.js';\n\n// Confidence adjustment constants\nconst VALIDATION_BOOST = 0.1;\nconst REJECTION_PENALTY = 0.2;\nconst MIN_CONFIDENCE = 0.0;\nconst MAX_CONFIDENCE = 1.0;\nconst AUTO_ARCHIVE_THRESHOLD = 0.1;\nconst DECAY_RATE = 0.05;\n\nexport class LessonManager {\n /**\n * Create a new lesson and generate its embedding\n */\n async create(input: CreateLessonInput): Promise<Lesson> {\n // Create the lesson\n const lesson = createLesson(input);\n\n // Generate and store embedding\n try {\n const embeddingText = this.buildEmbeddingText(lesson);\n const embedding = await getEmbedding(embeddingText);\n storeLessonEmbedding(lesson.id, embedding);\n } catch {\n // Embedding failed, lesson is still usable\n }\n\n return lesson;\n }\n\n /**\n * Update a lesson\n */\n update(id: string, updates: UpdateLessonInput): Lesson | null {\n return updateLesson(id, updates);\n }\n\n /**\n * Update a lesson and regenerate its embedding\n */\n async updateWithEmbedding(id: string, updates: UpdateLessonInput): Promise<Lesson | null> {\n const lesson = updateLesson(id, updates);\n\n if (lesson) {\n // Regenerate embedding if content changed\n if (updates.title || updates.triggerContext || updates.insight) {\n try {\n const embeddingText = this.buildEmbeddingText(lesson);\n const embedding = await getEmbedding(embeddingText);\n storeLessonEmbedding(lesson.id, embedding);\n } catch {\n // Embedding failed, continue\n }\n }\n }\n\n return lesson;\n }\n\n /**\n * Delete a lesson permanently\n */\n delete(id: string): boolean {\n return deleteLesson(id);\n }\n\n /**\n * Archive a lesson (soft delete)\n */\n archive(id: string): void {\n archiveLesson(id);\n }\n\n /**\n * Unarchive a lesson\n */\n unarchive(id: string): void {\n unarchiveLesson(id);\n }\n\n /**\n * Get a lesson by ID\n */\n get(id: string): Lesson | null {\n return getLesson(id);\n }\n\n /**\n * Get lessons for a project\n */\n getByProject(projectPath: string, options?: LessonSearchOptions): Lesson[] {\n return getLessonsByProject(projectPath, options);\n }\n\n /**\n * Get core lessons (high confidence, well-validated)\n */\n getCore(projectPath: string, limit?: number): Lesson[] {\n return getCoreLessons(projectPath, limit);\n }\n\n /**\n * Get all lessons\n */\n getAll(options?: LessonSearchOptions): Lesson[] {\n return getAllLessons(options);\n }\n\n /**\n * Record that a lesson was applied (shown to user)\n */\n recordApplication(id: string): void {\n recordLessonApplication(id);\n }\n\n /**\n * Record validation feedback - boosts confidence\n */\n recordValidation(id: string, sessionId?: string, comment?: string): void {\n recordLessonValidation(id, sessionId, comment);\n\n // Boost confidence\n const lesson = getLesson(id);\n if (lesson) {\n const newConfidence = Math.min(MAX_CONFIDENCE, lesson.confidence + VALIDATION_BOOST);\n updateLesson(id, { confidence: newConfidence });\n }\n }\n\n /**\n * Record rejection feedback - reduces confidence\n * Auto-archives if confidence drops too low\n */\n recordRejection(id: string, sessionId?: string, comment?: string): void {\n recordLessonRejection(id, sessionId, comment);\n\n // Reduce confidence\n const lesson = getLesson(id);\n if (lesson) {\n const newConfidence = Math.max(MIN_CONFIDENCE, lesson.confidence - REJECTION_PENALTY);\n\n if (newConfidence < AUTO_ARCHIVE_THRESHOLD) {\n // Auto-archive very low confidence lessons\n archiveLesson(id);\n } else {\n updateLesson(id, { confidence: newConfidence });\n }\n }\n }\n\n /**\n * Decay confidence of unused lessons\n * Should be run periodically (e.g., weekly)\n */\n decayUnusedLessons(daysThreshold: number = 30): number {\n const lessons = getAllLessons({ archived: false });\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - daysThreshold);\n\n let decayedCount = 0;\n\n for (const lesson of lessons) {\n const lastUsed = lesson.lastAppliedAt\n ? new Date(lesson.lastAppliedAt)\n : new Date(lesson.createdAt);\n\n if (lastUsed < cutoffDate) {\n const newConfidence = Math.max(MIN_CONFIDENCE, lesson.confidence - DECAY_RATE);\n\n if (newConfidence < AUTO_ARCHIVE_THRESHOLD) {\n archiveLesson(lesson.id);\n } else {\n updateLesson(lesson.id, { confidence: newConfidence });\n }\n\n decayedCount++;\n }\n }\n\n return decayedCount;\n }\n\n /**\n * Build text for embedding generation\n */\n private buildEmbeddingText(lesson: Lesson): string {\n const parts = [\n `Title: ${lesson.title}`,\n `Category: ${lesson.category}`,\n `When to apply: ${lesson.triggerContext}`,\n `Insight: ${lesson.insight}`,\n ];\n\n if (lesson.reasoning) {\n parts.push(`Reasoning: ${lesson.reasoning}`);\n }\n\n return parts.join('\\n\\n');\n }\n}\n\n// Export a singleton instance\nexport const lessonManager = new LessonManager();\n","/**\n * Learning Module Types\n *\n * Type definitions for the lesson learning system.\n */\n\nexport type LessonCategory =\n | 'architecture_decision'\n | 'anti_pattern'\n | 'bug_pattern'\n | 'project_convention'\n | 'dependency_knowledge'\n | 'domain_knowledge'\n | 'workflow'\n | 'other';\n\nexport type LessonSourceType = 'synthesized' | 'manual' | 'imported';\n\nexport type FeedbackType = 'validated' | 'rejected' | 'modified';\n\nexport interface Lesson {\n id: string;\n projectPath: string;\n category: LessonCategory;\n title: string;\n triggerContext: string;\n insight: string;\n reasoning?: string;\n confidence: number;\n timesApplied: number;\n timesValidated: number;\n timesRejected: number;\n sourceSessionId?: string;\n sourceType: LessonSourceType;\n archived: boolean;\n createdAt: string;\n updatedAt: string;\n lastAppliedAt?: string;\n}\n\nexport interface CreateLessonInput {\n projectPath: string;\n category: LessonCategory;\n title: string;\n triggerContext: string;\n insight: string;\n reasoning?: string;\n confidence?: number;\n sourceSessionId?: string;\n sourceType?: LessonSourceType;\n}\n\nexport interface UpdateLessonInput {\n category?: LessonCategory;\n title?: string;\n triggerContext?: string;\n insight?: string;\n reasoning?: string;\n confidence?: number;\n archived?: boolean;\n}\n\nexport interface LessonFeedback {\n id: number;\n lessonId: string;\n sessionId?: string;\n feedbackType: FeedbackType;\n comment?: string;\n createdAt: string;\n}\n\nexport interface SynthesisQueueItem {\n id: number;\n sessionId: string;\n projectPath: string;\n queuedAt: string;\n status: 'pending' | 'processing' | 'completed' | 'failed';\n processedAt?: string;\n lessonsCreated: number;\n error?: string;\n}\n\nexport interface LessonSearchOptions {\n category?: LessonCategory;\n archived?: boolean;\n minConfidence?: number;\n limit?: number;\n}\n\nexport interface ConsultOptions {\n maxSemantic?: number;\n maxCore?: number;\n}\n\nexport interface ConsultResult {\n lessons: Lesson[];\n formattedContext: string;\n}\n\n/**\n * Raw lesson extracted from synthesis (before deduplication)\n */\nexport interface RawLesson {\n category: LessonCategory;\n title: string;\n triggerContext: string;\n insight: string;\n reasoning?: string;\n confidence: number;\n}\n\n/**\n * Category display names for UI\n */\nexport const LESSON_CATEGORY_NAMES: Record<LessonCategory, string> = {\n architecture_decision: 'Architecture Decision',\n anti_pattern: 'Anti-Pattern',\n bug_pattern: 'Bug Pattern',\n project_convention: 'Project Convention',\n dependency_knowledge: 'Dependency Knowledge',\n domain_knowledge: 'Domain Knowledge',\n workflow: 'Workflow',\n other: 'Other',\n};\n\n/**\n * Valid categories for validation\n */\nexport const VALID_CATEGORIES: LessonCategory[] = [\n 'architecture_decision',\n 'anti_pattern',\n 'bug_pattern',\n 'project_convention',\n 'dependency_knowledge',\n 'domain_knowledge',\n 'workflow',\n 'other',\n];\n","/**\n * LessonConsultant\n *\n * Responsible for finding relevant lessons and formatting them for injection.\n * Called by the consult hook to provide context before Claude thinks.\n *\n * Features:\n * - Similarity threshold: Only inject highly relevant lessons\n * - Session deduplication: Don't re-inject lessons already in context\n */\n\nimport { getEmbedding, isReady, initializeEmbeddings } from '../embeddings/index.js';\nimport {\n searchLessonsByEmbeddingWithDistance,\n getCoreLessons,\n getInjectedLessonIds,\n recordInjectedLessons,\n} from '../db/lessons.js';\nimport { lessonManager } from './LessonManager.js';\nimport type {\n Lesson,\n LessonCategory,\n ConsultOptions,\n ConsultResult,\n} from './types.js';\nimport { LESSON_CATEGORY_NAMES } from './types.js';\n\n// Default limits\nconst DEFAULT_MAX_SEMANTIC = 5;\nconst DEFAULT_MAX_CORE = 3;\nconst MAX_TOTAL_LESSONS = 8;\n\n// Similarity thresholds (L2 distance - lower is more similar)\n// Typical L2 distances for nomic-embed-text:\n// - Very similar: < 0.5\n// - Similar: 0.5 - 1.0\n// - Somewhat related: 1.0 - 1.5\n// - Unrelated: > 1.5\nconst SIMILARITY_THRESHOLD = 1.2; // Only include lessons with distance below this\nconst MIN_CONFIDENCE = 0.3; // Skip very low confidence lessons\n\nexport class LessonConsultant {\n /**\n * Consult lessons based on a prompt\n * Returns relevant lessons and formatted context for injection\n *\n * @param prompt - The user's prompt\n * @param projectPath - Current project path\n * @param options - Consultation options\n * @param sessionId - Claude session ID for deduplication (optional)\n */\n async consult(\n prompt: string,\n projectPath: string,\n options?: ConsultOptions,\n sessionId?: string\n ): Promise<ConsultResult> {\n const maxSemantic = options?.maxSemantic ?? DEFAULT_MAX_SEMANTIC;\n const maxCore = options?.maxCore ?? DEFAULT_MAX_CORE;\n\n // Get already-injected lessons for this session\n const alreadyInjected = sessionId ? getInjectedLessonIds(sessionId) : new Set<string>();\n\n // Ensure embeddings are ready\n if (!isReady()) {\n try {\n await initializeEmbeddings();\n } catch {\n // If embeddings fail, return core lessons only (filtered)\n const coreLessons = this.filterLessons(\n getCoreLessons(projectPath, maxCore),\n alreadyInjected\n );\n return this.buildResult(coreLessons, sessionId);\n }\n }\n\n // 1. Semantic search for relevant lessons with distance scores\n let semanticLessons: Lesson[] = [];\n try {\n const promptEmbedding = await getEmbedding(prompt);\n const results = searchLessonsByEmbeddingWithDistance(\n promptEmbedding,\n projectPath,\n maxSemantic * 2 // Fetch extra since we'll filter\n );\n\n // Filter by similarity threshold\n semanticLessons = results\n .filter(r => r.distance < SIMILARITY_THRESHOLD)\n .filter(r => r.lesson.confidence >= MIN_CONFIDENCE)\n .map(r => r.lesson);\n } catch {\n // Embedding search failed, continue with core lessons\n }\n\n // 2. Get core lessons (high confidence, frequently validated)\n const coreLessons = getCoreLessons(projectPath, maxCore);\n\n // 3. Merge and dedupe\n const allLessons = this.mergeAndRank([...semanticLessons, ...coreLessons]);\n\n // 4. Filter out already-injected lessons\n const newLessons = this.filterLessons(allLessons, alreadyInjected);\n\n // 5. Limit total\n const finalLessons = newLessons.slice(0, MAX_TOTAL_LESSONS);\n\n // 6. Record applications\n for (const lesson of finalLessons) {\n lessonManager.recordApplication(lesson.id);\n }\n\n return this.buildResult(finalLessons, sessionId);\n }\n\n /**\n * Quick consult without embeddings - just return core lessons\n */\n consultCore(\n projectPath: string,\n limit?: number,\n sessionId?: string\n ): ConsultResult {\n const alreadyInjected = sessionId ? getInjectedLessonIds(sessionId) : new Set<string>();\n\n const coreLessons = this.filterLessons(\n getCoreLessons(projectPath, limit ?? DEFAULT_MAX_CORE),\n alreadyInjected\n );\n\n // Record applications\n for (const lesson of coreLessons) {\n lessonManager.recordApplication(lesson.id);\n }\n\n return this.buildResult(coreLessons, sessionId);\n }\n\n /**\n * Filter out already-injected lessons\n */\n private filterLessons(lessons: Lesson[], alreadyInjected: Set<string>): Lesson[] {\n if (alreadyInjected.size === 0) {\n return lessons;\n }\n\n return lessons.filter(lesson => !alreadyInjected.has(lesson.id));\n }\n\n /**\n * Merge lessons and remove duplicates, ranking by relevance\n */\n private mergeAndRank(lessons: Lesson[]): Lesson[] {\n const seen = new Set<string>();\n const unique: Lesson[] = [];\n\n for (const lesson of lessons) {\n if (!seen.has(lesson.id)) {\n seen.add(lesson.id);\n unique.push(lesson);\n }\n }\n\n // Sort by confidence * validation score\n return unique.sort((a, b) => {\n const scoreA = a.confidence * (1 + a.timesValidated * 0.1);\n const scoreB = b.confidence * (1 + b.timesValidated * 0.1);\n return scoreB - scoreA;\n });\n }\n\n /**\n * Build the consult result with formatted context\n * Also records injections for session deduplication\n */\n private buildResult(lessons: Lesson[], sessionId?: string): ConsultResult {\n const formattedContext = this.formatForInjection(lessons);\n\n // Record which lessons were injected for this session\n if (sessionId && lessons.length > 0) {\n recordInjectedLessons(sessionId, lessons.map(l => l.id));\n }\n\n return { lessons, formattedContext };\n }\n\n /**\n * Format lessons for injection into Claude's context\n */\n private formatForInjection(lessons: Lesson[]): string {\n if (lessons.length === 0) {\n return '';\n }\n\n const lines: string[] = [\n '<project_knowledge>',\n 'The following is established knowledge about this project.',\n '',\n ];\n\n // Group by category\n const byCategory = this.groupByCategory(lessons);\n\n for (const [category, categoryLessons] of Object.entries(byCategory)) {\n if (categoryLessons.length === 0) continue;\n\n const categoryName = LESSON_CATEGORY_NAMES[category as LessonCategory] || category;\n lines.push(`## ${categoryName}`);\n lines.push('');\n\n for (const lesson of categoryLessons) {\n lines.push(`### ${lesson.title}`);\n lines.push(lesson.insight);\n\n if (lesson.reasoning) {\n lines.push(`_Reason: ${lesson.reasoning}_`);\n }\n\n // Add confidence indicator for transparency\n if (lesson.confidence >= 0.8) {\n lines.push(`_(High confidence - validated ${lesson.timesValidated} times)_`);\n }\n\n lines.push('');\n }\n }\n\n lines.push('</project_knowledge>');\n\n return lines.join('\\n');\n }\n\n /**\n * Group lessons by category\n */\n private groupByCategory(lessons: Lesson[]): Record<LessonCategory, Lesson[]> {\n const groups: Record<LessonCategory, Lesson[]> = {\n architecture_decision: [],\n anti_pattern: [],\n bug_pattern: [],\n project_convention: [],\n dependency_knowledge: [],\n domain_knowledge: [],\n workflow: [],\n other: [],\n };\n\n for (const lesson of lessons) {\n groups[lesson.category].push(lesson);\n }\n\n return groups;\n }\n}\n\n// Export a singleton instance\nexport const lessonConsultant = new LessonConsultant();\n","/**\n * Lesson Consult Hook\n *\n * Called on UserPromptSubmit to inject relevant lessons before Claude thinks.\n * Searches for lessons relevant to the current prompt and formats them\n * as <project_knowledge> context for injection.\n */\n\nimport { lessonConsultant } from '../learning/LessonConsultant.js';\n\ninterface HookInput {\n prompt: string;\n cwd: string;\n session_id?: string;\n}\n\n\n/**\n * Read stdin as JSON\n */\nasync function readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let data = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('readable', () => {\n let chunk;\n while ((chunk = process.stdin.read()) !== null) {\n data += chunk;\n }\n });\n process.stdin.on('end', () => {\n resolve(data);\n });\n // If no stdin data after timeout, resolve with empty\n setTimeout(() => resolve(data), 100);\n });\n}\n\nasync function main() {\n try {\n const stdinData = await readStdin();\n if (!stdinData) {\n process.exit(0);\n }\n\n const input: HookInput = JSON.parse(stdinData);\n\n // Skip if no prompt or cwd\n if (!input.prompt || !input.cwd) {\n process.exit(0);\n }\n\n // Skip very short prompts (likely not worth consulting)\n if (input.prompt.length < 10) {\n process.exit(0);\n }\n\n // Consult lessons (pass session_id for deduplication)\n const { formattedContext } = await lessonConsultant.consult(\n input.prompt,\n input.cwd,\n {\n maxSemantic: 5,\n maxCore: 3,\n },\n input.session_id // Session ID for deduplication - don't re-inject same lessons\n );\n\n // Output context directly as plain text - Claude Code will inject it\n if (formattedContext) {\n console.log(formattedContext);\n }\n\n process.exit(0);\n } catch (error) {\n // Log error to stderr but don't block Claude\n console.error('Consult hook error:', error);\n process.exit(0);\n }\n}\n\nmain();\n"],"mappings":"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","names":["existsSync","join","join","existsSync","existsSync","existsSync","existsSync","db","db","db","db","MIN_CONFIDENCE","coreLessons"]}