@stackmemoryai/stackmemory 0.5.0 → 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.
Files changed (51) hide show
  1. package/dist/cli/commands/config.js +81 -0
  2. package/dist/cli/commands/config.js.map +2 -2
  3. package/dist/cli/commands/decision.js +262 -0
  4. package/dist/cli/commands/decision.js.map +7 -0
  5. package/dist/cli/commands/handoff.js +87 -24
  6. package/dist/cli/commands/handoff.js.map +3 -3
  7. package/dist/cli/commands/service.js +684 -0
  8. package/dist/cli/commands/service.js.map +7 -0
  9. package/dist/cli/commands/sweep.js +311 -0
  10. package/dist/cli/commands/sweep.js.map +7 -0
  11. package/dist/cli/index.js +98 -4
  12. package/dist/cli/index.js.map +2 -2
  13. package/dist/cli/streamlined-cli.js +144 -0
  14. package/dist/cli/streamlined-cli.js.map +7 -0
  15. package/dist/core/config/storage-config.js +111 -0
  16. package/dist/core/config/storage-config.js.map +7 -0
  17. package/dist/core/events/event-bus.js +110 -0
  18. package/dist/core/events/event-bus.js.map +7 -0
  19. package/dist/core/plugins/plugin-interface.js +87 -0
  20. package/dist/core/plugins/plugin-interface.js.map +7 -0
  21. package/dist/core/session/enhanced-handoff.js +654 -0
  22. package/dist/core/session/enhanced-handoff.js.map +7 -0
  23. package/dist/core/storage/simplified-storage.js +328 -0
  24. package/dist/core/storage/simplified-storage.js.map +7 -0
  25. package/dist/daemon/session-daemon.js +308 -0
  26. package/dist/daemon/session-daemon.js.map +7 -0
  27. package/dist/plugins/linear/index.js +166 -0
  28. package/dist/plugins/linear/index.js.map +7 -0
  29. package/dist/plugins/loader.js +57 -0
  30. package/dist/plugins/loader.js.map +7 -0
  31. package/dist/plugins/plugin-interface.js +67 -0
  32. package/dist/plugins/plugin-interface.js.map +7 -0
  33. package/dist/plugins/ralph/simple-ralph-plugin.js +305 -0
  34. package/dist/plugins/ralph/simple-ralph-plugin.js.map +7 -0
  35. package/dist/plugins/ralph/use-cases/code-generator.js +151 -0
  36. package/dist/plugins/ralph/use-cases/code-generator.js.map +7 -0
  37. package/dist/plugins/ralph/use-cases/test-generator.js +201 -0
  38. package/dist/plugins/ralph/use-cases/test-generator.js.map +7 -0
  39. package/dist/skills/repo-ingestion-skill.js +54 -10
  40. package/dist/skills/repo-ingestion-skill.js.map +2 -2
  41. package/package.json +4 -8
  42. package/scripts/archive/check-all-duplicates.ts +2 -2
  43. package/scripts/archive/merge-linear-duplicates.ts +6 -4
  44. package/scripts/install-claude-hooks-auto.js +72 -15
  45. package/scripts/measure-handoff-impact.mjs +395 -0
  46. package/scripts/measure-handoff-impact.ts +450 -0
  47. package/templates/claude-hooks/on-startup.js +200 -19
  48. package/templates/services/com.stackmemory.guardian.plist +59 -0
  49. package/templates/services/stackmemory-guardian.service +41 -0
  50. package/scripts/testing/results/real-performance-results.json +0 -90
  51. package/scripts/testing/test-tier-migration.js +0 -100
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/core/session/enhanced-handoff.ts"],
4
+ "sourcesContent": ["/**\n * Enhanced Handoff Generator\n * Produces high-efficacy handoffs (70-85% context preservation)\n * Target: 2,000-3,000 tokens for rich context\n */\n\nimport { execSync } from 'child_process';\nimport {\n existsSync,\n readFileSync,\n readdirSync,\n statSync,\n writeFileSync,\n mkdirSync,\n} from 'fs';\nimport { join, basename } from 'path';\nimport { homedir, tmpdir } from 'os';\nimport { globSync } from 'glob';\n\n// Token counting - use Anthropic's tokenizer for accurate counts\nlet countTokens: (text: string) => number;\ntry {\n // Dynamic import for CommonJS compatibility\n const tokenizer = await import('@anthropic-ai/tokenizer');\n countTokens = tokenizer.countTokens;\n} catch {\n // Fallback to estimation if tokenizer not available\n countTokens = (text: string) => Math.ceil(text.length / 3.5);\n}\n\n// Load session decisions if available\ninterface SessionDecision {\n id: string;\n what: string;\n why: string;\n alternatives?: string[];\n timestamp: string;\n category?: string;\n}\n\n// Review feedback persistence\ninterface StoredReviewFeedback {\n timestamp: string;\n source: string;\n keyPoints: string[];\n actionItems: string[];\n sourceFile?: string;\n}\n\ninterface ReviewFeedbackStore {\n feedbacks: StoredReviewFeedback[];\n lastUpdated: string;\n}\n\nfunction loadSessionDecisions(projectRoot: string): SessionDecision[] {\n const storePath = join(projectRoot, '.stackmemory', 'session-decisions.json');\n if (existsSync(storePath)) {\n try {\n const store = JSON.parse(readFileSync(storePath, 'utf-8'));\n return store.decisions || [];\n } catch {\n return [];\n }\n }\n return [];\n}\n\nfunction loadReviewFeedback(projectRoot: string): StoredReviewFeedback[] {\n const storePath = join(projectRoot, '.stackmemory', 'review-feedback.json');\n if (existsSync(storePath)) {\n try {\n const store: ReviewFeedbackStore = JSON.parse(\n readFileSync(storePath, 'utf-8')\n );\n // Return feedbacks from last 24 hours\n const cutoff = Date.now() - 24 * 60 * 60 * 1000;\n return store.feedbacks.filter(\n (f) => new Date(f.timestamp).getTime() > cutoff\n );\n } catch {\n return [];\n }\n }\n return [];\n}\n\nfunction saveReviewFeedback(\n projectRoot: string,\n feedbacks: StoredReviewFeedback[]\n): void {\n const dir = join(projectRoot, '.stackmemory');\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const storePath = join(dir, 'review-feedback.json');\n\n // Load existing and merge\n let existing: StoredReviewFeedback[] = [];\n if (existsSync(storePath)) {\n try {\n const store: ReviewFeedbackStore = JSON.parse(\n readFileSync(storePath, 'utf-8')\n );\n existing = store.feedbacks || [];\n } catch {\n // Ignore parse errors\n }\n }\n\n // Deduplicate by source + first key point\n const seen = new Set<string>();\n const merged: StoredReviewFeedback[] = [];\n\n for (const f of [...feedbacks, ...existing]) {\n const key = `${f.source}:${f.keyPoints[0] || ''}`;\n if (!seen.has(key)) {\n seen.add(key);\n merged.push(f);\n }\n }\n\n // Keep only last 20 feedbacks\n const store: ReviewFeedbackStore = {\n feedbacks: merged.slice(0, 20),\n lastUpdated: new Date().toISOString(),\n };\n\n writeFileSync(storePath, JSON.stringify(store, null, 2));\n}\n\n/**\n * Find Claude agent output directories dynamically\n */\nfunction findAgentOutputDirs(projectRoot: string): string[] {\n const dirs: string[] = [];\n\n // Try multiple locations where agent outputs might be stored\n const tmpBase = process.env['TMPDIR'] || tmpdir() || '/tmp';\n\n // Pattern 1: /tmp/claude/-path-to-project/tasks\n const projectPathEncoded = projectRoot.replace(/\\//g, '-').replace(/^-/, '');\n const pattern1 = join(tmpBase, 'claude', `*${projectPathEncoded}*`, 'tasks');\n try {\n const matches = globSync(pattern1);\n dirs.push(...matches);\n } catch {\n // Glob failed\n }\n\n // Pattern 2: /private/tmp/claude/... (macOS specific)\n if (tmpBase !== '/private/tmp') {\n const pattern2 = join(\n '/private/tmp',\n 'claude',\n `*${projectPathEncoded}*`,\n 'tasks'\n );\n try {\n const matches = globSync(pattern2);\n dirs.push(...matches);\n } catch {\n // Glob failed\n }\n }\n\n // Pattern 3: ~/.claude/projects/*/tasks (if exists)\n const homeClaudeDir = join(homedir(), '.claude', 'projects');\n if (existsSync(homeClaudeDir)) {\n try {\n const projectDirs = readdirSync(homeClaudeDir);\n for (const d of projectDirs) {\n const tasksDir = join(homeClaudeDir, d, 'tasks');\n if (existsSync(tasksDir)) {\n dirs.push(tasksDir);\n }\n }\n } catch {\n // Failed to read\n }\n }\n\n return [...new Set(dirs)]; // Deduplicate\n}\n\nexport interface EnhancedHandoff {\n // Metadata\n timestamp: string;\n project: string;\n branch: string;\n sessionDuration?: string;\n\n // What we're building (HIGH VALUE)\n activeWork: {\n description: string;\n status: 'in_progress' | 'blocked' | 'review' | 'done';\n keyFiles: string[];\n progress?: string;\n };\n\n // Decisions made (HIGH VALUE)\n decisions: Array<{\n what: string;\n why: string;\n alternatives?: string[];\n }>;\n\n // Architecture context (MEDIUM VALUE)\n architecture: {\n keyComponents: Array<{\n file: string;\n purpose: string;\n }>;\n patterns: string[];\n };\n\n // Blockers and issues (HIGH VALUE)\n blockers: Array<{\n issue: string;\n attempted: string[];\n status: 'resolved' | 'open';\n }>;\n\n // Review feedback (HIGH VALUE if present)\n reviewFeedback?: {\n source: string;\n keyPoints: string[];\n actionItems: string[];\n }[];\n\n // Next actions (MEDIUM VALUE)\n nextActions: string[];\n\n // Patterns established (LOW-MEDIUM VALUE)\n codePatterns?: string[];\n\n // Token metrics\n estimatedTokens: number;\n}\n\nexport class EnhancedHandoffGenerator {\n private projectRoot: string;\n private claudeProjectsDir: string;\n\n constructor(projectRoot: string) {\n this.projectRoot = projectRoot;\n this.claudeProjectsDir = join(homedir(), '.claude', 'projects');\n }\n\n /**\n * Generate a high-efficacy handoff\n */\n async generate(): Promise<EnhancedHandoff> {\n const handoff: EnhancedHandoff = {\n timestamp: new Date().toISOString(),\n project: basename(this.projectRoot),\n branch: this.getCurrentBranch(),\n activeWork: await this.extractActiveWork(),\n decisions: await this.extractDecisions(),\n architecture: await this.extractArchitecture(),\n blockers: await this.extractBlockers(),\n reviewFeedback: await this.extractReviewFeedback(),\n nextActions: await this.extractNextActions(),\n codePatterns: await this.extractCodePatterns(),\n estimatedTokens: 0,\n };\n\n // Calculate estimated tokens\n const markdown = this.toMarkdown(handoff);\n handoff.estimatedTokens = countTokens(markdown);\n\n return handoff;\n }\n\n /**\n * Extract what we're currently building from git and recent files\n */\n private async extractActiveWork(): Promise<EnhancedHandoff['activeWork']> {\n // Get recent commits to understand current work\n const recentCommits = this.getRecentCommits(5);\n const recentFiles = this.getRecentlyModifiedFiles(10);\n\n // Try to infer the active work from commit messages\n let description = 'Unknown - check git log for context';\n let status: EnhancedHandoff['activeWork']['status'] = 'in_progress';\n\n if (recentCommits.length > 0) {\n // Use most recent commit as indicator\n const lastCommit = recentCommits[0];\n if (lastCommit.includes('feat:') || lastCommit.includes('implement')) {\n description = lastCommit.replace(/^[a-f0-9]+\\s+/, '');\n } else if (lastCommit.includes('fix:')) {\n description = 'Bug fix: ' + lastCommit.replace(/^[a-f0-9]+\\s+/, '');\n } else if (\n lastCommit.includes('chore:') ||\n lastCommit.includes('refactor:')\n ) {\n description = lastCommit.replace(/^[a-f0-9]+\\s+/, '');\n } else {\n description = lastCommit.replace(/^[a-f0-9]+\\s+/, '');\n }\n }\n\n // Check for blocking indicators\n const gitStatus = this.getGitStatus();\n if (gitStatus.includes('conflict')) {\n status = 'blocked';\n }\n\n return {\n description,\n status,\n keyFiles: recentFiles.slice(0, 5),\n progress:\n recentCommits.length > 0\n ? `${recentCommits.length} commits in current session`\n : undefined,\n };\n }\n\n /**\n * Extract decisions from session store, git commits, and decision logs\n */\n private async extractDecisions(): Promise<EnhancedHandoff['decisions']> {\n const decisions: EnhancedHandoff['decisions'] = [];\n\n // First, load session decisions (highest priority - explicitly recorded)\n const sessionDecisions = loadSessionDecisions(this.projectRoot);\n for (const d of sessionDecisions) {\n decisions.push({\n what: d.what,\n why: d.why,\n alternatives: d.alternatives,\n });\n }\n\n // Then look for decision markers in recent commits\n const commits = this.getRecentCommits(20);\n for (const commit of commits) {\n // Look for decision-like patterns\n if (\n commit.toLowerCase().includes('use ') ||\n commit.toLowerCase().includes('switch to ') ||\n commit.toLowerCase().includes('default to ') ||\n (commit.toLowerCase().includes('make ') &&\n commit.toLowerCase().includes('optional'))\n ) {\n // Avoid duplicates\n const commitText = commit.replace(/^[a-f0-9]+\\s+/, '');\n if (!decisions.some((d) => d.what.includes(commitText.slice(0, 30)))) {\n decisions.push({\n what: commitText,\n why: 'See commit for details',\n });\n }\n }\n }\n\n // Check for a decisions file\n const decisionsFile = join(\n this.projectRoot,\n '.stackmemory',\n 'decisions.md'\n );\n if (existsSync(decisionsFile)) {\n const content = readFileSync(decisionsFile, 'utf-8');\n const parsed = this.parseDecisionsFile(content);\n decisions.push(...parsed);\n }\n\n return decisions.slice(0, 10); // Limit to prevent bloat\n }\n\n /**\n * Parse a decisions.md file\n */\n private parseDecisionsFile(content: string): EnhancedHandoff['decisions'] {\n const decisions: EnhancedHandoff['decisions'] = [];\n const lines = content.split('\\n');\n\n let currentDecision: {\n what: string;\n why: string;\n alternatives?: string[];\n } | null = null;\n\n for (const line of lines) {\n if (line.startsWith('## ') || line.startsWith('### ')) {\n if (currentDecision) {\n decisions.push(currentDecision);\n }\n currentDecision = { what: line.replace(/^#+\\s+/, ''), why: '' };\n } else if (currentDecision && line.toLowerCase().includes('rationale:')) {\n currentDecision.why = line.replace(/rationale:\\s*/i, '').trim();\n } else if (currentDecision && line.toLowerCase().includes('why:')) {\n currentDecision.why = line.replace(/why:\\s*/i, '').trim();\n } else if (\n currentDecision &&\n line.toLowerCase().includes('alternatives:')\n ) {\n currentDecision.alternatives = [];\n } else if (currentDecision?.alternatives && line.trim().startsWith('-')) {\n currentDecision.alternatives.push(line.replace(/^\\s*-\\s*/, '').trim());\n }\n }\n\n if (currentDecision) {\n decisions.push(currentDecision);\n }\n\n return decisions;\n }\n\n /**\n * Extract architecture context from key files\n */\n private async extractArchitecture(): Promise<\n EnhancedHandoff['architecture']\n > {\n const keyComponents: EnhancedHandoff['architecture']['keyComponents'] = [];\n const patterns: string[] = [];\n\n // Find recently modified TypeScript/JavaScript files\n const recentFiles = this.getRecentlyModifiedFiles(20);\n const codeFiles = recentFiles.filter(\n (f) => f.endsWith('.ts') || f.endsWith('.js') || f.endsWith('.tsx')\n );\n\n for (const file of codeFiles.slice(0, 8)) {\n const purpose = this.inferFilePurpose(file);\n if (purpose) {\n keyComponents.push({ file, purpose });\n }\n }\n\n // Detect patterns from file structure\n if (codeFiles.some((f) => f.includes('/daemon/'))) {\n patterns.push('Daemon/background process pattern');\n }\n if (codeFiles.some((f) => f.includes('/cli/'))) {\n patterns.push('CLI command pattern');\n }\n if (\n codeFiles.some((f) => f.includes('.test.') || f.includes('__tests__'))\n ) {\n patterns.push('Test files present');\n }\n if (codeFiles.some((f) => f.includes('/core/'))) {\n patterns.push('Core/domain separation');\n }\n\n return { keyComponents, patterns };\n }\n\n /**\n * Infer purpose from file name and path\n */\n private inferFilePurpose(filePath: string): string | null {\n const name = basename(filePath).replace(/\\.(ts|js|tsx)$/, '');\n const path = filePath.toLowerCase();\n\n if (path.includes('daemon')) return 'Background daemon/service';\n if (path.includes('cli/command')) return 'CLI command handler';\n if (path.includes('config')) return 'Configuration management';\n if (path.includes('storage')) return 'Data storage layer';\n if (path.includes('handoff')) return 'Session handoff logic';\n if (path.includes('service')) return 'Service orchestration';\n if (path.includes('manager')) return 'Resource/state management';\n if (path.includes('handler')) return 'Event/request handler';\n if (path.includes('util') || path.includes('helper'))\n return 'Utility functions';\n if (path.includes('types') || path.includes('interface'))\n return 'Type definitions';\n if (path.includes('test')) return null; // Skip test files\n if (name.includes('-')) {\n return name\n .split('-')\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n }\n return null;\n }\n\n /**\n * Extract blockers from git status and recent errors\n */\n private async extractBlockers(): Promise<EnhancedHandoff['blockers']> {\n const blockers: EnhancedHandoff['blockers'] = [];\n\n // Check for merge conflicts\n const gitStatus = this.getGitStatus();\n if (gitStatus.includes('UU ') || gitStatus.includes('both modified')) {\n blockers.push({\n issue: 'Merge conflict detected',\n attempted: ['Check git status for affected files'],\n status: 'open',\n });\n }\n\n // Check for failing tests\n try {\n const testResult = execSync('npm test 2>&1 || true', {\n encoding: 'utf-8',\n cwd: this.projectRoot,\n timeout: 30000,\n });\n if (testResult.includes('FAIL') || testResult.includes('failed')) {\n const failCount = (testResult.match(/(\\d+) failed/i) || ['', '?'])[1];\n blockers.push({\n issue: `Test failures: ${failCount} tests failing`,\n attempted: ['Run npm test for details'],\n status: 'open',\n });\n }\n } catch {\n // Test command failed - might indicate issues\n }\n\n // Check for lint errors\n try {\n const lintResult = execSync('npm run lint 2>&1 || true', {\n encoding: 'utf-8',\n cwd: this.projectRoot,\n timeout: 30000,\n });\n if (lintResult.includes('error') && !lintResult.includes('0 errors')) {\n blockers.push({\n issue: 'Lint errors present',\n attempted: ['Run npm run lint for details'],\n status: 'open',\n });\n }\n } catch {\n // Lint command failed\n }\n\n return blockers;\n }\n\n /**\n * Extract review feedback from agent output files and persisted storage\n */\n private async extractReviewFeedback(): Promise<\n EnhancedHandoff['reviewFeedback']\n > {\n const feedback: EnhancedHandoff['reviewFeedback'] = [];\n const newFeedbacks: StoredReviewFeedback[] = [];\n\n // Find agent output directories dynamically\n const outputDirs = findAgentOutputDirs(this.projectRoot);\n\n for (const tmpDir of outputDirs) {\n if (!existsSync(tmpDir)) continue;\n\n try {\n const files = readdirSync(tmpDir).filter((f) => f.endsWith('.output'));\n const recentFiles = files\n .map((f) => ({\n name: f,\n path: join(tmpDir, f),\n stat: statSync(join(tmpDir, f)),\n }))\n .filter((f) => Date.now() - f.stat.mtimeMs < 3600000) // Last hour\n .sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs)\n .slice(0, 3);\n\n for (const file of recentFiles) {\n const content = readFileSync(file.path, 'utf-8');\n const extracted = this.extractKeyPointsFromReview(content);\n if (extracted.keyPoints.length > 0) {\n feedback.push(extracted);\n\n // Also store for persistence\n newFeedbacks.push({\n timestamp: new Date().toISOString(),\n source: extracted.source,\n keyPoints: extracted.keyPoints,\n actionItems: extracted.actionItems,\n sourceFile: file.name,\n });\n }\n }\n } catch {\n // Failed to read agent outputs from this directory\n }\n }\n\n // Save new feedback to persistent storage\n if (newFeedbacks.length > 0) {\n saveReviewFeedback(this.projectRoot, newFeedbacks);\n }\n\n // Load persisted feedback if no new feedback found\n if (feedback.length === 0) {\n const stored = loadReviewFeedback(this.projectRoot);\n for (const s of stored.slice(0, 3)) {\n feedback.push({\n source: s.source,\n keyPoints: s.keyPoints,\n actionItems: s.actionItems,\n });\n }\n }\n\n return feedback.length > 0 ? feedback : undefined;\n }\n\n /**\n * Extract key points from a review output\n */\n private extractKeyPointsFromReview(content: string): {\n source: string;\n keyPoints: string[];\n actionItems: string[];\n } {\n const keyPoints: string[] = [];\n const actionItems: string[] = [];\n let source = 'Agent Review';\n\n // Detect review type\n if (\n content.includes('Product Manager') ||\n content.includes('product-manager')\n ) {\n source = 'Product Manager';\n } else if (\n content.includes('Staff Architect') ||\n content.includes('staff-architect')\n ) {\n source = 'Staff Architect';\n }\n\n // Extract key recommendations (look for common patterns)\n const lines = content.split('\\n');\n let inRecommendations = false;\n let inActionItems = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Detect section headers\n if (\n trimmed.toLowerCase().includes('recommendation') ||\n trimmed.toLowerCase().includes('key finding')\n ) {\n inRecommendations = true;\n inActionItems = false;\n continue;\n }\n if (\n trimmed.toLowerCase().includes('action') ||\n trimmed.toLowerCase().includes('next step') ||\n trimmed.toLowerCase().includes('priority')\n ) {\n inActionItems = true;\n inRecommendations = false;\n continue;\n }\n\n // Extract bullet points\n if (\n trimmed.startsWith('- ') ||\n trimmed.startsWith('* ') ||\n /^\\d+\\.\\s/.test(trimmed)\n ) {\n const point = trimmed.replace(/^[-*]\\s+/, '').replace(/^\\d+\\.\\s+/, '');\n if (point.length > 10 && point.length < 200) {\n if (inActionItems) {\n actionItems.push(point);\n } else if (inRecommendations) {\n keyPoints.push(point);\n }\n }\n }\n }\n\n // Limit to prevent bloat\n return {\n source,\n keyPoints: keyPoints.slice(0, 5),\n actionItems: actionItems.slice(0, 5),\n };\n }\n\n /**\n * Extract next actions from todo state and git\n */\n private async extractNextActions(): Promise<string[]> {\n const actions: string[] = [];\n\n // Check for uncommitted changes\n const gitStatus = this.getGitStatus();\n if (gitStatus.trim()) {\n actions.push('Commit pending changes');\n }\n\n // Look for TODO comments in recent files\n const recentFiles = this.getRecentlyModifiedFiles(5);\n for (const file of recentFiles) {\n try {\n const fullPath = join(this.projectRoot, file);\n if (existsSync(fullPath)) {\n const content = readFileSync(fullPath, 'utf-8');\n const todos = content.match(/\\/\\/\\s*TODO:?\\s*.+/gi) || [];\n for (const todo of todos.slice(0, 2)) {\n actions.push(todo.replace(/\\/\\/\\s*TODO:?\\s*/i, 'TODO: '));\n }\n }\n } catch {\n // Skip unreadable files\n }\n }\n\n // Check for pending tasks in .stackmemory\n const tasksFile = join(this.projectRoot, '.stackmemory', 'tasks.json');\n if (existsSync(tasksFile)) {\n try {\n const tasks = JSON.parse(readFileSync(tasksFile, 'utf-8'));\n const pending = tasks.filter(\n (t: any) => t.status === 'pending' || t.status === 'in_progress'\n );\n for (const task of pending.slice(0, 3)) {\n actions.push(task.title || task.description);\n }\n } catch {\n // Invalid tasks file\n }\n }\n\n return actions.slice(0, 8);\n }\n\n /**\n * Extract established code patterns\n */\n private async extractCodePatterns(): Promise<string[]> {\n const patterns: string[] = [];\n\n // Check ESLint config for patterns\n const eslintConfig = join(this.projectRoot, 'eslint.config.js');\n if (existsSync(eslintConfig)) {\n const content = readFileSync(eslintConfig, 'utf-8');\n if (content.includes('argsIgnorePattern')) {\n patterns.push('Underscore prefix for unused vars (_var)');\n }\n if (content.includes('ignores') && content.includes('test')) {\n patterns.push('Test files excluded from lint');\n }\n }\n\n // Check tsconfig for patterns\n const tsconfig = join(this.projectRoot, 'tsconfig.json');\n if (existsSync(tsconfig)) {\n const content = readFileSync(tsconfig, 'utf-8');\n if (content.includes('\"strict\": true')) {\n patterns.push('TypeScript strict mode enabled');\n }\n if (content.includes('ES2022') || content.includes('ESNext')) {\n patterns.push('ESM module system');\n }\n }\n\n return patterns;\n }\n\n /**\n * Get recent git commits\n */\n private getRecentCommits(count: number): string[] {\n try {\n const result = execSync(`git log --oneline -${count}`, {\n encoding: 'utf-8',\n cwd: this.projectRoot,\n });\n return result.trim().split('\\n').filter(Boolean);\n } catch {\n return [];\n }\n }\n\n /**\n * Get current git branch\n */\n private getCurrentBranch(): string {\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', {\n encoding: 'utf-8',\n cwd: this.projectRoot,\n }).trim();\n } catch {\n return 'unknown';\n }\n }\n\n /**\n * Get git status\n */\n private getGitStatus(): string {\n try {\n return execSync('git status --short', {\n encoding: 'utf-8',\n cwd: this.projectRoot,\n });\n } catch {\n return '';\n }\n }\n\n /**\n * Get recently modified files\n */\n private getRecentlyModifiedFiles(count: number): string[] {\n try {\n const result = execSync(\n `git diff --name-only HEAD~10 HEAD 2>/dev/null || git diff --name-only`,\n {\n encoding: 'utf-8',\n cwd: this.projectRoot,\n }\n );\n return result.trim().split('\\n').filter(Boolean).slice(0, count);\n } catch {\n return [];\n }\n }\n\n /**\n * Convert handoff to markdown\n */\n toMarkdown(handoff: EnhancedHandoff): string {\n const lines: string[] = [];\n\n lines.push(`# Session Handoff - ${handoff.timestamp.split('T')[0]}`);\n lines.push('');\n lines.push(`**Project**: ${handoff.project}`);\n lines.push(`**Branch**: ${handoff.branch}`);\n lines.push('');\n\n // Active Work (HIGH VALUE)\n lines.push('## Active Work');\n lines.push(`- **Building**: ${handoff.activeWork.description}`);\n lines.push(`- **Status**: ${handoff.activeWork.status}`);\n if (handoff.activeWork.keyFiles.length > 0) {\n lines.push(`- **Key files**: ${handoff.activeWork.keyFiles.join(', ')}`);\n }\n if (handoff.activeWork.progress) {\n lines.push(`- **Progress**: ${handoff.activeWork.progress}`);\n }\n lines.push('');\n\n // Decisions (HIGH VALUE)\n if (handoff.decisions.length > 0) {\n lines.push('## Key Decisions');\n for (const d of handoff.decisions) {\n lines.push(`1. **${d.what}**`);\n if (d.why) {\n lines.push(` - Rationale: ${d.why}`);\n }\n if (d.alternatives && d.alternatives.length > 0) {\n lines.push(\n ` - Alternatives considered: ${d.alternatives.join(', ')}`\n );\n }\n }\n lines.push('');\n }\n\n // Architecture (MEDIUM VALUE)\n if (handoff.architecture.keyComponents.length > 0) {\n lines.push('## Architecture Context');\n for (const c of handoff.architecture.keyComponents) {\n lines.push(`- \\`${c.file}\\`: ${c.purpose}`);\n }\n if (handoff.architecture.patterns.length > 0) {\n lines.push('');\n lines.push('**Patterns**: ' + handoff.architecture.patterns.join(', '));\n }\n lines.push('');\n }\n\n // Blockers (HIGH VALUE)\n if (handoff.blockers.length > 0) {\n lines.push('## Blockers');\n for (const b of handoff.blockers) {\n lines.push(`- **${b.issue}** [${b.status}]`);\n if (b.attempted.length > 0) {\n lines.push(` - Tried: ${b.attempted.join(', ')}`);\n }\n }\n lines.push('');\n }\n\n // Review Feedback (HIGH VALUE)\n if (handoff.reviewFeedback && handoff.reviewFeedback.length > 0) {\n lines.push('## Review Feedback');\n for (const r of handoff.reviewFeedback) {\n lines.push(`### ${r.source}`);\n if (r.keyPoints.length > 0) {\n lines.push('**Key Points**:');\n for (const p of r.keyPoints) {\n lines.push(`- ${p}`);\n }\n }\n if (r.actionItems.length > 0) {\n lines.push('**Action Items**:');\n for (const a of r.actionItems) {\n lines.push(`- ${a}`);\n }\n }\n lines.push('');\n }\n }\n\n // Next Actions (MEDIUM VALUE)\n if (handoff.nextActions.length > 0) {\n lines.push('## Next Actions');\n for (const a of handoff.nextActions) {\n lines.push(`1. ${a}`);\n }\n lines.push('');\n }\n\n // Code Patterns (LOW VALUE)\n if (handoff.codePatterns && handoff.codePatterns.length > 0) {\n lines.push('## Established Patterns');\n for (const p of handoff.codePatterns) {\n lines.push(`- ${p}`);\n }\n lines.push('');\n }\n\n lines.push('---');\n lines.push(`*Estimated tokens: ~${handoff.estimatedTokens}*`);\n lines.push(`*Generated at ${handoff.timestamp}*`);\n\n return lines.join('\\n');\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["store"]
7
+ }
@@ -0,0 +1,328 @@
1
+ import Database from "better-sqlite3";
2
+ import { Pool } from "pg";
3
+ import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
4
+ import { logger } from "../monitoring/logger.js";
5
+ import * as zlib from "zlib";
6
+ import { promisify } from "util";
7
+ const gzipAsync = promisify(zlib.gzip);
8
+ const gunzipAsync = promisify(zlib.gunzip);
9
+ var StorageTier = /* @__PURE__ */ ((StorageTier2) => {
10
+ StorageTier2["HOT"] = "hot";
11
+ StorageTier2["COLD"] = "cold";
12
+ return StorageTier2;
13
+ })(StorageTier || {});
14
+ const DEFAULT_SIMPLIFIED_CONFIG = {
15
+ database: {
16
+ type: process.env["DATABASE_URL"]?.startsWith("postgres") ? "postgresql" : "sqlite",
17
+ url: process.env["DATABASE_URL"] || "./storage/stackmemory.db",
18
+ maxConnections: 10
19
+ },
20
+ objectStorage: {
21
+ endpoint: process.env["S3_ENDPOINT"] || "https://s3.amazonaws.com",
22
+ bucket: process.env["S3_BUCKET"] || "stackmemory-archive",
23
+ accessKeyId: process.env["S3_ACCESS_KEY_ID"] || "",
24
+ secretAccessKey: process.env["S3_SECRET_ACCESS_KEY"] || "",
25
+ region: process.env["S3_REGION"] || "us-east-1"
26
+ },
27
+ tiers: {
28
+ archiveAfterDays: 30,
29
+ // Archive after 30 days instead of complex 3-tier
30
+ compressionThreshold: 1024
31
+ // Compress items > 1KB
32
+ }
33
+ };
34
+ class SimplifiedStorage {
35
+ config;
36
+ db;
37
+ pgPool;
38
+ s3Client;
39
+ isInitialized = false;
40
+ constructor(config = DEFAULT_SIMPLIFIED_CONFIG) {
41
+ this.config = config;
42
+ if (this.config.objectStorage.accessKeyId && this.config.objectStorage.secretAccessKey) {
43
+ this.s3Client = new S3Client({
44
+ endpoint: this.config.objectStorage.endpoint,
45
+ region: this.config.objectStorage.region,
46
+ credentials: {
47
+ accessKeyId: this.config.objectStorage.accessKeyId,
48
+ secretAccessKey: this.config.objectStorage.secretAccessKey
49
+ }
50
+ });
51
+ }
52
+ }
53
+ async initialize() {
54
+ if (this.isInitialized) return;
55
+ try {
56
+ if (this.config.database.type === "postgresql") {
57
+ await this.initializePostgreSQL();
58
+ } else {
59
+ await this.initializeSQLite();
60
+ }
61
+ await this.createTables();
62
+ this.isInitialized = true;
63
+ this.startArchivalProcess();
64
+ logger.info("Simplified storage initialized", {
65
+ databaseType: this.config.database.type,
66
+ objectStorageEnabled: !!this.s3Client
67
+ });
68
+ } catch (error) {
69
+ logger.error("Failed to initialize simplified storage", { error });
70
+ throw error;
71
+ }
72
+ }
73
+ async initializePostgreSQL() {
74
+ this.pgPool = new Pool({
75
+ connectionString: this.config.database.url,
76
+ max: this.config.database.maxConnections,
77
+ idleTimeoutMillis: 3e4
78
+ });
79
+ }
80
+ async initializeSQLite() {
81
+ this.db = new Database(this.config.database.url);
82
+ this.db.pragma("foreign_keys = ON");
83
+ this.db.pragma("journal_mode = WAL");
84
+ }
85
+ async createTables() {
86
+ const schema = `
87
+ CREATE TABLE IF NOT EXISTS storage_items (
88
+ id TEXT PRIMARY KEY,
89
+ data BLOB,
90
+ metadata TEXT,
91
+ tier TEXT NOT NULL,
92
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
93
+ last_accessed TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
94
+ compressed BOOLEAN DEFAULT FALSE,
95
+ object_key TEXT -- For cold storage reference
96
+ );
97
+ CREATE INDEX IF NOT EXISTS idx_storage_tier ON storage_items(tier);
98
+ CREATE INDEX IF NOT EXISTS idx_storage_created ON storage_items(created_at);
99
+ `;
100
+ if (this.pgPool) {
101
+ await this.pgPool.query(schema.replace(/BLOB/g, "BYTEA").replace(/TIMESTAMP DEFAULT CURRENT_TIMESTAMP/g, "TIMESTAMP DEFAULT NOW()"));
102
+ } else if (this.db) {
103
+ this.db.exec(schema);
104
+ }
105
+ }
106
+ /**
107
+ * Store item in hot tier (database)
108
+ */
109
+ async store(id, data, metadata = {}) {
110
+ if (!this.isInitialized) await this.initialize();
111
+ let processedData = data;
112
+ let compressed = false;
113
+ if (data.length > this.config.tiers.compressionThreshold) {
114
+ processedData = await gzipAsync(data);
115
+ compressed = true;
116
+ logger.debug("Compressed storage item", { id, originalSize: data.length, compressedSize: processedData.length });
117
+ }
118
+ const item = {
119
+ id,
120
+ data: processedData,
121
+ metadata,
122
+ tier: "hot" /* HOT */,
123
+ createdAt: /* @__PURE__ */ new Date(),
124
+ lastAccessed: /* @__PURE__ */ new Date(),
125
+ compressed
126
+ };
127
+ if (this.pgPool) {
128
+ await this.pgPool.query(
129
+ `INSERT INTO storage_items (id, data, metadata, tier, created_at, last_accessed, compressed)
130
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
131
+ ON CONFLICT (id) DO UPDATE SET
132
+ data = EXCLUDED.data,
133
+ metadata = EXCLUDED.metadata,
134
+ last_accessed = EXCLUDED.last_accessed,
135
+ compressed = EXCLUDED.compressed`,
136
+ [id, processedData, JSON.stringify(metadata), item.tier, item.createdAt, item.lastAccessed, compressed]
137
+ );
138
+ } else if (this.db) {
139
+ const stmt = this.db.prepare(`
140
+ INSERT OR REPLACE INTO storage_items (id, data, metadata, tier, created_at, last_accessed, compressed)
141
+ VALUES (?, ?, ?, ?, ?, ?, ?)
142
+ `);
143
+ stmt.run(id, processedData, JSON.stringify(metadata), item.tier, item.createdAt?.toISOString(), item.lastAccessed?.toISOString(), compressed ? 1 : 0);
144
+ }
145
+ logger.debug("Stored item in hot tier", { id, size: processedData.length, compressed });
146
+ }
147
+ /**
148
+ * Retrieve item from appropriate tier
149
+ */
150
+ async retrieve(id) {
151
+ if (!this.isInitialized) await this.initialize();
152
+ await this.updateLastAccessed(id);
153
+ let row;
154
+ if (this.pgPool) {
155
+ const { rows } = await this.pgPool.query("SELECT * FROM storage_items WHERE id = $1", [id]);
156
+ row = rows[0];
157
+ } else if (this.db) {
158
+ row = this.db.prepare("SELECT * FROM storage_items WHERE id = ?").get(id);
159
+ }
160
+ if (!row) return null;
161
+ let data;
162
+ if (row.tier === "cold" /* COLD */ && row.object_key && this.s3Client) {
163
+ data = await this.retrieveFromColdStorage(row.object_key);
164
+ } else {
165
+ data = row.data;
166
+ }
167
+ if (row.compressed) {
168
+ data = await gunzipAsync(data);
169
+ }
170
+ logger.debug("Retrieved item", { id, tier: row.tier, compressed: row.compressed });
171
+ return data;
172
+ }
173
+ /**
174
+ * Archive old items to cold storage
175
+ */
176
+ async archiveOldItems() {
177
+ if (!this.s3Client) return;
178
+ const cutoffDate = /* @__PURE__ */ new Date();
179
+ cutoffDate.setDate(cutoffDate.getDate() - this.config.tiers.archiveAfterDays);
180
+ let rows;
181
+ if (this.pgPool) {
182
+ const { rows: pgRows } = await this.pgPool.query(
183
+ "SELECT * FROM storage_items WHERE tier = $1 AND created_at < $2 LIMIT 100",
184
+ ["hot" /* HOT */, cutoffDate]
185
+ );
186
+ rows = pgRows;
187
+ } else if (this.db) {
188
+ rows = this.db.prepare(
189
+ "SELECT * FROM storage_items WHERE tier = ? AND created_at < ? LIMIT 100"
190
+ ).all("hot" /* HOT */, cutoffDate.toISOString());
191
+ } else {
192
+ return;
193
+ }
194
+ for (const row of rows) {
195
+ try {
196
+ await this.archiveItem(row);
197
+ logger.debug("Archived item to cold storage", { id: row.id });
198
+ } catch (error) {
199
+ logger.warn("Failed to archive item", { id: row.id, error });
200
+ }
201
+ }
202
+ if (rows.length > 0) {
203
+ logger.info("Archived old items", { count: rows.length, cutoffDate });
204
+ }
205
+ }
206
+ async archiveItem(row) {
207
+ if (!this.s3Client) return;
208
+ const objectKey = `archived/${row.id}`;
209
+ await this.s3Client.send(
210
+ new PutObjectCommand({
211
+ Bucket: this.config.objectStorage.bucket,
212
+ Key: objectKey,
213
+ Body: row.data,
214
+ Metadata: {
215
+ originalId: row.id,
216
+ compressed: row.compressed.toString(),
217
+ archivedAt: (/* @__PURE__ */ new Date()).toISOString()
218
+ }
219
+ })
220
+ );
221
+ if (this.pgPool) {
222
+ await this.pgPool.query(
223
+ "UPDATE storage_items SET tier = $1, data = NULL, object_key = $2 WHERE id = $3",
224
+ ["cold" /* COLD */, objectKey, row.id]
225
+ );
226
+ } else if (this.db) {
227
+ this.db.prepare(
228
+ "UPDATE storage_items SET tier = ?, data = NULL, object_key = ? WHERE id = ?"
229
+ ).run("cold" /* COLD */, objectKey, row.id);
230
+ }
231
+ }
232
+ async retrieveFromColdStorage(objectKey) {
233
+ if (!this.s3Client) throw new Error("Object storage not configured");
234
+ const response = await this.s3Client.send(
235
+ new GetObjectCommand({
236
+ Bucket: this.config.objectStorage.bucket,
237
+ Key: objectKey
238
+ })
239
+ );
240
+ if (!response.Body) throw new Error("Empty response from cold storage");
241
+ const chunks = [];
242
+ const reader = response.Body;
243
+ return new Promise((resolve, reject) => {
244
+ reader.on("data", (chunk) => chunks.push(chunk));
245
+ reader.on("end", () => resolve(Buffer.concat(chunks)));
246
+ reader.on("error", reject);
247
+ });
248
+ }
249
+ async updateLastAccessed(id) {
250
+ const now = /* @__PURE__ */ new Date();
251
+ if (this.pgPool) {
252
+ await this.pgPool.query(
253
+ "UPDATE storage_items SET last_accessed = $1 WHERE id = $2",
254
+ [now, id]
255
+ );
256
+ } else if (this.db) {
257
+ this.db.prepare("UPDATE storage_items SET last_accessed = ? WHERE id = ?").run(now.toISOString(), id);
258
+ }
259
+ }
260
+ startArchivalProcess() {
261
+ setInterval(async () => {
262
+ try {
263
+ await this.archiveOldItems();
264
+ } catch (error) {
265
+ logger.error("Archival process failed", { error });
266
+ }
267
+ }, 6 * 60 * 60 * 1e3);
268
+ logger.info("Started archival process", { intervalHours: 6 });
269
+ }
270
+ /**
271
+ * Get storage statistics
272
+ */
273
+ async getStats() {
274
+ if (!this.isInitialized) await this.initialize();
275
+ let hotItems = 0;
276
+ let coldItems = 0;
277
+ let totalSize = 0;
278
+ if (this.pgPool) {
279
+ const { rows } = await this.pgPool.query(`
280
+ SELECT tier, COUNT(*) as count, SUM(length(data)) as size
281
+ FROM storage_items
282
+ GROUP BY tier
283
+ `);
284
+ for (const row of rows) {
285
+ if (row.tier === "hot" /* HOT */) {
286
+ hotItems = parseInt(row.count);
287
+ totalSize += parseInt(row.size || 0);
288
+ } else {
289
+ coldItems = parseInt(row.count);
290
+ }
291
+ }
292
+ } else if (this.db) {
293
+ const stmt = this.db.prepare(`
294
+ SELECT tier, COUNT(*) as count, SUM(length(data)) as size
295
+ FROM storage_items
296
+ GROUP BY tier
297
+ `);
298
+ const rows = stmt.all();
299
+ for (const row of rows) {
300
+ if (row.tier === "hot" /* HOT */) {
301
+ hotItems = row.count;
302
+ totalSize += row.size || 0;
303
+ } else {
304
+ coldItems = row.count;
305
+ }
306
+ }
307
+ }
308
+ return { hotItems, coldItems, totalSize };
309
+ }
310
+ async close() {
311
+ if (this.pgPool) {
312
+ await this.pgPool.end();
313
+ }
314
+ if (this.db) {
315
+ this.db.close();
316
+ }
317
+ this.isInitialized = false;
318
+ logger.info("Simplified storage closed");
319
+ }
320
+ }
321
+ var simplified_storage_default = SimplifiedStorage;
322
+ export {
323
+ DEFAULT_SIMPLIFIED_CONFIG,
324
+ SimplifiedStorage,
325
+ StorageTier,
326
+ simplified_storage_default as default
327
+ };
328
+ //# sourceMappingURL=simplified-storage.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/core/storage/simplified-storage.ts"],
4
+ "sourcesContent": ["/**\n * Simplified 2-Tier Storage System\n * Tier 1: SQLite/PostgreSQL (Hot) - Active data, immediate access\n * Tier 2: Object Storage (Cold) - Archive data, cost-effective long-term storage\n * \n * ARCHITECT RECOMMENDATION: Removed Redis complexity, GCS overkill\n */\n\nimport Database from 'better-sqlite3';\nimport { Pool } from 'pg';\nimport { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';\nimport { logger } from '../monitoring/logger.js';\nimport * as zlib from 'zlib';\nimport { promisify } from 'util';\n\nconst gzipAsync = promisify(zlib.gzip);\nconst gunzipAsync = promisify(zlib.gunzip);\n\nexport enum StorageTier {\n HOT = 'hot', // SQLite/PostgreSQL: Active data\n COLD = 'cold', // Object Storage: Archive data\n}\n\nexport interface SimplifiedStorageConfig {\n database: {\n type: 'sqlite' | 'postgresql';\n url: string;\n maxConnections?: number;\n };\n objectStorage: {\n endpoint: string;\n bucket: string;\n accessKeyId: string;\n secretAccessKey: string;\n region: string;\n };\n tiers: {\n archiveAfterDays: number; // Move to cold storage after N days\n compressionThreshold: number; // Compress items larger than N bytes\n };\n}\n\nexport const DEFAULT_SIMPLIFIED_CONFIG: SimplifiedStorageConfig = {\n database: {\n type: process.env['DATABASE_URL']?.startsWith('postgres') ? 'postgresql' : 'sqlite',\n url: process.env['DATABASE_URL'] || './storage/stackmemory.db',\n maxConnections: 10,\n },\n objectStorage: {\n endpoint: process.env['S3_ENDPOINT'] || 'https://s3.amazonaws.com',\n bucket: process.env['S3_BUCKET'] || 'stackmemory-archive',\n accessKeyId: process.env['S3_ACCESS_KEY_ID'] || '',\n secretAccessKey: process.env['S3_SECRET_ACCESS_KEY'] || '',\n region: process.env['S3_REGION'] || 'us-east-1',\n },\n tiers: {\n archiveAfterDays: 30, // Archive after 30 days instead of complex 3-tier\n compressionThreshold: 1024, // Compress items > 1KB\n },\n};\n\nexport interface StoredItem {\n id: string;\n data: Buffer;\n metadata: Record<string, unknown>;\n tier: StorageTier;\n createdAt: Date;\n lastAccessed: Date;\n compressed: boolean;\n}\n\ninterface DatabaseRow {\n id: string;\n data: Buffer;\n metadata: string;\n tier: string;\n created_at: string;\n last_accessed: string;\n compressed: number | boolean;\n object_key?: string;\n}\n\n/**\n * Simplified Storage System (Architect-approved)\n * Removes Redis complexity, GCS overkill, premature optimization\n */\nexport class SimplifiedStorage {\n private config: SimplifiedStorageConfig;\n private db?: Database.Database;\n private pgPool?: Pool;\n private s3Client?: S3Client;\n private isInitialized = false;\n\n constructor(config: SimplifiedStorageConfig = DEFAULT_SIMPLIFIED_CONFIG) {\n this.config = config;\n \n // Only initialize S3 if properly configured\n if (this.config.objectStorage.accessKeyId && this.config.objectStorage.secretAccessKey) {\n this.s3Client = new S3Client({\n endpoint: this.config.objectStorage.endpoint,\n region: this.config.objectStorage.region,\n credentials: {\n accessKeyId: this.config.objectStorage.accessKeyId,\n secretAccessKey: this.config.objectStorage.secretAccessKey,\n },\n });\n }\n }\n\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n try {\n if (this.config.database.type === 'postgresql') {\n await this.initializePostgreSQL();\n } else {\n await this.initializeSQLite();\n }\n\n await this.createTables();\n this.isInitialized = true;\n \n // Start background archival process (simple cron-like)\n this.startArchivalProcess();\n \n logger.info('Simplified storage initialized', {\n databaseType: this.config.database.type,\n objectStorageEnabled: !!this.s3Client,\n });\n } catch (error) {\n logger.error('Failed to initialize simplified storage', { error });\n throw error;\n }\n }\n\n private async initializePostgreSQL(): Promise<void> {\n this.pgPool = new Pool({\n connectionString: this.config.database.url,\n max: this.config.database.maxConnections,\n idleTimeoutMillis: 30000,\n });\n }\n\n private async initializeSQLite(): Promise<void> {\n this.db = new Database(this.config.database.url);\n this.db.pragma('foreign_keys = ON');\n this.db.pragma('journal_mode = WAL');\n }\n\n private async createTables(): Promise<void> {\n const schema = `\n CREATE TABLE IF NOT EXISTS storage_items (\n id TEXT PRIMARY KEY,\n data BLOB,\n metadata TEXT,\n tier TEXT NOT NULL,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n last_accessed TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n compressed BOOLEAN DEFAULT FALSE,\n object_key TEXT -- For cold storage reference\n );\n CREATE INDEX IF NOT EXISTS idx_storage_tier ON storage_items(tier);\n CREATE INDEX IF NOT EXISTS idx_storage_created ON storage_items(created_at);\n `;\n\n if (this.pgPool) {\n await this.pgPool.query(schema.replace(/BLOB/g, 'BYTEA').replace(/TIMESTAMP DEFAULT CURRENT_TIMESTAMP/g, 'TIMESTAMP DEFAULT NOW()'));\n } else if (this.db) {\n this.db.exec(schema);\n }\n }\n\n /**\n * Store item in hot tier (database)\n */\n async store(id: string, data: Buffer, metadata: Record<string, unknown> = {}): Promise<void> {\n if (!this.isInitialized) await this.initialize();\n\n let processedData = data;\n let compressed = false;\n\n // Compress if over threshold\n if (data.length > this.config.tiers.compressionThreshold) {\n processedData = await gzipAsync(data);\n compressed = true;\n logger.debug('Compressed storage item', { id, originalSize: data.length, compressedSize: processedData.length });\n }\n\n const item: Partial<StoredItem> = {\n id,\n data: processedData,\n metadata,\n tier: StorageTier.HOT,\n createdAt: new Date(),\n lastAccessed: new Date(),\n compressed,\n };\n\n if (this.pgPool) {\n await this.pgPool.query(\n `INSERT INTO storage_items (id, data, metadata, tier, created_at, last_accessed, compressed)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (id) DO UPDATE SET\n data = EXCLUDED.data,\n metadata = EXCLUDED.metadata,\n last_accessed = EXCLUDED.last_accessed,\n compressed = EXCLUDED.compressed`,\n [id, processedData, JSON.stringify(metadata), item.tier, item.createdAt, item.lastAccessed, compressed]\n );\n } else if (this.db) {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO storage_items (id, data, metadata, tier, created_at, last_accessed, compressed)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n stmt.run(id, processedData, JSON.stringify(metadata), item.tier, item.createdAt?.toISOString(), item.lastAccessed?.toISOString(), compressed ? 1 : 0);\n }\n\n logger.debug('Stored item in hot tier', { id, size: processedData.length, compressed });\n }\n\n /**\n * Retrieve item from appropriate tier\n */\n async retrieve(id: string): Promise<Buffer | null> {\n if (!this.isInitialized) await this.initialize();\n\n // Update last accessed timestamp\n await this.updateLastAccessed(id);\n\n let row: DatabaseRow | undefined;\n if (this.pgPool) {\n const { rows } = await this.pgPool.query('SELECT * FROM storage_items WHERE id = $1', [id]);\n row = rows[0];\n } else if (this.db) {\n row = this.db.prepare('SELECT * FROM storage_items WHERE id = ?').get(id);\n }\n\n if (!row) return null;\n\n let data: Buffer;\n \n if (row.tier === StorageTier.COLD && row.object_key && this.s3Client) {\n // Retrieve from cold storage\n data = await this.retrieveFromColdStorage(row.object_key);\n } else {\n // Retrieve from hot storage\n data = row.data;\n }\n\n // Decompress if needed\n if (row.compressed) {\n data = await gunzipAsync(data);\n }\n\n logger.debug('Retrieved item', { id, tier: row.tier, compressed: row.compressed });\n return data;\n }\n\n /**\n * Archive old items to cold storage\n */\n private async archiveOldItems(): Promise<void> {\n if (!this.s3Client) return; // Skip if object storage not configured\n\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - this.config.tiers.archiveAfterDays);\n\n let rows: DatabaseRow[];\n if (this.pgPool) {\n const { rows: pgRows } = await this.pgPool.query(\n 'SELECT * FROM storage_items WHERE tier = $1 AND created_at < $2 LIMIT 100',\n [StorageTier.HOT, cutoffDate]\n );\n rows = pgRows;\n } else if (this.db) {\n rows = this.db.prepare(\n 'SELECT * FROM storage_items WHERE tier = ? AND created_at < ? LIMIT 100'\n ).all(StorageTier.HOT, cutoffDate.toISOString());\n } else {\n return;\n }\n\n for (const row of rows) {\n try {\n await this.archiveItem(row);\n logger.debug('Archived item to cold storage', { id: row.id });\n } catch (error) {\n logger.warn('Failed to archive item', { id: row.id, error });\n }\n }\n\n if (rows.length > 0) {\n logger.info('Archived old items', { count: rows.length, cutoffDate });\n }\n }\n\n private async archiveItem(row: DatabaseRow): Promise<void> {\n if (!this.s3Client) return;\n\n const objectKey = `archived/${row.id}`;\n \n // Upload to S3\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.config.objectStorage.bucket,\n Key: objectKey,\n Body: row.data,\n Metadata: {\n originalId: row.id,\n compressed: row.compressed.toString(),\n archivedAt: new Date().toISOString(),\n },\n })\n );\n\n // Update database record\n if (this.pgPool) {\n await this.pgPool.query(\n 'UPDATE storage_items SET tier = $1, data = NULL, object_key = $2 WHERE id = $3',\n [StorageTier.COLD, objectKey, row.id]\n );\n } else if (this.db) {\n this.db.prepare(\n 'UPDATE storage_items SET tier = ?, data = NULL, object_key = ? WHERE id = ?'\n ).run(StorageTier.COLD, objectKey, row.id);\n }\n }\n\n private async retrieveFromColdStorage(objectKey: string): Promise<Buffer> {\n if (!this.s3Client) throw new Error('Object storage not configured');\n\n const response = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.config.objectStorage.bucket,\n Key: objectKey,\n })\n );\n\n if (!response.Body) throw new Error('Empty response from cold storage');\n\n // Convert stream to buffer\n const chunks: Buffer[] = [];\n const reader = response.Body as NodeJS.ReadableStream;\n \n return new Promise((resolve, reject) => {\n reader.on('data', (chunk: Buffer) => chunks.push(chunk));\n reader.on('end', () => resolve(Buffer.concat(chunks)));\n reader.on('error', reject);\n });\n }\n\n private async updateLastAccessed(id: string): Promise<void> {\n const now = new Date();\n \n if (this.pgPool) {\n await this.pgPool.query(\n 'UPDATE storage_items SET last_accessed = $1 WHERE id = $2',\n [now, id]\n );\n } else if (this.db) {\n this.db.prepare('UPDATE storage_items SET last_accessed = ? WHERE id = ?')\n .run(now.toISOString(), id);\n }\n }\n\n private startArchivalProcess(): void {\n // Run archival every 6 hours instead of complex real-time monitoring\n setInterval(async () => {\n try {\n await this.archiveOldItems();\n } catch (error) {\n logger.error('Archival process failed', { error });\n }\n }, 6 * 60 * 60 * 1000); // 6 hours\n\n logger.info('Started archival process', { intervalHours: 6 });\n }\n\n /**\n * Get storage statistics\n */\n async getStats(): Promise<{ hotItems: number; coldItems: number; totalSize: number }> {\n if (!this.isInitialized) await this.initialize();\n\n let hotItems = 0;\n let coldItems = 0;\n let totalSize = 0;\n\n if (this.pgPool) {\n const { rows } = await this.pgPool.query(`\n SELECT tier, COUNT(*) as count, SUM(length(data)) as size\n FROM storage_items\n GROUP BY tier\n `);\n \n for (const row of rows) {\n if (row.tier === StorageTier.HOT) {\n hotItems = parseInt(row.count);\n totalSize += parseInt(row.size || 0);\n } else {\n coldItems = parseInt(row.count);\n }\n }\n } else if (this.db) {\n const stmt = this.db.prepare(`\n SELECT tier, COUNT(*) as count, SUM(length(data)) as size\n FROM storage_items\n GROUP BY tier\n `);\n const rows = stmt.all();\n \n for (const row of rows) {\n if (row.tier === StorageTier.HOT) {\n hotItems = row.count;\n totalSize += row.size || 0;\n } else {\n coldItems = row.count;\n }\n }\n }\n\n return { hotItems, coldItems, totalSize };\n }\n\n async close(): Promise<void> {\n if (this.pgPool) {\n await this.pgPool.end();\n }\n if (this.db) {\n this.db.close();\n }\n this.isInitialized = false;\n logger.info('Simplified storage closed');\n }\n}\n\nexport default SimplifiedStorage;"],
5
+ "mappings": "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",
6
+ "names": ["StorageTier"]
7
+ }