@stackmemoryai/stackmemory 0.5.33 → 0.5.35

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 (111) hide show
  1. package/dist/agents/core/agent-task-manager.js.map +1 -1
  2. package/dist/cli/commands/clear.js +1 -1
  3. package/dist/cli/commands/clear.js.map +1 -1
  4. package/dist/cli/commands/context.js +1 -1
  5. package/dist/cli/commands/context.js.map +1 -1
  6. package/dist/cli/commands/dashboard.js.map +1 -1
  7. package/dist/cli/commands/discovery.js +1 -1
  8. package/dist/cli/commands/discovery.js.map +1 -1
  9. package/dist/cli/commands/handoff.js +1 -1
  10. package/dist/cli/commands/handoff.js.map +1 -1
  11. package/dist/cli/commands/monitor.js +1 -1
  12. package/dist/cli/commands/monitor.js.map +1 -1
  13. package/dist/cli/commands/quality.js +1 -1
  14. package/dist/cli/commands/quality.js.map +1 -1
  15. package/dist/cli/commands/skills.js +1 -1
  16. package/dist/cli/commands/skills.js.map +1 -1
  17. package/dist/cli/commands/workflow.js +1 -1
  18. package/dist/cli/commands/workflow.js.map +1 -1
  19. package/dist/cli/commands/worktree.js +1 -1
  20. package/dist/cli/commands/worktree.js.map +1 -1
  21. package/dist/cli/index.js +1 -1
  22. package/dist/cli/index.js.map +1 -1
  23. package/dist/core/context/auto-context.js.map +1 -1
  24. package/dist/core/context/compaction-handler.js.map +2 -2
  25. package/dist/core/context/context-bridge.js.map +2 -2
  26. package/dist/core/context/dual-stack-manager.js +1 -1
  27. package/dist/core/context/dual-stack-manager.js.map +1 -1
  28. package/dist/core/context/enhanced-rehydration.js.map +1 -1
  29. package/dist/core/context/frame-database.js +43 -10
  30. package/dist/core/context/frame-database.js.map +2 -2
  31. package/dist/core/context/frame-handoff-manager.js.map +1 -1
  32. package/dist/core/context/frame-lifecycle-hooks.js +119 -0
  33. package/dist/core/context/frame-lifecycle-hooks.js.map +7 -0
  34. package/dist/core/context/frame-stack.js +36 -7
  35. package/dist/core/context/frame-stack.js.map +2 -2
  36. package/dist/core/context/incremental-gc.js.map +2 -2
  37. package/dist/core/context/index.js +4 -22
  38. package/dist/core/context/index.js.map +2 -2
  39. package/dist/core/context/refactored-frame-manager.js +170 -37
  40. package/dist/core/context/refactored-frame-manager.js.map +3 -3
  41. package/dist/core/context/shared-context-layer.js.map +1 -1
  42. package/dist/core/context/stack-merge-resolver.js.map +1 -1
  43. package/dist/core/database/database-adapter.js.map +1 -1
  44. package/dist/core/database/paradedb-adapter.js.map +1 -1
  45. package/dist/core/database/query-router.js.map +1 -1
  46. package/dist/core/database/sqlite-adapter.js.map +1 -1
  47. package/dist/core/digest/frame-digest-integration.js.map +1 -1
  48. package/dist/core/digest/hybrid-digest-generator.js.map +1 -1
  49. package/dist/core/digest/types.js.map +1 -1
  50. package/dist/core/errors/index.js +249 -0
  51. package/dist/core/errors/index.js.map +2 -2
  52. package/dist/core/frame/workflow-templates.js.map +2 -2
  53. package/dist/core/merge/conflict-detector.js.map +1 -1
  54. package/dist/core/merge/resolution-engine.js.map +1 -1
  55. package/dist/core/merge/stack-diff.js.map +1 -1
  56. package/dist/core/models/model-router.js +10 -1
  57. package/dist/core/models/model-router.js.map +2 -2
  58. package/dist/core/monitoring/error-handler.js +37 -270
  59. package/dist/core/monitoring/error-handler.js.map +3 -3
  60. package/dist/core/monitoring/session-monitor.js.map +1 -1
  61. package/dist/core/performance/lazy-context-loader.js.map +1 -1
  62. package/dist/core/performance/optimized-frame-context.js.map +1 -1
  63. package/dist/core/retrieval/context-retriever.js.map +1 -1
  64. package/dist/core/retrieval/graph-retrieval.js.map +1 -1
  65. package/dist/core/retrieval/hierarchical-retrieval.js.map +1 -1
  66. package/dist/core/retrieval/llm-context-retrieval.js.map +1 -1
  67. package/dist/core/retrieval/retrieval-benchmarks.js.map +1 -1
  68. package/dist/core/retrieval/summary-generator.js.map +1 -1
  69. package/dist/core/retrieval/types.js.map +1 -1
  70. package/dist/core/storage/chromadb-adapter.js.map +1 -1
  71. package/dist/core/storage/infinite-storage.js.map +1 -1
  72. package/dist/core/storage/two-tier-storage.js.map +1 -1
  73. package/dist/features/tasks/task-aware-context.js.map +1 -1
  74. package/dist/features/web/server/index.js +1 -1
  75. package/dist/features/web/server/index.js.map +1 -1
  76. package/dist/hooks/schemas.js +50 -0
  77. package/dist/hooks/schemas.js.map +2 -2
  78. package/dist/hooks/sms-action-runner.js +47 -1
  79. package/dist/hooks/sms-action-runner.js.map +2 -2
  80. package/dist/hooks/sms-notify.js +63 -1
  81. package/dist/hooks/sms-notify.js.map +2 -2
  82. package/dist/hooks/sms-webhook.js +10 -3
  83. package/dist/hooks/sms-webhook.js.map +2 -2
  84. package/dist/hooks/whatsapp-commands.js +172 -69
  85. package/dist/hooks/whatsapp-commands.js.map +2 -2
  86. package/dist/hooks/whatsapp-sync.js +34 -0
  87. package/dist/hooks/whatsapp-sync.js.map +2 -2
  88. package/dist/index.js +1 -1
  89. package/dist/index.js.map +1 -1
  90. package/dist/integrations/mcp/handlers/context-handlers.js.map +1 -1
  91. package/dist/integrations/mcp/handlers/discovery-handlers.js.map +1 -1
  92. package/dist/integrations/mcp/server.js +1 -1
  93. package/dist/integrations/mcp/server.js.map +1 -1
  94. package/dist/integrations/ralph/bridge/ralph-stackmemory-bridge.js +1 -1
  95. package/dist/integrations/ralph/bridge/ralph-stackmemory-bridge.js.map +1 -1
  96. package/dist/integrations/ralph/context/stackmemory-context-loader.js +1 -1
  97. package/dist/integrations/ralph/context/stackmemory-context-loader.js.map +1 -1
  98. package/dist/integrations/ralph/learning/pattern-learner.js +1 -1
  99. package/dist/integrations/ralph/learning/pattern-learner.js.map +1 -1
  100. package/dist/integrations/ralph/orchestration/multi-loop-orchestrator.js +1 -1
  101. package/dist/integrations/ralph/orchestration/multi-loop-orchestrator.js.map +1 -1
  102. package/dist/integrations/ralph/swarm/swarm-coordinator.js +1 -1
  103. package/dist/integrations/ralph/swarm/swarm-coordinator.js.map +1 -1
  104. package/dist/integrations/ralph/visualization/ralph-debugger.js +1 -1
  105. package/dist/integrations/ralph/visualization/ralph-debugger.js.map +1 -1
  106. package/dist/mcp/stackmemory-mcp-server.js +1 -1
  107. package/dist/mcp/stackmemory-mcp-server.js.map +1 -1
  108. package/dist/skills/claude-skills.js.map +1 -1
  109. package/dist/skills/recursive-agent-orchestrator.js.map +1 -1
  110. package/dist/skills/unified-rlm-orchestrator.js.map +1 -1
  111. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/retrieval/llm-context-retrieval.ts"],
4
- "sourcesContent": ["/**\n * LLM-Driven Context Retrieval System\n * Uses LLM analysis to intelligently select relevant context\n */\n\nimport Database from 'better-sqlite3';\nimport {\n FrameManager,\n Frame,\n Anchor,\n Event,\n} from '../context/frame-manager.js';\nimport { QueryParser, StackMemoryQuery } from '../query/query-parser.js';\nimport { CompressedSummaryGenerator } from './summary-generator.js';\nimport {\n CompressedSummary,\n LLMAnalysisRequest,\n LLMAnalysisResponse,\n RetrievedContext,\n FrameRetrievalPlan,\n ContextRecommendation,\n RetrievalConfig,\n DEFAULT_RETRIEVAL_CONFIG,\n RetrievalHints,\n RetrievalMetadata,\n} from './types.js';\nimport { logger } from '../monitoring/logger.js';\nimport { LazyContextLoader } from '../performance/lazy-context-loader.js';\nimport { ContextCache } from '../performance/context-cache.js';\nimport { LLMProvider, createLLMProvider } from './llm-provider.js';\nimport { RetrievalAuditStore } from './retrieval-audit.js';\n\n// Re-export LLMProvider type for external use\nexport type { LLMProvider } from './llm-provider.js';\n\n/**\n * LLM provider interface for context analysis\n * @deprecated Use import from './llm-provider.js' instead\n */\nexport interface LLMProviderInterface {\n analyze(prompt: string, maxTokens: number): Promise<string>;\n}\n\n/**\n * Simple heuristic-based fallback when LLM is unavailable\n */\nclass HeuristicAnalyzer {\n analyze(\n query: string,\n summary: CompressedSummary,\n parsedQuery?: StackMemoryQuery\n ): LLMAnalysisResponse {\n const framesToRetrieve: FrameRetrievalPlan[] = [];\n const recommendations: ContextRecommendation[] = [];\n const matchedPatterns: string[] = [];\n\n // Score frames based on query relevance\n const queryLower = query.toLowerCase();\n const queryWords = queryLower.split(/\\W+/).filter((w) => w.length > 2);\n\n for (const frame of summary.recentSession.frames) {\n let priority = 5; // Base priority\n const reasons: string[] = [];\n\n // Recency boost\n const ageHours = (Date.now() - frame.createdAt) / (1000 * 60 * 60);\n if (ageHours < 1) {\n priority += 3;\n reasons.push('very recent');\n } else if (ageHours < 6) {\n priority += 2;\n reasons.push('recent');\n }\n\n // Score boost\n priority += Math.floor(frame.score * 3);\n\n // Name matching\n const nameLower = frame.name.toLowerCase();\n const nameMatches = queryWords.filter((w) => nameLower.includes(w));\n if (nameMatches.length > 0) {\n priority += nameMatches.length * 2;\n reasons.push(`matches: ${nameMatches.join(', ')}`);\n matchedPatterns.push(`name_match:${nameMatches.join(',')}`);\n }\n\n // Type matching from parsed query\n if (parsedQuery?.frame?.type) {\n const frameType = frame.type.toLowerCase();\n if (parsedQuery.frame.type.some((t) => t.toLowerCase() === frameType)) {\n priority += 2;\n reasons.push('type match');\n }\n }\n\n // Topic matching\n if (parsedQuery?.content?.topic) {\n const topics = parsedQuery.content.topic;\n const topicMatches = topics.filter(\n (t) =>\n nameLower.includes(t.toLowerCase()) ||\n (frame.digestPreview &&\n frame.digestPreview.toLowerCase().includes(t.toLowerCase()))\n );\n if (topicMatches.length > 0) {\n priority += topicMatches.length;\n reasons.push(`topic: ${topicMatches.join(', ')}`);\n }\n }\n\n // Cap priority at 10\n priority = Math.min(priority, 10);\n\n if (priority >= 5) {\n framesToRetrieve.push({\n frameId: frame.frameId,\n priority,\n reason: reasons.length > 0 ? reasons.join('; ') : 'relevant context',\n includeEvents: priority >= 7,\n includeAnchors: true,\n includeDigest: true,\n estimatedTokens: this.estimateFrameTokens(frame),\n });\n }\n }\n\n // Sort by priority\n framesToRetrieve.sort((a, b) => b.priority - a.priority);\n\n // Generate recommendations based on errors\n if (summary.recentSession.errorsEncountered.length > 0) {\n recommendations.push({\n type: 'include',\n target: 'error_context',\n reason: `${summary.recentSession.errorsEncountered.length} errors encountered recently`,\n impact: 'medium',\n });\n }\n\n // Recommend including decisions if query seems decision-related\n if (\n queryLower.includes('decision') ||\n queryLower.includes('why') ||\n queryLower.includes('chose')\n ) {\n recommendations.push({\n type: 'include',\n target: 'decisions',\n reason: 'Query appears to be about past decisions',\n impact: 'high',\n });\n }\n\n // Calculate confidence based on match quality\n const avgPriority =\n framesToRetrieve.length > 0\n ? framesToRetrieve.reduce((sum, f) => sum + f.priority, 0) /\n framesToRetrieve.length\n : 0;\n const confidenceScore = Math.min(avgPriority / 10, 0.95);\n\n // Generate reasoning\n const reasoning = this.generateReasoning(\n query,\n framesToRetrieve,\n summary,\n matchedPatterns\n );\n\n return {\n reasoning,\n framesToRetrieve: framesToRetrieve.slice(0, 10), // Limit to top 10\n confidenceScore,\n recommendations,\n metadata: {\n analysisTimeMs: 0, // Will be set by caller\n summaryTokens: this.estimateSummaryTokens(summary),\n queryComplexity: this.assessQueryComplexity(query, parsedQuery),\n matchedPatterns,\n fallbackUsed: true,\n },\n };\n }\n\n private estimateFrameTokens(frame: {\n eventCount: number;\n anchorCount: number;\n digestPreview?: string;\n }): number {\n let tokens = 50; // Base frame header\n tokens += frame.eventCount * 30; // Estimate per event\n tokens += frame.anchorCount * 40; // Estimate per anchor\n if (frame.digestPreview) tokens += frame.digestPreview.length / 4;\n return Math.floor(tokens);\n }\n\n private estimateSummaryTokens(summary: CompressedSummary): number {\n return Math.floor(JSON.stringify(summary).length / 4);\n }\n\n private assessQueryComplexity(\n query: string,\n parsedQuery?: StackMemoryQuery\n ): 'simple' | 'moderate' | 'complex' {\n const wordCount = query.split(/\\s+/).length;\n const hasTimeFilter = !!parsedQuery?.time;\n const hasContentFilter = !!parsedQuery?.content;\n const hasPeopleFilter = !!parsedQuery?.people;\n const hasFrameFilter = !!parsedQuery?.frame;\n\n const filterCount = [\n hasTimeFilter,\n hasContentFilter,\n hasPeopleFilter,\n hasFrameFilter,\n ].filter(Boolean).length;\n\n if (wordCount <= 5 && filterCount <= 1) return 'simple';\n if (wordCount <= 15 && filterCount <= 2) return 'moderate';\n return 'complex';\n }\n\n private generateReasoning(\n query: string,\n frames: FrameRetrievalPlan[],\n summary: CompressedSummary,\n matchedPatterns: string[]\n ): string {\n const parts: string[] = [];\n\n parts.push(`Query: \"${query}\"`);\n parts.push(\n `Analyzed ${summary.recentSession.frames.length} recent frames.`\n );\n\n if (matchedPatterns.length > 0) {\n parts.push(`Matched patterns: ${matchedPatterns.join(', ')}`);\n }\n\n if (frames.length > 0) {\n parts.push(`Selected ${frames.length} frames for retrieval.`);\n const topFrames = frames.slice(0, 3);\n parts.push(\n `Top frames: ${topFrames.map((f) => `${f.frameId} (priority: ${f.priority})`).join(', ')}`\n );\n } else {\n parts.push('No highly relevant frames found. Using general context.');\n }\n\n return parts.join(' ');\n }\n}\n\n/**\n * Main LLM Context Retrieval class\n */\nexport class LLMContextRetrieval {\n private db: Database.Database;\n private frameManager: FrameManager;\n private summaryGenerator: CompressedSummaryGenerator;\n private queryParser: QueryParser;\n private heuristicAnalyzer: HeuristicAnalyzer;\n private llmProvider?: LLMProvider;\n private config: RetrievalConfig;\n private projectId: string;\n private lazyLoader: LazyContextLoader;\n private contextCache: ContextCache<RetrievedContext>;\n private auditStore: RetrievalAuditStore;\n private enableAudit: boolean;\n\n constructor(\n db: Database.Database,\n frameManager: FrameManager,\n projectId: string,\n config: Partial<RetrievalConfig> = {},\n llmProvider?: LLMProvider\n ) {\n this.db = db;\n this.frameManager = frameManager;\n this.projectId = projectId;\n this.config = { ...DEFAULT_RETRIEVAL_CONFIG, ...config };\n\n // Auto-create LLM provider if not provided and API key is available\n this.llmProvider = llmProvider ?? createLLMProvider();\n if (this.llmProvider) {\n logger.info('LLM provider configured for context retrieval', {\n projectId,\n provider: this.config.llmConfig.provider,\n });\n }\n\n this.summaryGenerator = new CompressedSummaryGenerator(\n db,\n frameManager,\n projectId,\n config\n );\n this.queryParser = new QueryParser();\n this.heuristicAnalyzer = new HeuristicAnalyzer();\n\n // Initialize audit store\n this.auditStore = new RetrievalAuditStore(db, projectId);\n this.enableAudit = true; // Can be made configurable\n\n // Initialize performance optimizations\n this.lazyLoader = new LazyContextLoader(db, projectId);\n this.contextCache = new ContextCache<RetrievedContext>({\n maxSize: 50 * 1024 * 1024, // 50MB for context cache\n maxItems: 100,\n defaultTTL: 600000, // 10 minutes\n });\n\n // Start cache cleanup\n this.contextCache.startCleanup(60000);\n }\n\n /**\n * Get the audit store for external access\n */\n getAuditStore(): RetrievalAuditStore {\n return this.auditStore;\n }\n\n /**\n * Check if LLM provider is available\n */\n hasLLMProvider(): boolean {\n return !!this.llmProvider;\n }\n\n /**\n * Retrieve context based on query using LLM analysis (with caching)\n */\n public async retrieveContext(\n query: string,\n options: {\n tokenBudget?: number;\n hints?: RetrievalHints;\n forceRefresh?: boolean;\n } = {}\n ): Promise<RetrievedContext> {\n const startTime = Date.now();\n const tokenBudget = options.tokenBudget || this.config.defaultTokenBudget;\n\n // Check cache first unless force refresh\n if (!options.forceRefresh) {\n const cacheKey = `${query}:${tokenBudget}:${JSON.stringify(options.hints || {})}`;\n const cached = this.contextCache.get(cacheKey);\n if (cached) {\n logger.debug('Context cache hit', {\n query: query.substring(0, 50),\n cacheStats: this.contextCache.getStats(),\n });\n return cached;\n }\n }\n\n logger.info('Starting context retrieval', {\n projectId: this.projectId,\n query: query.substring(0, 100),\n tokenBudget,\n });\n\n // 1. Parse the query\n const parsedQuery = this.queryParser.parseNaturalLanguage(query);\n\n // 2. Generate compressed summary\n const summary = this.summaryGenerator.generateSummary({\n forceRefresh: options.forceRefresh,\n });\n\n // 3. Perform LLM analysis\n const analysis = await this.analyzeWithLLM({\n currentQuery: query,\n parsedQuery,\n compressedSummary: summary,\n tokenBudget,\n hints: options.hints,\n });\n\n // 4. Retrieve frames based on analysis\n const { frames, anchors, events, tokensUsed } = await this.retrieveFrames(\n analysis,\n tokenBudget\n );\n\n // 5. Assemble context string\n const context = this.assembleContext(frames, anchors, events, analysis);\n\n const metadata: RetrievalMetadata = {\n retrievalTimeMs: Date.now() - startTime,\n cacheHit: false, // Would need cache tracking\n framesScanned: summary.recentSession.frames.length,\n framesIncluded: frames.length,\n compressionRatio: tokensUsed > 0 ? tokenBudget / tokensUsed : 1,\n };\n\n logger.info('Context retrieval complete', {\n projectId: this.projectId,\n framesIncluded: frames.length,\n tokensUsed,\n retrievalTimeMs: metadata.retrievalTimeMs,\n confidence: analysis.confidenceScore,\n });\n\n const result: RetrievedContext = {\n context,\n frames,\n anchors,\n events,\n analysis,\n tokenUsage: {\n budget: tokenBudget,\n used: tokensUsed,\n remaining: tokenBudget - tokensUsed,\n },\n metadata,\n };\n\n // Record audit entry\n if (this.enableAudit) {\n const provider = analysis.metadata.fallbackUsed\n ? 'heuristic'\n : this.llmProvider\n ? 'anthropic'\n : 'heuristic';\n this.auditStore.record(query, analysis, {\n tokensUsed,\n tokenBudget,\n provider,\n });\n }\n\n // Cache the result\n if (!options.forceRefresh) {\n const cacheKey = `${query}:${tokenBudget}:${JSON.stringify(options.hints || {})}`;\n this.contextCache.set(cacheKey, result, {\n ttl: 600000, // 10 minutes\n });\n }\n\n return result;\n }\n\n /**\n * Perform LLM analysis or fall back to heuristics\n */\n private async analyzeWithLLM(\n request: LLMAnalysisRequest\n ): Promise<LLMAnalysisResponse> {\n const startTime = Date.now();\n\n // Try LLM analysis if provider is available\n if (this.llmProvider) {\n try {\n const prompt = this.buildAnalysisPrompt(request);\n const response = await this.llmProvider.analyze(\n prompt,\n this.config.llmConfig.maxTokens\n );\n const analysis = this.parseAnalysisResponse(response, request);\n analysis.metadata.analysisTimeMs = Date.now() - startTime;\n analysis.metadata.fallbackUsed = false;\n\n // Validate confidence threshold\n if (analysis.confidenceScore >= this.config.minConfidenceThreshold) {\n return analysis;\n }\n\n logger.warn('LLM confidence below threshold, using fallback', {\n confidence: analysis.confidenceScore,\n threshold: this.config.minConfidenceThreshold,\n });\n } catch (error: any) {\n logger.error(\n 'LLM analysis failed, using fallback',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n // Fall back to heuristic analysis\n if (this.config.enableFallback) {\n const analysis = this.heuristicAnalyzer.analyze(\n request.currentQuery,\n request.compressedSummary,\n request.parsedQuery\n );\n analysis.metadata.analysisTimeMs = Date.now() - startTime;\n return analysis;\n }\n\n // Return empty analysis if no fallback\n return {\n reasoning:\n 'Unable to perform analysis - LLM unavailable and fallback disabled',\n framesToRetrieve: [],\n confidenceScore: 0,\n recommendations: [],\n metadata: {\n analysisTimeMs: Date.now() - startTime,\n summaryTokens: 0,\n queryComplexity: 'simple',\n matchedPatterns: [],\n fallbackUsed: false,\n },\n };\n }\n\n /**\n * Build the prompt for LLM analysis\n */\n private buildAnalysisPrompt(request: LLMAnalysisRequest): string {\n const summary = request.compressedSummary;\n\n return `You are analyzing a code project's memory to retrieve relevant context.\n\n## Current Query\n\"${request.currentQuery}\"\n\n## Token Budget\n${request.tokenBudget} tokens available\n\n## Recent Session Summary\n- Frames: ${summary.recentSession.frames.length}\n- Time range: ${new Date(summary.recentSession.timeRange.start).toISOString()} to ${new Date(summary.recentSession.timeRange.end).toISOString()}\n- Dominant operations: ${summary.recentSession.dominantOperations.map((o) => `${o.operation}(${o.count})`).join(', ')}\n- Files touched: ${summary.recentSession.filesTouched\n .slice(0, 5)\n .map((f) => f.path)\n .join(', ')}\n- Errors: ${summary.recentSession.errorsEncountered.length}\n\n## Available Frames\n${summary.recentSession.frames\n .slice(0, 15)\n .map(\n (f) =>\n `- ${f.frameId}: \"${f.name}\" (${f.type}, score: ${f.score.toFixed(2)}, events: ${f.eventCount})`\n )\n .join('\\n')}\n\n## Key Decisions\n${summary.historicalPatterns.keyDecisions\n .slice(0, 5)\n .map((d) => `- ${d.text.substring(0, 80)}...`)\n .join('\\n')}\n\n## Task\nAnalyze the query and select the most relevant frames to retrieve.\nReturn a JSON object with:\n{\n \"reasoning\": \"Your analysis of why these frames are relevant\",\n \"framesToRetrieve\": [\n {\"frameId\": \"...\", \"priority\": 1-10, \"reason\": \"...\", \"includeEvents\": true/false, \"includeAnchors\": true/false}\n ],\n \"confidenceScore\": 0.0-1.0,\n \"recommendations\": [{\"type\": \"include/exclude/summarize\", \"target\": \"...\", \"reason\": \"...\", \"impact\": \"low/medium/high\"}]\n}\n\n${request.hints ? `\\n## Hints\\n${JSON.stringify(request.hints)}` : ''}\n\nRespond with only the JSON object, no other text.`;\n }\n\n /**\n * Parse LLM response into structured analysis\n */\n private parseAnalysisResponse(\n response: string,\n request: LLMAnalysisRequest\n ): LLMAnalysisResponse {\n try {\n // Extract JSON from response (handle markdown code blocks)\n let jsonStr = response;\n const jsonMatch = response.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n if (jsonMatch) {\n jsonStr = jsonMatch[1];\n }\n\n const parsed = JSON.parse(jsonStr.trim());\n\n // Validate and normalize the response\n return {\n reasoning: parsed.reasoning || 'No reasoning provided',\n framesToRetrieve: (parsed.framesToRetrieve || []).map((f: any) => ({\n frameId: f.frameId,\n priority: Math.min(10, Math.max(1, f.priority || 5)),\n reason: f.reason || 'Selected by LLM',\n includeEvents: f.includeEvents ?? true,\n includeAnchors: f.includeAnchors ?? true,\n includeDigest: f.includeDigest ?? true,\n estimatedTokens: f.estimatedTokens || 100,\n })),\n confidenceScore: Math.min(\n 1,\n Math.max(0, parsed.confidenceScore || 0.5)\n ),\n recommendations: (parsed.recommendations || []).map((r: any) => ({\n type: r.type || 'include',\n target: r.target || '',\n reason: r.reason || '',\n impact: r.impact || 'medium',\n })),\n metadata: {\n analysisTimeMs: 0,\n summaryTokens: Math.floor(\n JSON.stringify(request.compressedSummary).length / 4\n ),\n queryComplexity: this.assessQueryComplexity(request.currentQuery),\n matchedPatterns: [],\n fallbackUsed: false,\n },\n };\n } catch (error: unknown) {\n logger.warn('Failed to parse LLM response, using fallback', {\n error,\n response,\n });\n return this.heuristicAnalyzer.analyze(\n request.currentQuery,\n request.compressedSummary,\n request.parsedQuery\n );\n }\n }\n\n private assessQueryComplexity(\n query: string\n ): 'simple' | 'moderate' | 'complex' {\n const wordCount = query.split(/\\s+/).length;\n if (wordCount <= 5) return 'simple';\n if (wordCount <= 15) return 'moderate';\n return 'complex';\n }\n\n /**\n * Retrieve frames based on analysis (with lazy loading)\n */\n private async retrieveFrames(\n analysis: LLMAnalysisResponse,\n tokenBudget: number\n ): Promise<{\n frames: Frame[];\n anchors: Anchor[];\n events: Event[];\n tokensUsed: number;\n }> {\n const frames: Frame[] = [];\n const anchors: Anchor[] = [];\n const events: Event[] = [];\n let tokensUsed = 0;\n\n // Preload frames for better performance\n const frameIds = analysis.framesToRetrieve.map((p: any) => p.frameId);\n await this.lazyLoader.preloadContext(frameIds, {\n parallel: true,\n depth: 2, // Load frames, anchors, and events\n });\n\n // Retrieve frames in priority order within budget\n for (const plan of analysis.framesToRetrieve) {\n if (tokensUsed + plan.estimatedTokens > tokenBudget) {\n logger.debug('Token budget exceeded, stopping retrieval', {\n tokensUsed,\n budget: tokenBudget,\n });\n break;\n }\n\n // Use lazy loader for efficient retrieval\n try {\n const frame = await this.lazyLoader.lazyFrame(plan.frameId).get();\n frames.push(frame);\n tokensUsed += 50; // Base frame tokens\n\n // Include anchors if requested\n if (plan.includeAnchors) {\n const frameAnchors = await this.lazyLoader\n .lazyAnchors(plan.frameId)\n .get();\n anchors.push(...frameAnchors);\n tokensUsed += frameAnchors.length * 40;\n }\n\n // Include events if requested\n if (plan.includeEvents) {\n const frameEvents = await this.lazyLoader\n .lazyEvents(plan.frameId, 10)\n .get();\n events.push(...frameEvents);\n tokensUsed += frameEvents.length * 30;\n }\n } catch (error: unknown) {\n logger.warn('Failed to retrieve frame', {\n frameId: plan.frameId,\n error,\n });\n }\n }\n\n return { frames, anchors, events, tokensUsed };\n }\n\n private getFrameAnchors(frameId: string): Anchor[] {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT * FROM anchors WHERE frame_id = ?\n ORDER BY priority DESC, created_at DESC\n `\n )\n .all(frameId) as any[];\n\n return rows.map((row) => ({\n ...row,\n metadata: JSON.parse(row.metadata || '{}'),\n }));\n } catch {\n return [];\n }\n }\n\n /**\n * Assemble final context string\n */\n private assembleContext(\n frames: Frame[],\n anchors: Anchor[],\n events: Event[],\n analysis: LLMAnalysisResponse\n ): string {\n const sections: string[] = [];\n\n // Add retrieval reasoning (auditable)\n sections.push('## Context Retrieval Analysis');\n sections.push(\n `*Confidence: ${(analysis.confidenceScore * 100).toFixed(0)}%*`\n );\n sections.push(analysis.reasoning);\n sections.push('');\n\n // Add frames\n if (frames.length > 0) {\n sections.push('## Relevant Frames');\n for (const frame of frames) {\n sections.push(`### ${frame.name} (${frame.type})`);\n if (frame.digest_text) {\n sections.push(frame.digest_text);\n }\n sections.push('');\n }\n }\n\n // Add key anchors\n const decisions = anchors.filter((a) => a.type === 'DECISION');\n const constraints = anchors.filter((a) => a.type === 'CONSTRAINT');\n const facts = anchors.filter((a) => a.type === 'FACT');\n\n if (decisions.length > 0) {\n sections.push('## Key Decisions');\n for (const d of decisions.slice(0, 5)) {\n sections.push(`- ${d.text}`);\n }\n sections.push('');\n }\n\n if (constraints.length > 0) {\n sections.push('## Active Constraints');\n for (const c of constraints.slice(0, 5)) {\n sections.push(`- ${c.text}`);\n }\n sections.push('');\n }\n\n if (facts.length > 0) {\n sections.push('## Important Facts');\n for (const f of facts.slice(0, 5)) {\n sections.push(`- ${f.text}`);\n }\n sections.push('');\n }\n\n // Add recent events summary\n if (events.length > 0) {\n sections.push('## Recent Activity');\n const eventSummary = this.summarizeEvents(events);\n sections.push(eventSummary);\n sections.push('');\n }\n\n // Add recommendations\n if (analysis.recommendations.length > 0) {\n sections.push('## Recommendations');\n for (const rec of analysis.recommendations) {\n const icon =\n rec.type === 'include' ? '+' : rec.type === 'exclude' ? '-' : '~';\n sections.push(`${icon} [${rec.impact.toUpperCase()}] ${rec.reason}`);\n }\n }\n\n return sections.join('\\n');\n }\n\n private summarizeEvents(events: Event[]): string {\n const byType: Record<string, number> = {};\n for (const event of events) {\n byType[event.event_type] = (byType[event.event_type] || 0) + 1;\n }\n\n return Object.entries(byType)\n .map(([type, count]) => `- ${type}: ${count} occurrences`)\n .join('\\n');\n }\n\n /**\n * Get just the compressed summary (useful for external analysis)\n */\n public getSummary(forceRefresh = false): CompressedSummary {\n return this.summaryGenerator.generateSummary({ forceRefresh });\n }\n\n /**\n * Set LLM provider\n */\n public setLLMProvider(provider: LLMProvider): void {\n this.llmProvider = provider;\n }\n\n /**\n * Clear all caches\n */\n public clearCache(): void {\n this.summaryGenerator.clearCache();\n this.lazyLoader.clearCache();\n this.contextCache.clear();\n logger.info('Cleared all caches', {\n projectId: this.projectId,\n cacheStats: this.contextCache.getStats(),\n });\n }\n}\n"],
4
+ "sourcesContent": ["/**\n * LLM-Driven Context Retrieval System\n * Uses LLM analysis to intelligently select relevant context\n */\n\nimport Database from 'better-sqlite3';\nimport {\n FrameManager,\n Frame,\n Anchor,\n Event,\n} from '../context/index.js';\nimport { QueryParser, StackMemoryQuery } from '../query/query-parser.js';\nimport { CompressedSummaryGenerator } from './summary-generator.js';\nimport {\n CompressedSummary,\n LLMAnalysisRequest,\n LLMAnalysisResponse,\n RetrievedContext,\n FrameRetrievalPlan,\n ContextRecommendation,\n RetrievalConfig,\n DEFAULT_RETRIEVAL_CONFIG,\n RetrievalHints,\n RetrievalMetadata,\n} from './types.js';\nimport { logger } from '../monitoring/logger.js';\nimport { LazyContextLoader } from '../performance/lazy-context-loader.js';\nimport { ContextCache } from '../performance/context-cache.js';\nimport { LLMProvider, createLLMProvider } from './llm-provider.js';\nimport { RetrievalAuditStore } from './retrieval-audit.js';\n\n// Re-export LLMProvider type for external use\nexport type { LLMProvider } from './llm-provider.js';\n\n/**\n * LLM provider interface for context analysis\n * @deprecated Use import from './llm-provider.js' instead\n */\nexport interface LLMProviderInterface {\n analyze(prompt: string, maxTokens: number): Promise<string>;\n}\n\n/**\n * Simple heuristic-based fallback when LLM is unavailable\n */\nclass HeuristicAnalyzer {\n analyze(\n query: string,\n summary: CompressedSummary,\n parsedQuery?: StackMemoryQuery\n ): LLMAnalysisResponse {\n const framesToRetrieve: FrameRetrievalPlan[] = [];\n const recommendations: ContextRecommendation[] = [];\n const matchedPatterns: string[] = [];\n\n // Score frames based on query relevance\n const queryLower = query.toLowerCase();\n const queryWords = queryLower.split(/\\W+/).filter((w) => w.length > 2);\n\n for (const frame of summary.recentSession.frames) {\n let priority = 5; // Base priority\n const reasons: string[] = [];\n\n // Recency boost\n const ageHours = (Date.now() - frame.createdAt) / (1000 * 60 * 60);\n if (ageHours < 1) {\n priority += 3;\n reasons.push('very recent');\n } else if (ageHours < 6) {\n priority += 2;\n reasons.push('recent');\n }\n\n // Score boost\n priority += Math.floor(frame.score * 3);\n\n // Name matching\n const nameLower = frame.name.toLowerCase();\n const nameMatches = queryWords.filter((w) => nameLower.includes(w));\n if (nameMatches.length > 0) {\n priority += nameMatches.length * 2;\n reasons.push(`matches: ${nameMatches.join(', ')}`);\n matchedPatterns.push(`name_match:${nameMatches.join(',')}`);\n }\n\n // Type matching from parsed query\n if (parsedQuery?.frame?.type) {\n const frameType = frame.type.toLowerCase();\n if (parsedQuery.frame.type.some((t) => t.toLowerCase() === frameType)) {\n priority += 2;\n reasons.push('type match');\n }\n }\n\n // Topic matching\n if (parsedQuery?.content?.topic) {\n const topics = parsedQuery.content.topic;\n const topicMatches = topics.filter(\n (t) =>\n nameLower.includes(t.toLowerCase()) ||\n (frame.digestPreview &&\n frame.digestPreview.toLowerCase().includes(t.toLowerCase()))\n );\n if (topicMatches.length > 0) {\n priority += topicMatches.length;\n reasons.push(`topic: ${topicMatches.join(', ')}`);\n }\n }\n\n // Cap priority at 10\n priority = Math.min(priority, 10);\n\n if (priority >= 5) {\n framesToRetrieve.push({\n frameId: frame.frameId,\n priority,\n reason: reasons.length > 0 ? reasons.join('; ') : 'relevant context',\n includeEvents: priority >= 7,\n includeAnchors: true,\n includeDigest: true,\n estimatedTokens: this.estimateFrameTokens(frame),\n });\n }\n }\n\n // Sort by priority\n framesToRetrieve.sort((a, b) => b.priority - a.priority);\n\n // Generate recommendations based on errors\n if (summary.recentSession.errorsEncountered.length > 0) {\n recommendations.push({\n type: 'include',\n target: 'error_context',\n reason: `${summary.recentSession.errorsEncountered.length} errors encountered recently`,\n impact: 'medium',\n });\n }\n\n // Recommend including decisions if query seems decision-related\n if (\n queryLower.includes('decision') ||\n queryLower.includes('why') ||\n queryLower.includes('chose')\n ) {\n recommendations.push({\n type: 'include',\n target: 'decisions',\n reason: 'Query appears to be about past decisions',\n impact: 'high',\n });\n }\n\n // Calculate confidence based on match quality\n const avgPriority =\n framesToRetrieve.length > 0\n ? framesToRetrieve.reduce((sum, f) => sum + f.priority, 0) /\n framesToRetrieve.length\n : 0;\n const confidenceScore = Math.min(avgPriority / 10, 0.95);\n\n // Generate reasoning\n const reasoning = this.generateReasoning(\n query,\n framesToRetrieve,\n summary,\n matchedPatterns\n );\n\n return {\n reasoning,\n framesToRetrieve: framesToRetrieve.slice(0, 10), // Limit to top 10\n confidenceScore,\n recommendations,\n metadata: {\n analysisTimeMs: 0, // Will be set by caller\n summaryTokens: this.estimateSummaryTokens(summary),\n queryComplexity: this.assessQueryComplexity(query, parsedQuery),\n matchedPatterns,\n fallbackUsed: true,\n },\n };\n }\n\n private estimateFrameTokens(frame: {\n eventCount: number;\n anchorCount: number;\n digestPreview?: string;\n }): number {\n let tokens = 50; // Base frame header\n tokens += frame.eventCount * 30; // Estimate per event\n tokens += frame.anchorCount * 40; // Estimate per anchor\n if (frame.digestPreview) tokens += frame.digestPreview.length / 4;\n return Math.floor(tokens);\n }\n\n private estimateSummaryTokens(summary: CompressedSummary): number {\n return Math.floor(JSON.stringify(summary).length / 4);\n }\n\n private assessQueryComplexity(\n query: string,\n parsedQuery?: StackMemoryQuery\n ): 'simple' | 'moderate' | 'complex' {\n const wordCount = query.split(/\\s+/).length;\n const hasTimeFilter = !!parsedQuery?.time;\n const hasContentFilter = !!parsedQuery?.content;\n const hasPeopleFilter = !!parsedQuery?.people;\n const hasFrameFilter = !!parsedQuery?.frame;\n\n const filterCount = [\n hasTimeFilter,\n hasContentFilter,\n hasPeopleFilter,\n hasFrameFilter,\n ].filter(Boolean).length;\n\n if (wordCount <= 5 && filterCount <= 1) return 'simple';\n if (wordCount <= 15 && filterCount <= 2) return 'moderate';\n return 'complex';\n }\n\n private generateReasoning(\n query: string,\n frames: FrameRetrievalPlan[],\n summary: CompressedSummary,\n matchedPatterns: string[]\n ): string {\n const parts: string[] = [];\n\n parts.push(`Query: \"${query}\"`);\n parts.push(\n `Analyzed ${summary.recentSession.frames.length} recent frames.`\n );\n\n if (matchedPatterns.length > 0) {\n parts.push(`Matched patterns: ${matchedPatterns.join(', ')}`);\n }\n\n if (frames.length > 0) {\n parts.push(`Selected ${frames.length} frames for retrieval.`);\n const topFrames = frames.slice(0, 3);\n parts.push(\n `Top frames: ${topFrames.map((f) => `${f.frameId} (priority: ${f.priority})`).join(', ')}`\n );\n } else {\n parts.push('No highly relevant frames found. Using general context.');\n }\n\n return parts.join(' ');\n }\n}\n\n/**\n * Main LLM Context Retrieval class\n */\nexport class LLMContextRetrieval {\n private db: Database.Database;\n private frameManager: FrameManager;\n private summaryGenerator: CompressedSummaryGenerator;\n private queryParser: QueryParser;\n private heuristicAnalyzer: HeuristicAnalyzer;\n private llmProvider?: LLMProvider;\n private config: RetrievalConfig;\n private projectId: string;\n private lazyLoader: LazyContextLoader;\n private contextCache: ContextCache<RetrievedContext>;\n private auditStore: RetrievalAuditStore;\n private enableAudit: boolean;\n\n constructor(\n db: Database.Database,\n frameManager: FrameManager,\n projectId: string,\n config: Partial<RetrievalConfig> = {},\n llmProvider?: LLMProvider\n ) {\n this.db = db;\n this.frameManager = frameManager;\n this.projectId = projectId;\n this.config = { ...DEFAULT_RETRIEVAL_CONFIG, ...config };\n\n // Auto-create LLM provider if not provided and API key is available\n this.llmProvider = llmProvider ?? createLLMProvider();\n if (this.llmProvider) {\n logger.info('LLM provider configured for context retrieval', {\n projectId,\n provider: this.config.llmConfig.provider,\n });\n }\n\n this.summaryGenerator = new CompressedSummaryGenerator(\n db,\n frameManager,\n projectId,\n config\n );\n this.queryParser = new QueryParser();\n this.heuristicAnalyzer = new HeuristicAnalyzer();\n\n // Initialize audit store\n this.auditStore = new RetrievalAuditStore(db, projectId);\n this.enableAudit = true; // Can be made configurable\n\n // Initialize performance optimizations\n this.lazyLoader = new LazyContextLoader(db, projectId);\n this.contextCache = new ContextCache<RetrievedContext>({\n maxSize: 50 * 1024 * 1024, // 50MB for context cache\n maxItems: 100,\n defaultTTL: 600000, // 10 minutes\n });\n\n // Start cache cleanup\n this.contextCache.startCleanup(60000);\n }\n\n /**\n * Get the audit store for external access\n */\n getAuditStore(): RetrievalAuditStore {\n return this.auditStore;\n }\n\n /**\n * Check if LLM provider is available\n */\n hasLLMProvider(): boolean {\n return !!this.llmProvider;\n }\n\n /**\n * Retrieve context based on query using LLM analysis (with caching)\n */\n public async retrieveContext(\n query: string,\n options: {\n tokenBudget?: number;\n hints?: RetrievalHints;\n forceRefresh?: boolean;\n } = {}\n ): Promise<RetrievedContext> {\n const startTime = Date.now();\n const tokenBudget = options.tokenBudget || this.config.defaultTokenBudget;\n\n // Check cache first unless force refresh\n if (!options.forceRefresh) {\n const cacheKey = `${query}:${tokenBudget}:${JSON.stringify(options.hints || {})}`;\n const cached = this.contextCache.get(cacheKey);\n if (cached) {\n logger.debug('Context cache hit', {\n query: query.substring(0, 50),\n cacheStats: this.contextCache.getStats(),\n });\n return cached;\n }\n }\n\n logger.info('Starting context retrieval', {\n projectId: this.projectId,\n query: query.substring(0, 100),\n tokenBudget,\n });\n\n // 1. Parse the query\n const parsedQuery = this.queryParser.parseNaturalLanguage(query);\n\n // 2. Generate compressed summary\n const summary = this.summaryGenerator.generateSummary({\n forceRefresh: options.forceRefresh,\n });\n\n // 3. Perform LLM analysis\n const analysis = await this.analyzeWithLLM({\n currentQuery: query,\n parsedQuery,\n compressedSummary: summary,\n tokenBudget,\n hints: options.hints,\n });\n\n // 4. Retrieve frames based on analysis\n const { frames, anchors, events, tokensUsed } = await this.retrieveFrames(\n analysis,\n tokenBudget\n );\n\n // 5. Assemble context string\n const context = this.assembleContext(frames, anchors, events, analysis);\n\n const metadata: RetrievalMetadata = {\n retrievalTimeMs: Date.now() - startTime,\n cacheHit: false, // Would need cache tracking\n framesScanned: summary.recentSession.frames.length,\n framesIncluded: frames.length,\n compressionRatio: tokensUsed > 0 ? tokenBudget / tokensUsed : 1,\n };\n\n logger.info('Context retrieval complete', {\n projectId: this.projectId,\n framesIncluded: frames.length,\n tokensUsed,\n retrievalTimeMs: metadata.retrievalTimeMs,\n confidence: analysis.confidenceScore,\n });\n\n const result: RetrievedContext = {\n context,\n frames,\n anchors,\n events,\n analysis,\n tokenUsage: {\n budget: tokenBudget,\n used: tokensUsed,\n remaining: tokenBudget - tokensUsed,\n },\n metadata,\n };\n\n // Record audit entry\n if (this.enableAudit) {\n const provider = analysis.metadata.fallbackUsed\n ? 'heuristic'\n : this.llmProvider\n ? 'anthropic'\n : 'heuristic';\n this.auditStore.record(query, analysis, {\n tokensUsed,\n tokenBudget,\n provider,\n });\n }\n\n // Cache the result\n if (!options.forceRefresh) {\n const cacheKey = `${query}:${tokenBudget}:${JSON.stringify(options.hints || {})}`;\n this.contextCache.set(cacheKey, result, {\n ttl: 600000, // 10 minutes\n });\n }\n\n return result;\n }\n\n /**\n * Perform LLM analysis or fall back to heuristics\n */\n private async analyzeWithLLM(\n request: LLMAnalysisRequest\n ): Promise<LLMAnalysisResponse> {\n const startTime = Date.now();\n\n // Try LLM analysis if provider is available\n if (this.llmProvider) {\n try {\n const prompt = this.buildAnalysisPrompt(request);\n const response = await this.llmProvider.analyze(\n prompt,\n this.config.llmConfig.maxTokens\n );\n const analysis = this.parseAnalysisResponse(response, request);\n analysis.metadata.analysisTimeMs = Date.now() - startTime;\n analysis.metadata.fallbackUsed = false;\n\n // Validate confidence threshold\n if (analysis.confidenceScore >= this.config.minConfidenceThreshold) {\n return analysis;\n }\n\n logger.warn('LLM confidence below threshold, using fallback', {\n confidence: analysis.confidenceScore,\n threshold: this.config.minConfidenceThreshold,\n });\n } catch (error: any) {\n logger.error(\n 'LLM analysis failed, using fallback',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n // Fall back to heuristic analysis\n if (this.config.enableFallback) {\n const analysis = this.heuristicAnalyzer.analyze(\n request.currentQuery,\n request.compressedSummary,\n request.parsedQuery\n );\n analysis.metadata.analysisTimeMs = Date.now() - startTime;\n return analysis;\n }\n\n // Return empty analysis if no fallback\n return {\n reasoning:\n 'Unable to perform analysis - LLM unavailable and fallback disabled',\n framesToRetrieve: [],\n confidenceScore: 0,\n recommendations: [],\n metadata: {\n analysisTimeMs: Date.now() - startTime,\n summaryTokens: 0,\n queryComplexity: 'simple',\n matchedPatterns: [],\n fallbackUsed: false,\n },\n };\n }\n\n /**\n * Build the prompt for LLM analysis\n */\n private buildAnalysisPrompt(request: LLMAnalysisRequest): string {\n const summary = request.compressedSummary;\n\n return `You are analyzing a code project's memory to retrieve relevant context.\n\n## Current Query\n\"${request.currentQuery}\"\n\n## Token Budget\n${request.tokenBudget} tokens available\n\n## Recent Session Summary\n- Frames: ${summary.recentSession.frames.length}\n- Time range: ${new Date(summary.recentSession.timeRange.start).toISOString()} to ${new Date(summary.recentSession.timeRange.end).toISOString()}\n- Dominant operations: ${summary.recentSession.dominantOperations.map((o) => `${o.operation}(${o.count})`).join(', ')}\n- Files touched: ${summary.recentSession.filesTouched\n .slice(0, 5)\n .map((f) => f.path)\n .join(', ')}\n- Errors: ${summary.recentSession.errorsEncountered.length}\n\n## Available Frames\n${summary.recentSession.frames\n .slice(0, 15)\n .map(\n (f) =>\n `- ${f.frameId}: \"${f.name}\" (${f.type}, score: ${f.score.toFixed(2)}, events: ${f.eventCount})`\n )\n .join('\\n')}\n\n## Key Decisions\n${summary.historicalPatterns.keyDecisions\n .slice(0, 5)\n .map((d) => `- ${d.text.substring(0, 80)}...`)\n .join('\\n')}\n\n## Task\nAnalyze the query and select the most relevant frames to retrieve.\nReturn a JSON object with:\n{\n \"reasoning\": \"Your analysis of why these frames are relevant\",\n \"framesToRetrieve\": [\n {\"frameId\": \"...\", \"priority\": 1-10, \"reason\": \"...\", \"includeEvents\": true/false, \"includeAnchors\": true/false}\n ],\n \"confidenceScore\": 0.0-1.0,\n \"recommendations\": [{\"type\": \"include/exclude/summarize\", \"target\": \"...\", \"reason\": \"...\", \"impact\": \"low/medium/high\"}]\n}\n\n${request.hints ? `\\n## Hints\\n${JSON.stringify(request.hints)}` : ''}\n\nRespond with only the JSON object, no other text.`;\n }\n\n /**\n * Parse LLM response into structured analysis\n */\n private parseAnalysisResponse(\n response: string,\n request: LLMAnalysisRequest\n ): LLMAnalysisResponse {\n try {\n // Extract JSON from response (handle markdown code blocks)\n let jsonStr = response;\n const jsonMatch = response.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n if (jsonMatch) {\n jsonStr = jsonMatch[1];\n }\n\n const parsed = JSON.parse(jsonStr.trim());\n\n // Validate and normalize the response\n return {\n reasoning: parsed.reasoning || 'No reasoning provided',\n framesToRetrieve: (parsed.framesToRetrieve || []).map((f: any) => ({\n frameId: f.frameId,\n priority: Math.min(10, Math.max(1, f.priority || 5)),\n reason: f.reason || 'Selected by LLM',\n includeEvents: f.includeEvents ?? true,\n includeAnchors: f.includeAnchors ?? true,\n includeDigest: f.includeDigest ?? true,\n estimatedTokens: f.estimatedTokens || 100,\n })),\n confidenceScore: Math.min(\n 1,\n Math.max(0, parsed.confidenceScore || 0.5)\n ),\n recommendations: (parsed.recommendations || []).map((r: any) => ({\n type: r.type || 'include',\n target: r.target || '',\n reason: r.reason || '',\n impact: r.impact || 'medium',\n })),\n metadata: {\n analysisTimeMs: 0,\n summaryTokens: Math.floor(\n JSON.stringify(request.compressedSummary).length / 4\n ),\n queryComplexity: this.assessQueryComplexity(request.currentQuery),\n matchedPatterns: [],\n fallbackUsed: false,\n },\n };\n } catch (error: unknown) {\n logger.warn('Failed to parse LLM response, using fallback', {\n error,\n response,\n });\n return this.heuristicAnalyzer.analyze(\n request.currentQuery,\n request.compressedSummary,\n request.parsedQuery\n );\n }\n }\n\n private assessQueryComplexity(\n query: string\n ): 'simple' | 'moderate' | 'complex' {\n const wordCount = query.split(/\\s+/).length;\n if (wordCount <= 5) return 'simple';\n if (wordCount <= 15) return 'moderate';\n return 'complex';\n }\n\n /**\n * Retrieve frames based on analysis (with lazy loading)\n */\n private async retrieveFrames(\n analysis: LLMAnalysisResponse,\n tokenBudget: number\n ): Promise<{\n frames: Frame[];\n anchors: Anchor[];\n events: Event[];\n tokensUsed: number;\n }> {\n const frames: Frame[] = [];\n const anchors: Anchor[] = [];\n const events: Event[] = [];\n let tokensUsed = 0;\n\n // Preload frames for better performance\n const frameIds = analysis.framesToRetrieve.map((p: any) => p.frameId);\n await this.lazyLoader.preloadContext(frameIds, {\n parallel: true,\n depth: 2, // Load frames, anchors, and events\n });\n\n // Retrieve frames in priority order within budget\n for (const plan of analysis.framesToRetrieve) {\n if (tokensUsed + plan.estimatedTokens > tokenBudget) {\n logger.debug('Token budget exceeded, stopping retrieval', {\n tokensUsed,\n budget: tokenBudget,\n });\n break;\n }\n\n // Use lazy loader for efficient retrieval\n try {\n const frame = await this.lazyLoader.lazyFrame(plan.frameId).get();\n frames.push(frame);\n tokensUsed += 50; // Base frame tokens\n\n // Include anchors if requested\n if (plan.includeAnchors) {\n const frameAnchors = await this.lazyLoader\n .lazyAnchors(plan.frameId)\n .get();\n anchors.push(...frameAnchors);\n tokensUsed += frameAnchors.length * 40;\n }\n\n // Include events if requested\n if (plan.includeEvents) {\n const frameEvents = await this.lazyLoader\n .lazyEvents(plan.frameId, 10)\n .get();\n events.push(...frameEvents);\n tokensUsed += frameEvents.length * 30;\n }\n } catch (error: unknown) {\n logger.warn('Failed to retrieve frame', {\n frameId: plan.frameId,\n error,\n });\n }\n }\n\n return { frames, anchors, events, tokensUsed };\n }\n\n private getFrameAnchors(frameId: string): Anchor[] {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT * FROM anchors WHERE frame_id = ?\n ORDER BY priority DESC, created_at DESC\n `\n )\n .all(frameId) as any[];\n\n return rows.map((row) => ({\n ...row,\n metadata: JSON.parse(row.metadata || '{}'),\n }));\n } catch {\n return [];\n }\n }\n\n /**\n * Assemble final context string\n */\n private assembleContext(\n frames: Frame[],\n anchors: Anchor[],\n events: Event[],\n analysis: LLMAnalysisResponse\n ): string {\n const sections: string[] = [];\n\n // Add retrieval reasoning (auditable)\n sections.push('## Context Retrieval Analysis');\n sections.push(\n `*Confidence: ${(analysis.confidenceScore * 100).toFixed(0)}%*`\n );\n sections.push(analysis.reasoning);\n sections.push('');\n\n // Add frames\n if (frames.length > 0) {\n sections.push('## Relevant Frames');\n for (const frame of frames) {\n sections.push(`### ${frame.name} (${frame.type})`);\n if (frame.digest_text) {\n sections.push(frame.digest_text);\n }\n sections.push('');\n }\n }\n\n // Add key anchors\n const decisions = anchors.filter((a) => a.type === 'DECISION');\n const constraints = anchors.filter((a) => a.type === 'CONSTRAINT');\n const facts = anchors.filter((a) => a.type === 'FACT');\n\n if (decisions.length > 0) {\n sections.push('## Key Decisions');\n for (const d of decisions.slice(0, 5)) {\n sections.push(`- ${d.text}`);\n }\n sections.push('');\n }\n\n if (constraints.length > 0) {\n sections.push('## Active Constraints');\n for (const c of constraints.slice(0, 5)) {\n sections.push(`- ${c.text}`);\n }\n sections.push('');\n }\n\n if (facts.length > 0) {\n sections.push('## Important Facts');\n for (const f of facts.slice(0, 5)) {\n sections.push(`- ${f.text}`);\n }\n sections.push('');\n }\n\n // Add recent events summary\n if (events.length > 0) {\n sections.push('## Recent Activity');\n const eventSummary = this.summarizeEvents(events);\n sections.push(eventSummary);\n sections.push('');\n }\n\n // Add recommendations\n if (analysis.recommendations.length > 0) {\n sections.push('## Recommendations');\n for (const rec of analysis.recommendations) {\n const icon =\n rec.type === 'include' ? '+' : rec.type === 'exclude' ? '-' : '~';\n sections.push(`${icon} [${rec.impact.toUpperCase()}] ${rec.reason}`);\n }\n }\n\n return sections.join('\\n');\n }\n\n private summarizeEvents(events: Event[]): string {\n const byType: Record<string, number> = {};\n for (const event of events) {\n byType[event.event_type] = (byType[event.event_type] || 0) + 1;\n }\n\n return Object.entries(byType)\n .map(([type, count]) => `- ${type}: ${count} occurrences`)\n .join('\\n');\n }\n\n /**\n * Get just the compressed summary (useful for external analysis)\n */\n public getSummary(forceRefresh = false): CompressedSummary {\n return this.summaryGenerator.generateSummary({ forceRefresh });\n }\n\n /**\n * Set LLM provider\n */\n public setLLMProvider(provider: LLMProvider): void {\n this.llmProvider = provider;\n }\n\n /**\n * Clear all caches\n */\n public clearCache(): void {\n this.summaryGenerator.clearCache();\n this.lazyLoader.clearCache();\n this.contextCache.clear();\n logger.info('Cleared all caches', {\n projectId: this.projectId,\n cacheStats: this.contextCache.getStats(),\n });\n }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/retrieval/retrieval-benchmarks.ts"],
4
- "sourcesContent": ["/**\n * Retrieval Quality Benchmarks at Scale\n * Tests different retrieval strategies to monitor for semantic collapse\n *\n * Key metrics:\n * - Precision: How relevant are retrieved results?\n * - Recall: Are we missing important context?\n * - Semantic Drift: Do embeddings collapse over time?\n * - Query Latency: Performance at scale\n */\n\nimport Database from 'better-sqlite3';\nimport { logger } from '../monitoring/logger.js';\nimport { Trace } from '../trace/types.js';\nimport { LLMContextRetrieval } from './llm-context-retrieval.js';\nimport { HierarchicalRetrieval } from './hierarchical-retrieval.js';\nimport { GraphRetrieval } from './graph-retrieval.js';\nimport { FrameManager } from '../context/frame-manager.js';\n\nexport interface BenchmarkQuery {\n query: string;\n expectedTraceIds: string[];\n expectedTopics: string[];\n difficulty: 'easy' | 'medium' | 'hard';\n category: string;\n}\n\nexport interface BenchmarkResult {\n strategy: 'flat' | 'hierarchical' | 'graph';\n query: BenchmarkQuery;\n retrievedTraceIds: string[];\n precision: number;\n recall: number;\n f1Score: number;\n queryTimeMs: number;\n tokensUsed: number;\n semanticDrift?: number;\n explanation: string;\n}\n\nexport interface BenchmarkReport {\n timestamp: number;\n traceCount: number;\n strategies: {\n flat: StrategyMetrics;\n hierarchical: StrategyMetrics;\n graph: StrategyMetrics;\n };\n warnings: string[];\n recommendations: string[];\n}\n\nexport interface StrategyMetrics {\n avgPrecision: number;\n avgRecall: number;\n avgF1Score: number;\n avgQueryTime: number;\n avgTokensUsed: number;\n semanticCollapse: boolean;\n collapseIndicators: string[];\n}\n\n/**\n * Benchmark suite for retrieval quality at scale\n */\nexport class RetrievalBenchmarks {\n private db: Database.Database;\n private flatRetrieval: LLMContextRetrieval;\n private hierarchicalRetrieval: HierarchicalRetrieval;\n private graphRetrieval: GraphRetrieval;\n private frameManager: FrameManager;\n\n constructor(\n db: Database.Database,\n frameManager: FrameManager,\n projectId: string\n ) {\n this.db = db;\n this.frameManager = frameManager;\n\n // Initialize retrieval strategies\n this.flatRetrieval = new LLMContextRetrieval(db, frameManager, projectId);\n this.hierarchicalRetrieval = new HierarchicalRetrieval(db);\n this.graphRetrieval = new GraphRetrieval(db);\n\n this.initializeSchema();\n }\n\n private initializeSchema(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS benchmark_results (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n timestamp INTEGER DEFAULT (unixepoch() * 1000),\n strategy TEXT NOT NULL,\n query TEXT NOT NULL,\n difficulty TEXT,\n category TEXT,\n precision REAL,\n recall REAL,\n f1_score REAL,\n query_time_ms INTEGER,\n tokens_used INTEGER,\n semantic_drift REAL,\n trace_count INTEGER,\n retrieved_ids TEXT,\n expected_ids TEXT\n )\n `);\n\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_benchmark_strategy ON benchmark_results(strategy);\n CREATE INDEX IF NOT EXISTS idx_benchmark_timestamp ON benchmark_results(timestamp);\n CREATE INDEX IF NOT EXISTS idx_benchmark_difficulty ON benchmark_results(difficulty);\n `);\n }\n\n /**\n * Generate benchmark queries based on current data\n */\n async generateBenchmarkQueries(traces: Trace[]): Promise<BenchmarkQuery[]> {\n const queries: BenchmarkQuery[] = [];\n\n // Easy: Recent specific traces\n const recentTraces = traces\n .sort((a, b) => b.metadata.startTime - a.metadata.startTime)\n .slice(0, 10);\n\n for (const trace of recentTraces.slice(0, 3)) {\n queries.push({\n query: `Show me the ${trace.type} operation from ${new Date(trace.metadata.startTime).toLocaleString()}`,\n expectedTraceIds: [trace.id],\n expectedTopics: [trace.type],\n difficulty: 'easy',\n category: 'specific_lookup',\n });\n }\n\n // Medium: Topic-based queries\n const topicGroups = this.groupByTopic(traces);\n\n for (const [topic, topicTraces] of topicGroups.entries()) {\n if (topicTraces.length >= 5) {\n queries.push({\n query: `What ${topic} operations have been performed recently?`,\n expectedTraceIds: topicTraces.slice(0, 10).map((t) => t.id),\n expectedTopics: [topic],\n difficulty: 'medium',\n category: 'topic_search',\n });\n }\n }\n\n // Hard: Cross-topic and temporal queries\n if (traces.length > 50) {\n // Find traces with errors\n const errorTraces = traces.filter(\n (t) => t.metadata.errorsEncountered.length > 0\n );\n\n if (errorTraces.length > 0) {\n queries.push({\n query: 'What errors occurred and how were they resolved?',\n expectedTraceIds: errorTraces.map((t) => t.id),\n expectedTopics: ['error', 'fix', 'debug'],\n difficulty: 'hard',\n category: 'error_analysis',\n });\n }\n\n // Find decision chains\n const decisionTraces = traces.filter(\n (t) => t.metadata.decisionsRecorded.length > 0\n );\n\n if (decisionTraces.length > 0) {\n queries.push({\n query: 'What architectural decisions were made and why?',\n expectedTraceIds: decisionTraces.map((t) => t.id),\n expectedTopics: ['decision', 'architecture', 'design'],\n difficulty: 'hard',\n category: 'decision_tracking',\n });\n }\n }\n\n // Adversarial: Test for semantic collapse\n queries.push({\n query: 'something about code',\n expectedTraceIds: [],\n expectedTopics: [],\n difficulty: 'hard',\n category: 'vague_query',\n });\n\n queries.push({\n query: 'the thing we did yesterday with the files',\n expectedTraceIds: [],\n expectedTopics: [],\n difficulty: 'hard',\n category: 'ambiguous_query',\n });\n\n return queries;\n }\n\n /**\n * Run benchmarks on all strategies\n */\n async runBenchmarks(\n traces: Trace[],\n sampleSize: number = 10\n ): Promise<BenchmarkReport> {\n logger.info('Starting retrieval benchmarks', {\n traceCount: traces.length,\n sampleSize,\n });\n\n const queries = await this.generateBenchmarkQueries(traces);\n const sampledQueries = this.sampleQueries(queries, sampleSize);\n\n const results: BenchmarkResult[] = [];\n\n // Build hierarchical and graph structures\n await this.hierarchicalRetrieval.buildHierarchy(traces);\n const frames = await this.frameManager.getAllFrames();\n await this.graphRetrieval.buildGraph(traces, frames);\n\n // Test flat retrieval\n for (const query of sampledQueries) {\n const result = await this.benchmarkFlatRetrieval(query, traces);\n results.push(result);\n await this.saveResult(result, traces.length);\n }\n\n // Test hierarchical retrieval\n for (const query of sampledQueries) {\n const result = await this.benchmarkHierarchicalRetrieval(query, traces);\n results.push(result);\n await this.saveResult(result, traces.length);\n }\n\n // Test graph retrieval\n for (const query of sampledQueries) {\n const result = await this.benchmarkGraphRetrieval(query, traces);\n results.push(result);\n await this.saveResult(result, traces.length);\n }\n\n // Generate report\n const report = this.generateReport(results, traces.length);\n\n logger.info('Benchmarks complete', {\n strategies: 3,\n queries: sampledQueries.length,\n avgF1: {\n flat: report.strategies.flat.avgF1Score,\n hierarchical: report.strategies.hierarchical.avgF1Score,\n graph: report.strategies.graph.avgF1Score,\n },\n });\n\n return report;\n }\n\n /**\n * Benchmark flat embedding retrieval\n */\n private async benchmarkFlatRetrieval(\n query: BenchmarkQuery,\n traces: Trace[]\n ): Promise<BenchmarkResult> {\n const startTime = Date.now();\n\n try {\n const result = await this.flatRetrieval.retrieveContext(query.query, {\n tokenBudget: 4000,\n });\n\n const retrievedIds = result.frames.map((f) => f.id);\n const queryTime = Date.now() - startTime;\n\n return this.evaluateResult(\n 'flat',\n query,\n retrievedIds,\n queryTime,\n result.tokenUsage.used,\n 'Standard flat embedding retrieval'\n );\n } catch (error: unknown) {\n logger.error('Flat retrieval failed', error);\n return this.createErrorResult('flat', query, error as Error);\n }\n }\n\n /**\n * Benchmark hierarchical retrieval\n */\n private async benchmarkHierarchicalRetrieval(\n query: BenchmarkQuery,\n traces: Trace[]\n ): Promise<BenchmarkResult> {\n const startTime = Date.now();\n\n try {\n const context = await this.hierarchicalRetrieval.retrieve(\n query.query,\n 4,\n 4000\n );\n\n const queryTime = Date.now() - startTime;\n\n // Extract trace IDs from context (simplified)\n const retrievedIds = this.extractTraceIds(context);\n\n return this.evaluateResult(\n 'hierarchical',\n query,\n retrievedIds,\n queryTime,\n context.length / 4, // Estimate tokens\n 'Hierarchical retrieval with progressive summarization'\n );\n } catch (error: unknown) {\n logger.error('Hierarchical retrieval failed', error);\n return this.createErrorResult('hierarchical', query, error as Error);\n }\n }\n\n /**\n * Benchmark graph retrieval\n */\n private async benchmarkGraphRetrieval(\n query: BenchmarkQuery,\n traces: Trace[]\n ): Promise<BenchmarkResult> {\n const startTime = Date.now();\n\n try {\n const paths = await this.graphRetrieval.traverse(query.query, {\n maxHops: 3,\n minWeight: 0.3,\n });\n\n const queryTime = Date.now() - startTime;\n\n // Extract trace IDs from paths\n const retrievedIds = new Set<string>();\n for (const path of paths) {\n for (const node of path.nodes) {\n if (node.metadata.traceIds) {\n node.metadata.traceIds.forEach((id) => retrievedIds.add(id));\n }\n }\n }\n\n return this.evaluateResult(\n 'graph',\n query,\n Array.from(retrievedIds),\n queryTime,\n paths.length * 100, // Estimate tokens\n 'Graph-based retrieval with explicit relationships'\n );\n } catch (error: unknown) {\n logger.error('Graph retrieval failed', error);\n return this.createErrorResult('graph', query, error as Error);\n }\n }\n\n /**\n * Evaluate retrieval results\n */\n private evaluateResult(\n strategy: 'flat' | 'hierarchical' | 'graph',\n query: BenchmarkQuery,\n retrievedIds: string[],\n queryTimeMs: number,\n tokensUsed: number,\n explanation: string\n ): BenchmarkResult {\n const expectedSet = new Set(query.expectedTraceIds);\n const retrievedSet = new Set(retrievedIds);\n\n // Calculate metrics\n const truePositives = [...retrievedSet].filter((id) =>\n expectedSet.has(id)\n ).length;\n const falsePositives = retrievedSet.size - truePositives;\n const falseNegatives = expectedSet.size - truePositives;\n\n const precision =\n retrievedSet.size > 0 ? truePositives / retrievedSet.size : 0;\n const recall = expectedSet.size > 0 ? truePositives / expectedSet.size : 1;\n const f1Score =\n precision + recall > 0\n ? (2 * (precision * recall)) / (precision + recall)\n : 0;\n\n // Check for semantic drift (simplified)\n const semanticDrift = this.calculateSemanticDrift(query, retrievedIds);\n\n return {\n strategy,\n query,\n retrievedTraceIds: retrievedIds,\n precision,\n recall,\n f1Score,\n queryTimeMs,\n tokensUsed,\n semanticDrift,\n explanation,\n };\n }\n\n /**\n * Calculate semantic drift indicator\n */\n private calculateSemanticDrift(\n query: BenchmarkQuery,\n retrievedIds: string[]\n ): number {\n // If vague query returns too many results, indicates collapse\n if (query.category === 'vague_query' && retrievedIds.length > 10) {\n return 0.8; // High drift\n }\n\n // If specific query returns nothing, indicates problem\n if (query.category === 'specific_lookup' && retrievedIds.length === 0) {\n return 0.7;\n }\n\n return 0.1; // Normal\n }\n\n /**\n * Create error result\n */\n private createErrorResult(\n strategy: 'flat' | 'hierarchical' | 'graph',\n query: BenchmarkQuery,\n error: Error\n ): BenchmarkResult {\n return {\n strategy,\n query,\n retrievedTraceIds: [],\n precision: 0,\n recall: 0,\n f1Score: 0,\n queryTimeMs: 0,\n tokensUsed: 0,\n semanticDrift: 1.0,\n explanation: `Error: ${error.message}`,\n };\n }\n\n /**\n * Generate benchmark report\n */\n private generateReport(\n results: BenchmarkResult[],\n traceCount: number\n ): BenchmarkReport {\n const byStrategy = this.groupByStrategy(results);\n\n const report: BenchmarkReport = {\n timestamp: Date.now(),\n traceCount,\n strategies: {\n flat: this.calculateStrategyMetrics(byStrategy.flat || []),\n hierarchical: this.calculateStrategyMetrics(\n byStrategy.hierarchical || []\n ),\n graph: this.calculateStrategyMetrics(byStrategy.graph || []),\n },\n warnings: [],\n recommendations: [],\n };\n\n // Generate warnings\n if (report.strategies.flat.semanticCollapse) {\n report.warnings.push('Flat embedding shows signs of semantic collapse');\n }\n\n if (report.strategies.flat.avgQueryTime > 1000) {\n report.warnings.push('Flat retrieval query time exceeds 1 second');\n }\n\n // Generate recommendations\n const bestStrategy = this.getBestStrategy(report.strategies);\n report.recommendations.push(`Best overall strategy: ${bestStrategy}`);\n\n if (traceCount > 10000) {\n report.recommendations.push('Consider hierarchical retrieval for scale');\n }\n\n if (\n report.strategies.graph.avgF1Score >\n report.strategies.flat.avgF1Score * 1.2\n ) {\n report.recommendations.push(\n 'Graph retrieval significantly outperforms flat - consider switching'\n );\n }\n\n return report;\n }\n\n /**\n * Calculate strategy metrics\n */\n private calculateStrategyMetrics(\n results: BenchmarkResult[]\n ): StrategyMetrics {\n if (results.length === 0) {\n return {\n avgPrecision: 0,\n avgRecall: 0,\n avgF1Score: 0,\n avgQueryTime: 0,\n avgTokensUsed: 0,\n semanticCollapse: false,\n collapseIndicators: [],\n };\n }\n\n const avgPrecision =\n results.reduce((sum, r) => sum + r.precision, 0) / results.length;\n const avgRecall =\n results.reduce((sum, r) => sum + r.recall, 0) / results.length;\n const avgF1Score =\n results.reduce((sum, r) => sum + r.f1Score, 0) / results.length;\n const avgQueryTime =\n results.reduce((sum, r) => sum + r.queryTimeMs, 0) / results.length;\n const avgTokensUsed =\n results.reduce((sum, r) => sum + r.tokensUsed, 0) / results.length;\n\n // Check for semantic collapse\n const collapseIndicators: string[] = [];\n const avgDrift =\n results.reduce((sum, r) => sum + (r.semanticDrift || 0), 0) /\n results.length;\n\n if (avgDrift > 0.5) {\n collapseIndicators.push('High semantic drift detected');\n }\n\n // Check for uniform results (everything returns same thing)\n const uniqueResults = new Set(\n results.map((r) => r.retrievedTraceIds.sort().join(','))\n );\n if (uniqueResults.size < results.length * 0.3) {\n collapseIndicators.push(\n 'Low result diversity - possible embedding collapse'\n );\n }\n\n // Check for poor precision on specific queries\n const specificQueries = results.filter(\n (r) => r.query.difficulty === 'easy'\n );\n const specificPrecision =\n specificQueries.reduce((sum, r) => sum + r.precision, 0) /\n (specificQueries.length || 1);\n if (specificPrecision < 0.5) {\n collapseIndicators.push('Poor precision on specific queries');\n }\n\n return {\n avgPrecision,\n avgRecall,\n avgF1Score,\n avgQueryTime,\n avgTokensUsed,\n semanticCollapse: collapseIndicators.length > 0,\n collapseIndicators,\n };\n }\n\n /**\n * Determine best strategy\n */\n private getBestStrategy(strategies: BenchmarkReport['strategies']): string {\n const scores = {\n flat:\n strategies.flat.avgF1Score * (1 - strategies.flat.avgQueryTime / 5000),\n hierarchical:\n strategies.hierarchical.avgF1Score *\n (1 - strategies.hierarchical.avgQueryTime / 5000),\n graph:\n strategies.graph.avgF1Score *\n (1 - strategies.graph.avgQueryTime / 5000),\n };\n\n return Object.entries(scores).sort((a, b) => b[1] - a[1])[0][0];\n }\n\n /**\n * Group traces by topic\n */\n private groupByTopic(traces: Trace[]): Map<string, Trace[]> {\n const groups = new Map<string, Trace[]>();\n\n for (const trace of traces) {\n if (!groups.has(trace.type)) {\n groups.set(trace.type, []);\n }\n groups.get(trace.type)!.push(trace);\n }\n\n return groups;\n }\n\n /**\n * Group results by strategy\n */\n private groupByStrategy(\n results: BenchmarkResult[]\n ): Record<string, BenchmarkResult[]> {\n const grouped: Record<string, BenchmarkResult[]> = {};\n\n for (const result of results) {\n if (!grouped[result.strategy]) {\n grouped[result.strategy] = [];\n }\n grouped[result.strategy].push(result);\n }\n\n return grouped;\n }\n\n /**\n * Sample queries for benchmarking\n */\n private sampleQueries(\n queries: BenchmarkQuery[],\n sampleSize: number\n ): BenchmarkQuery[] {\n // Ensure we get a mix of difficulties\n const byDifficulty = this.groupByDifficulty(queries);\n const sampled: BenchmarkQuery[] = [];\n\n const perDifficulty = Math.ceil(sampleSize / 3);\n\n for (const difficulty of ['easy', 'medium', 'hard'] as const) {\n const difficultyQueries = byDifficulty[difficulty] || [];\n const sample = this.randomSample(difficultyQueries, perDifficulty);\n sampled.push(...sample);\n }\n\n return sampled.slice(0, sampleSize);\n }\n\n /**\n * Group queries by difficulty\n */\n private groupByDifficulty(\n queries: BenchmarkQuery[]\n ): Record<string, BenchmarkQuery[]> {\n const grouped: Record<string, BenchmarkQuery[]> = {};\n\n for (const query of queries) {\n if (!grouped[query.difficulty]) {\n grouped[query.difficulty] = [];\n }\n grouped[query.difficulty].push(query);\n }\n\n return grouped;\n }\n\n /**\n * Random sample from array\n */\n private randomSample<T>(array: T[], size: number): T[] {\n const shuffled = [...array].sort(() => Math.random() - 0.5);\n return shuffled.slice(0, size);\n }\n\n /**\n * Extract trace IDs from context string\n */\n private extractTraceIds(context: string): string[] {\n // Simple pattern matching for trace IDs\n const matches = context.match(/trace_[a-f0-9]{16}/g) || [];\n return [...new Set(matches)];\n }\n\n /**\n * Save benchmark result to database\n */\n private async saveResult(\n result: BenchmarkResult,\n traceCount: number\n ): Promise<void> {\n this.db\n .prepare(\n `\n INSERT INTO benchmark_results (\n strategy, query, difficulty, category,\n precision, recall, f1_score,\n query_time_ms, tokens_used, semantic_drift,\n trace_count, retrieved_ids, expected_ids\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `\n )\n .run(\n result.strategy,\n result.query.query,\n result.query.difficulty,\n result.query.category,\n result.precision,\n result.recall,\n result.f1Score,\n result.queryTimeMs,\n result.tokensUsed,\n result.semanticDrift || 0,\n traceCount,\n JSON.stringify(result.retrievedTraceIds),\n JSON.stringify(result.query.expectedTraceIds)\n );\n }\n\n /**\n * Get historical benchmark trends\n */\n getHistoricalTrends(days: number = 7): any {\n const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;\n\n const trends = this.db\n .prepare(\n `\n SELECT \n strategy,\n DATE(timestamp / 1000, 'unixepoch') as date,\n AVG(precision) as avg_precision,\n AVG(recall) as avg_recall,\n AVG(f1_score) as avg_f1,\n AVG(query_time_ms) as avg_query_time,\n AVG(semantic_drift) as avg_drift,\n COUNT(*) as query_count\n FROM benchmark_results\n WHERE timestamp > ?\n GROUP BY strategy, date\n ORDER BY date, strategy\n `\n )\n .all(cutoff);\n\n return trends;\n }\n}\n"],
4
+ "sourcesContent": ["/**\n * Retrieval Quality Benchmarks at Scale\n * Tests different retrieval strategies to monitor for semantic collapse\n *\n * Key metrics:\n * - Precision: How relevant are retrieved results?\n * - Recall: Are we missing important context?\n * - Semantic Drift: Do embeddings collapse over time?\n * - Query Latency: Performance at scale\n */\n\nimport Database from 'better-sqlite3';\nimport { logger } from '../monitoring/logger.js';\nimport { Trace } from '../trace/types.js';\nimport { LLMContextRetrieval } from './llm-context-retrieval.js';\nimport { HierarchicalRetrieval } from './hierarchical-retrieval.js';\nimport { GraphRetrieval } from './graph-retrieval.js';\nimport { FrameManager } from '../context/index.js';\n\nexport interface BenchmarkQuery {\n query: string;\n expectedTraceIds: string[];\n expectedTopics: string[];\n difficulty: 'easy' | 'medium' | 'hard';\n category: string;\n}\n\nexport interface BenchmarkResult {\n strategy: 'flat' | 'hierarchical' | 'graph';\n query: BenchmarkQuery;\n retrievedTraceIds: string[];\n precision: number;\n recall: number;\n f1Score: number;\n queryTimeMs: number;\n tokensUsed: number;\n semanticDrift?: number;\n explanation: string;\n}\n\nexport interface BenchmarkReport {\n timestamp: number;\n traceCount: number;\n strategies: {\n flat: StrategyMetrics;\n hierarchical: StrategyMetrics;\n graph: StrategyMetrics;\n };\n warnings: string[];\n recommendations: string[];\n}\n\nexport interface StrategyMetrics {\n avgPrecision: number;\n avgRecall: number;\n avgF1Score: number;\n avgQueryTime: number;\n avgTokensUsed: number;\n semanticCollapse: boolean;\n collapseIndicators: string[];\n}\n\n/**\n * Benchmark suite for retrieval quality at scale\n */\nexport class RetrievalBenchmarks {\n private db: Database.Database;\n private flatRetrieval: LLMContextRetrieval;\n private hierarchicalRetrieval: HierarchicalRetrieval;\n private graphRetrieval: GraphRetrieval;\n private frameManager: FrameManager;\n\n constructor(\n db: Database.Database,\n frameManager: FrameManager,\n projectId: string\n ) {\n this.db = db;\n this.frameManager = frameManager;\n\n // Initialize retrieval strategies\n this.flatRetrieval = new LLMContextRetrieval(db, frameManager, projectId);\n this.hierarchicalRetrieval = new HierarchicalRetrieval(db);\n this.graphRetrieval = new GraphRetrieval(db);\n\n this.initializeSchema();\n }\n\n private initializeSchema(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS benchmark_results (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n timestamp INTEGER DEFAULT (unixepoch() * 1000),\n strategy TEXT NOT NULL,\n query TEXT NOT NULL,\n difficulty TEXT,\n category TEXT,\n precision REAL,\n recall REAL,\n f1_score REAL,\n query_time_ms INTEGER,\n tokens_used INTEGER,\n semantic_drift REAL,\n trace_count INTEGER,\n retrieved_ids TEXT,\n expected_ids TEXT\n )\n `);\n\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_benchmark_strategy ON benchmark_results(strategy);\n CREATE INDEX IF NOT EXISTS idx_benchmark_timestamp ON benchmark_results(timestamp);\n CREATE INDEX IF NOT EXISTS idx_benchmark_difficulty ON benchmark_results(difficulty);\n `);\n }\n\n /**\n * Generate benchmark queries based on current data\n */\n async generateBenchmarkQueries(traces: Trace[]): Promise<BenchmarkQuery[]> {\n const queries: BenchmarkQuery[] = [];\n\n // Easy: Recent specific traces\n const recentTraces = traces\n .sort((a, b) => b.metadata.startTime - a.metadata.startTime)\n .slice(0, 10);\n\n for (const trace of recentTraces.slice(0, 3)) {\n queries.push({\n query: `Show me the ${trace.type} operation from ${new Date(trace.metadata.startTime).toLocaleString()}`,\n expectedTraceIds: [trace.id],\n expectedTopics: [trace.type],\n difficulty: 'easy',\n category: 'specific_lookup',\n });\n }\n\n // Medium: Topic-based queries\n const topicGroups = this.groupByTopic(traces);\n\n for (const [topic, topicTraces] of topicGroups.entries()) {\n if (topicTraces.length >= 5) {\n queries.push({\n query: `What ${topic} operations have been performed recently?`,\n expectedTraceIds: topicTraces.slice(0, 10).map((t) => t.id),\n expectedTopics: [topic],\n difficulty: 'medium',\n category: 'topic_search',\n });\n }\n }\n\n // Hard: Cross-topic and temporal queries\n if (traces.length > 50) {\n // Find traces with errors\n const errorTraces = traces.filter(\n (t) => t.metadata.errorsEncountered.length > 0\n );\n\n if (errorTraces.length > 0) {\n queries.push({\n query: 'What errors occurred and how were they resolved?',\n expectedTraceIds: errorTraces.map((t) => t.id),\n expectedTopics: ['error', 'fix', 'debug'],\n difficulty: 'hard',\n category: 'error_analysis',\n });\n }\n\n // Find decision chains\n const decisionTraces = traces.filter(\n (t) => t.metadata.decisionsRecorded.length > 0\n );\n\n if (decisionTraces.length > 0) {\n queries.push({\n query: 'What architectural decisions were made and why?',\n expectedTraceIds: decisionTraces.map((t) => t.id),\n expectedTopics: ['decision', 'architecture', 'design'],\n difficulty: 'hard',\n category: 'decision_tracking',\n });\n }\n }\n\n // Adversarial: Test for semantic collapse\n queries.push({\n query: 'something about code',\n expectedTraceIds: [],\n expectedTopics: [],\n difficulty: 'hard',\n category: 'vague_query',\n });\n\n queries.push({\n query: 'the thing we did yesterday with the files',\n expectedTraceIds: [],\n expectedTopics: [],\n difficulty: 'hard',\n category: 'ambiguous_query',\n });\n\n return queries;\n }\n\n /**\n * Run benchmarks on all strategies\n */\n async runBenchmarks(\n traces: Trace[],\n sampleSize: number = 10\n ): Promise<BenchmarkReport> {\n logger.info('Starting retrieval benchmarks', {\n traceCount: traces.length,\n sampleSize,\n });\n\n const queries = await this.generateBenchmarkQueries(traces);\n const sampledQueries = this.sampleQueries(queries, sampleSize);\n\n const results: BenchmarkResult[] = [];\n\n // Build hierarchical and graph structures\n await this.hierarchicalRetrieval.buildHierarchy(traces);\n const frames = await this.frameManager.getAllFrames();\n await this.graphRetrieval.buildGraph(traces, frames);\n\n // Test flat retrieval\n for (const query of sampledQueries) {\n const result = await this.benchmarkFlatRetrieval(query, traces);\n results.push(result);\n await this.saveResult(result, traces.length);\n }\n\n // Test hierarchical retrieval\n for (const query of sampledQueries) {\n const result = await this.benchmarkHierarchicalRetrieval(query, traces);\n results.push(result);\n await this.saveResult(result, traces.length);\n }\n\n // Test graph retrieval\n for (const query of sampledQueries) {\n const result = await this.benchmarkGraphRetrieval(query, traces);\n results.push(result);\n await this.saveResult(result, traces.length);\n }\n\n // Generate report\n const report = this.generateReport(results, traces.length);\n\n logger.info('Benchmarks complete', {\n strategies: 3,\n queries: sampledQueries.length,\n avgF1: {\n flat: report.strategies.flat.avgF1Score,\n hierarchical: report.strategies.hierarchical.avgF1Score,\n graph: report.strategies.graph.avgF1Score,\n },\n });\n\n return report;\n }\n\n /**\n * Benchmark flat embedding retrieval\n */\n private async benchmarkFlatRetrieval(\n query: BenchmarkQuery,\n traces: Trace[]\n ): Promise<BenchmarkResult> {\n const startTime = Date.now();\n\n try {\n const result = await this.flatRetrieval.retrieveContext(query.query, {\n tokenBudget: 4000,\n });\n\n const retrievedIds = result.frames.map((f) => f.id);\n const queryTime = Date.now() - startTime;\n\n return this.evaluateResult(\n 'flat',\n query,\n retrievedIds,\n queryTime,\n result.tokenUsage.used,\n 'Standard flat embedding retrieval'\n );\n } catch (error: unknown) {\n logger.error('Flat retrieval failed', error);\n return this.createErrorResult('flat', query, error as Error);\n }\n }\n\n /**\n * Benchmark hierarchical retrieval\n */\n private async benchmarkHierarchicalRetrieval(\n query: BenchmarkQuery,\n traces: Trace[]\n ): Promise<BenchmarkResult> {\n const startTime = Date.now();\n\n try {\n const context = await this.hierarchicalRetrieval.retrieve(\n query.query,\n 4,\n 4000\n );\n\n const queryTime = Date.now() - startTime;\n\n // Extract trace IDs from context (simplified)\n const retrievedIds = this.extractTraceIds(context);\n\n return this.evaluateResult(\n 'hierarchical',\n query,\n retrievedIds,\n queryTime,\n context.length / 4, // Estimate tokens\n 'Hierarchical retrieval with progressive summarization'\n );\n } catch (error: unknown) {\n logger.error('Hierarchical retrieval failed', error);\n return this.createErrorResult('hierarchical', query, error as Error);\n }\n }\n\n /**\n * Benchmark graph retrieval\n */\n private async benchmarkGraphRetrieval(\n query: BenchmarkQuery,\n traces: Trace[]\n ): Promise<BenchmarkResult> {\n const startTime = Date.now();\n\n try {\n const paths = await this.graphRetrieval.traverse(query.query, {\n maxHops: 3,\n minWeight: 0.3,\n });\n\n const queryTime = Date.now() - startTime;\n\n // Extract trace IDs from paths\n const retrievedIds = new Set<string>();\n for (const path of paths) {\n for (const node of path.nodes) {\n if (node.metadata.traceIds) {\n node.metadata.traceIds.forEach((id) => retrievedIds.add(id));\n }\n }\n }\n\n return this.evaluateResult(\n 'graph',\n query,\n Array.from(retrievedIds),\n queryTime,\n paths.length * 100, // Estimate tokens\n 'Graph-based retrieval with explicit relationships'\n );\n } catch (error: unknown) {\n logger.error('Graph retrieval failed', error);\n return this.createErrorResult('graph', query, error as Error);\n }\n }\n\n /**\n * Evaluate retrieval results\n */\n private evaluateResult(\n strategy: 'flat' | 'hierarchical' | 'graph',\n query: BenchmarkQuery,\n retrievedIds: string[],\n queryTimeMs: number,\n tokensUsed: number,\n explanation: string\n ): BenchmarkResult {\n const expectedSet = new Set(query.expectedTraceIds);\n const retrievedSet = new Set(retrievedIds);\n\n // Calculate metrics\n const truePositives = [...retrievedSet].filter((id) =>\n expectedSet.has(id)\n ).length;\n const falsePositives = retrievedSet.size - truePositives;\n const falseNegatives = expectedSet.size - truePositives;\n\n const precision =\n retrievedSet.size > 0 ? truePositives / retrievedSet.size : 0;\n const recall = expectedSet.size > 0 ? truePositives / expectedSet.size : 1;\n const f1Score =\n precision + recall > 0\n ? (2 * (precision * recall)) / (precision + recall)\n : 0;\n\n // Check for semantic drift (simplified)\n const semanticDrift = this.calculateSemanticDrift(query, retrievedIds);\n\n return {\n strategy,\n query,\n retrievedTraceIds: retrievedIds,\n precision,\n recall,\n f1Score,\n queryTimeMs,\n tokensUsed,\n semanticDrift,\n explanation,\n };\n }\n\n /**\n * Calculate semantic drift indicator\n */\n private calculateSemanticDrift(\n query: BenchmarkQuery,\n retrievedIds: string[]\n ): number {\n // If vague query returns too many results, indicates collapse\n if (query.category === 'vague_query' && retrievedIds.length > 10) {\n return 0.8; // High drift\n }\n\n // If specific query returns nothing, indicates problem\n if (query.category === 'specific_lookup' && retrievedIds.length === 0) {\n return 0.7;\n }\n\n return 0.1; // Normal\n }\n\n /**\n * Create error result\n */\n private createErrorResult(\n strategy: 'flat' | 'hierarchical' | 'graph',\n query: BenchmarkQuery,\n error: Error\n ): BenchmarkResult {\n return {\n strategy,\n query,\n retrievedTraceIds: [],\n precision: 0,\n recall: 0,\n f1Score: 0,\n queryTimeMs: 0,\n tokensUsed: 0,\n semanticDrift: 1.0,\n explanation: `Error: ${error.message}`,\n };\n }\n\n /**\n * Generate benchmark report\n */\n private generateReport(\n results: BenchmarkResult[],\n traceCount: number\n ): BenchmarkReport {\n const byStrategy = this.groupByStrategy(results);\n\n const report: BenchmarkReport = {\n timestamp: Date.now(),\n traceCount,\n strategies: {\n flat: this.calculateStrategyMetrics(byStrategy.flat || []),\n hierarchical: this.calculateStrategyMetrics(\n byStrategy.hierarchical || []\n ),\n graph: this.calculateStrategyMetrics(byStrategy.graph || []),\n },\n warnings: [],\n recommendations: [],\n };\n\n // Generate warnings\n if (report.strategies.flat.semanticCollapse) {\n report.warnings.push('Flat embedding shows signs of semantic collapse');\n }\n\n if (report.strategies.flat.avgQueryTime > 1000) {\n report.warnings.push('Flat retrieval query time exceeds 1 second');\n }\n\n // Generate recommendations\n const bestStrategy = this.getBestStrategy(report.strategies);\n report.recommendations.push(`Best overall strategy: ${bestStrategy}`);\n\n if (traceCount > 10000) {\n report.recommendations.push('Consider hierarchical retrieval for scale');\n }\n\n if (\n report.strategies.graph.avgF1Score >\n report.strategies.flat.avgF1Score * 1.2\n ) {\n report.recommendations.push(\n 'Graph retrieval significantly outperforms flat - consider switching'\n );\n }\n\n return report;\n }\n\n /**\n * Calculate strategy metrics\n */\n private calculateStrategyMetrics(\n results: BenchmarkResult[]\n ): StrategyMetrics {\n if (results.length === 0) {\n return {\n avgPrecision: 0,\n avgRecall: 0,\n avgF1Score: 0,\n avgQueryTime: 0,\n avgTokensUsed: 0,\n semanticCollapse: false,\n collapseIndicators: [],\n };\n }\n\n const avgPrecision =\n results.reduce((sum, r) => sum + r.precision, 0) / results.length;\n const avgRecall =\n results.reduce((sum, r) => sum + r.recall, 0) / results.length;\n const avgF1Score =\n results.reduce((sum, r) => sum + r.f1Score, 0) / results.length;\n const avgQueryTime =\n results.reduce((sum, r) => sum + r.queryTimeMs, 0) / results.length;\n const avgTokensUsed =\n results.reduce((sum, r) => sum + r.tokensUsed, 0) / results.length;\n\n // Check for semantic collapse\n const collapseIndicators: string[] = [];\n const avgDrift =\n results.reduce((sum, r) => sum + (r.semanticDrift || 0), 0) /\n results.length;\n\n if (avgDrift > 0.5) {\n collapseIndicators.push('High semantic drift detected');\n }\n\n // Check for uniform results (everything returns same thing)\n const uniqueResults = new Set(\n results.map((r) => r.retrievedTraceIds.sort().join(','))\n );\n if (uniqueResults.size < results.length * 0.3) {\n collapseIndicators.push(\n 'Low result diversity - possible embedding collapse'\n );\n }\n\n // Check for poor precision on specific queries\n const specificQueries = results.filter(\n (r) => r.query.difficulty === 'easy'\n );\n const specificPrecision =\n specificQueries.reduce((sum, r) => sum + r.precision, 0) /\n (specificQueries.length || 1);\n if (specificPrecision < 0.5) {\n collapseIndicators.push('Poor precision on specific queries');\n }\n\n return {\n avgPrecision,\n avgRecall,\n avgF1Score,\n avgQueryTime,\n avgTokensUsed,\n semanticCollapse: collapseIndicators.length > 0,\n collapseIndicators,\n };\n }\n\n /**\n * Determine best strategy\n */\n private getBestStrategy(strategies: BenchmarkReport['strategies']): string {\n const scores = {\n flat:\n strategies.flat.avgF1Score * (1 - strategies.flat.avgQueryTime / 5000),\n hierarchical:\n strategies.hierarchical.avgF1Score *\n (1 - strategies.hierarchical.avgQueryTime / 5000),\n graph:\n strategies.graph.avgF1Score *\n (1 - strategies.graph.avgQueryTime / 5000),\n };\n\n return Object.entries(scores).sort((a, b) => b[1] - a[1])[0][0];\n }\n\n /**\n * Group traces by topic\n */\n private groupByTopic(traces: Trace[]): Map<string, Trace[]> {\n const groups = new Map<string, Trace[]>();\n\n for (const trace of traces) {\n if (!groups.has(trace.type)) {\n groups.set(trace.type, []);\n }\n groups.get(trace.type)!.push(trace);\n }\n\n return groups;\n }\n\n /**\n * Group results by strategy\n */\n private groupByStrategy(\n results: BenchmarkResult[]\n ): Record<string, BenchmarkResult[]> {\n const grouped: Record<string, BenchmarkResult[]> = {};\n\n for (const result of results) {\n if (!grouped[result.strategy]) {\n grouped[result.strategy] = [];\n }\n grouped[result.strategy].push(result);\n }\n\n return grouped;\n }\n\n /**\n * Sample queries for benchmarking\n */\n private sampleQueries(\n queries: BenchmarkQuery[],\n sampleSize: number\n ): BenchmarkQuery[] {\n // Ensure we get a mix of difficulties\n const byDifficulty = this.groupByDifficulty(queries);\n const sampled: BenchmarkQuery[] = [];\n\n const perDifficulty = Math.ceil(sampleSize / 3);\n\n for (const difficulty of ['easy', 'medium', 'hard'] as const) {\n const difficultyQueries = byDifficulty[difficulty] || [];\n const sample = this.randomSample(difficultyQueries, perDifficulty);\n sampled.push(...sample);\n }\n\n return sampled.slice(0, sampleSize);\n }\n\n /**\n * Group queries by difficulty\n */\n private groupByDifficulty(\n queries: BenchmarkQuery[]\n ): Record<string, BenchmarkQuery[]> {\n const grouped: Record<string, BenchmarkQuery[]> = {};\n\n for (const query of queries) {\n if (!grouped[query.difficulty]) {\n grouped[query.difficulty] = [];\n }\n grouped[query.difficulty].push(query);\n }\n\n return grouped;\n }\n\n /**\n * Random sample from array\n */\n private randomSample<T>(array: T[], size: number): T[] {\n const shuffled = [...array].sort(() => Math.random() - 0.5);\n return shuffled.slice(0, size);\n }\n\n /**\n * Extract trace IDs from context string\n */\n private extractTraceIds(context: string): string[] {\n // Simple pattern matching for trace IDs\n const matches = context.match(/trace_[a-f0-9]{16}/g) || [];\n return [...new Set(matches)];\n }\n\n /**\n * Save benchmark result to database\n */\n private async saveResult(\n result: BenchmarkResult,\n traceCount: number\n ): Promise<void> {\n this.db\n .prepare(\n `\n INSERT INTO benchmark_results (\n strategy, query, difficulty, category,\n precision, recall, f1_score,\n query_time_ms, tokens_used, semantic_drift,\n trace_count, retrieved_ids, expected_ids\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `\n )\n .run(\n result.strategy,\n result.query.query,\n result.query.difficulty,\n result.query.category,\n result.precision,\n result.recall,\n result.f1Score,\n result.queryTimeMs,\n result.tokensUsed,\n result.semanticDrift || 0,\n traceCount,\n JSON.stringify(result.retrievedTraceIds),\n JSON.stringify(result.query.expectedTraceIds)\n );\n }\n\n /**\n * Get historical benchmark trends\n */\n getHistoricalTrends(days: number = 7): any {\n const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;\n\n const trends = this.db\n .prepare(\n `\n SELECT \n strategy,\n DATE(timestamp / 1000, 'unixepoch') as date,\n AVG(precision) as avg_precision,\n AVG(recall) as avg_recall,\n AVG(f1_score) as avg_f1,\n AVG(query_time_ms) as avg_query_time,\n AVG(semantic_drift) as avg_drift,\n COUNT(*) as query_count\n FROM benchmark_results\n WHERE timestamp > ?\n GROUP BY strategy, date\n ORDER BY date, strategy\n `\n )\n .all(cutoff);\n\n return trends;\n }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/retrieval/summary-generator.ts"],
4
- "sourcesContent": ["/**\n * Compressed Summary Generator\n * Creates compact summaries of project memory for LLM analysis\n */\n\nimport Database from 'better-sqlite3';\nimport {\n FrameManager,\n Frame,\n Anchor,\n Event,\n} from '../context/frame-manager.js';\nimport { TraceDetector } from '../trace/trace-detector.js';\nimport {\n CompressedSummary,\n RecentSessionSummary,\n HistoricalPatterns,\n QueryableIndices,\n SummaryStats,\n FrameSummary,\n OperationSummary,\n FileSummary,\n ErrorSummary,\n DecisionSummary,\n IssueSummary,\n ToolSequence,\n ActivityPattern,\n RetrievalConfig,\n DEFAULT_RETRIEVAL_CONFIG,\n} from './types.js';\nimport { logger } from '../monitoring/logger.js';\n\nexport class CompressedSummaryGenerator {\n private db: Database.Database;\n private frameManager: FrameManager;\n private traceDetector?: TraceDetector;\n private projectId: string;\n private config: RetrievalConfig;\n private cache: Map<\n string,\n { summary: CompressedSummary; expiresAt: number }\n > = new Map();\n\n constructor(\n db: Database.Database,\n frameManager: FrameManager,\n projectId: string,\n config: Partial<RetrievalConfig> = {},\n traceDetector?: TraceDetector\n ) {\n this.db = db;\n this.frameManager = frameManager;\n this.projectId = projectId;\n this.config = { ...DEFAULT_RETRIEVAL_CONFIG, ...config };\n this.traceDetector = traceDetector;\n }\n\n /**\n * Generate a compressed summary for LLM analysis\n */\n public generateSummary(\n options: {\n maxFrames?: number;\n timeRangeHours?: number;\n forceRefresh?: boolean;\n } = {}\n ): CompressedSummary {\n const cacheKey = `summary_${this.projectId}_${options.maxFrames || this.config.maxSummaryFrames}`;\n\n // Check cache\n if (!options.forceRefresh) {\n const cached = this.cache.get(cacheKey);\n if (cached && cached.expiresAt > Date.now()) {\n logger.debug('Using cached summary', { projectId: this.projectId });\n return cached.summary;\n }\n }\n\n const startTime = Date.now();\n const maxFrames = options.maxFrames || this.config.maxSummaryFrames;\n const timeRangeHours = options.timeRangeHours || 24;\n\n // Generate all components\n const recentSession = this.generateRecentSessionSummary(\n maxFrames,\n timeRangeHours\n );\n const historicalPatterns = this.generateHistoricalPatterns();\n const queryableIndices = this.generateQueryableIndices();\n const stats = this.generateStats();\n\n const summary: CompressedSummary = {\n projectId: this.projectId,\n generatedAt: Date.now(),\n recentSession,\n historicalPatterns,\n queryableIndices,\n stats,\n };\n\n // Cache the result\n this.cache.set(cacheKey, {\n summary,\n expiresAt: Date.now() + this.config.cacheTtlSeconds * 1000,\n });\n\n logger.info('Generated compressed summary', {\n projectId: this.projectId,\n frames: recentSession.frames.length,\n generationTimeMs: Date.now() - startTime,\n });\n\n return summary;\n }\n\n /**\n * Generate recent session summary\n */\n private generateRecentSessionSummary(\n maxFrames: number,\n timeRangeHours: number\n ): RecentSessionSummary {\n const cutoffTime = Math.floor(Date.now() / 1000) - timeRangeHours * 3600;\n\n // Get recent frames\n const frames = this.getRecentFrames(maxFrames, cutoffTime);\n const frameSummaries = frames.map((f) => this.summarizeFrame(f));\n\n // Get dominant operations\n const dominantOperations = this.getDominantOperations(cutoffTime);\n\n // Get files touched\n const filesTouched = this.getFilesTouched(cutoffTime);\n\n // Get errors encountered\n const errorsEncountered = this.getErrorsEncountered(cutoffTime);\n\n // Calculate time range\n const timestamps = frames.map((f) => f.created_at).filter((t) => t);\n const start = timestamps.length > 0 ? Math.min(...timestamps) : cutoffTime;\n const end =\n timestamps.length > 0\n ? Math.max(...timestamps)\n : Math.floor(Date.now() / 1000);\n\n return {\n frames: frameSummaries,\n dominantOperations,\n filesTouched,\n errorsEncountered,\n timeRange: {\n start: start * 1000,\n end: end * 1000,\n durationMs: (end - start) * 1000,\n },\n };\n }\n\n /**\n * Generate historical patterns\n */\n private generateHistoricalPatterns(): HistoricalPatterns {\n return {\n topicFrameCounts: this.getTopicFrameCounts(),\n keyDecisions: this.getKeyDecisions(),\n recurringIssues: this.getRecurringIssues(),\n commonToolSequences: this.getCommonToolSequences(),\n activityPatterns: this.getActivityPatterns(),\n };\n }\n\n /**\n * Generate queryable indices\n */\n private generateQueryableIndices(): QueryableIndices {\n return {\n byErrorType: this.indexByErrorType(),\n byTimeframe: this.indexByTimeframe(),\n byContributor: this.indexByContributor(),\n byTopic: this.indexByTopic(),\n byFile: this.indexByFile(),\n };\n }\n\n /**\n * Generate summary statistics\n */\n private generateStats(): SummaryStats {\n try {\n const frameStats =\n (this.db\n .prepare(\n `\n SELECT \n COUNT(*) as totalFrames,\n MIN(created_at) as oldestFrame,\n MAX(created_at) as newestFrame,\n AVG(depth) as avgDepth\n FROM frames\n WHERE project_id = ?\n `\n )\n .get(this.projectId) as any) || {};\n\n const eventCount = (this.db\n .prepare(\n `\n SELECT COUNT(*) as count FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ?\n `\n )\n .get(this.projectId) as any) || { count: 0 };\n\n const anchorCount = (this.db\n .prepare(\n `\n SELECT COUNT(*) as count FROM anchors a\n JOIN frames f ON a.frame_id = f.frame_id\n WHERE f.project_id = ?\n `\n )\n .get(this.projectId) as any) || { count: 0 };\n\n const decisionCount = (this.db\n .prepare(\n `\n SELECT COUNT(*) as count FROM anchors a\n JOIN frames f ON a.frame_id = f.frame_id\n WHERE f.project_id = ? AND a.type = 'DECISION'\n `\n )\n .get(this.projectId) as any) || { count: 0 };\n\n const totalFrames = frameStats.totalFrames || 0;\n\n return {\n totalFrames,\n totalEvents: eventCount.count || 0,\n totalAnchors: anchorCount.count || 0,\n totalDecisions: decisionCount.count || 0,\n oldestFrame: (frameStats.oldestFrame || 0) * 1000,\n newestFrame: (frameStats.newestFrame || 0) * 1000,\n avgFrameDepth: frameStats.avgDepth || 0,\n avgEventsPerFrame:\n totalFrames > 0 ? (eventCount.count || 0) / totalFrames : 0,\n };\n } catch (error: unknown) {\n logger.warn('Error generating stats, using defaults', { error });\n return {\n totalFrames: 0,\n totalEvents: 0,\n totalAnchors: 0,\n totalDecisions: 0,\n oldestFrame: 0,\n newestFrame: 0,\n avgFrameDepth: 0,\n avgEventsPerFrame: 0,\n };\n }\n }\n\n // Helper methods for recent session\n\n private getRecentFrames(limit: number, cutoffTime: number): Frame[] {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT * FROM frames\n WHERE project_id = ? AND created_at >= ?\n ORDER BY created_at DESC\n LIMIT ?\n `\n )\n .all(this.projectId, cutoffTime, limit) as any[];\n\n return rows.map((row) => ({\n ...row,\n inputs: JSON.parse(row.inputs || '{}'),\n outputs: JSON.parse(row.outputs || '{}'),\n digest_json: JSON.parse(row.digest_json || '{}'),\n }));\n } catch {\n return [];\n }\n }\n\n private summarizeFrame(frame: Frame): FrameSummary {\n // Get event count\n const eventCount = (this.db\n .prepare(\n `\n SELECT COUNT(*) as count FROM events WHERE frame_id = ?\n `\n )\n .get(frame.frame_id) as any) || { count: 0 };\n\n // Get anchor count\n const anchorCount = (this.db\n .prepare(\n `\n SELECT COUNT(*) as count FROM anchors WHERE frame_id = ?\n `\n )\n .get(frame.frame_id) as any) || { count: 0 };\n\n // Calculate score based on activity\n const score = this.calculateFrameScore(\n frame,\n eventCount.count,\n anchorCount.count\n );\n\n return {\n frameId: frame.frame_id,\n name: frame.name,\n type: frame.type,\n depth: frame.depth,\n eventCount: eventCount.count,\n anchorCount: anchorCount.count,\n score,\n createdAt: frame.created_at * 1000,\n closedAt: frame.closed_at ? frame.closed_at * 1000 : undefined,\n digestPreview: frame.digest_text?.substring(0, 100),\n };\n }\n\n private calculateFrameScore(\n frame: Frame,\n eventCount: number,\n anchorCount: number\n ): number {\n let score = 0.3; // Base score\n\n // Activity bonus\n score += Math.min(eventCount / 50, 0.3);\n score += Math.min(anchorCount / 10, 0.2);\n\n // Recency bonus\n const ageHours = (Date.now() / 1000 - frame.created_at) / 3600;\n if (ageHours < 1) score += 0.2;\n else if (ageHours < 6) score += 0.1;\n\n // Open frame bonus\n if (frame.state === 'active') score += 0.1;\n\n return Math.min(score, 1.0);\n }\n\n private getDominantOperations(cutoffTime: number): OperationSummary[] {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT \n e.event_type as operation,\n COUNT(*) as count,\n MAX(e.ts) as lastOccurrence,\n SUM(CASE WHEN json_extract(e.payload, '$.success') = 1 THEN 1 ELSE 0 END) as successCount\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ? AND e.ts >= ?\n GROUP BY e.event_type\n ORDER BY count DESC\n LIMIT 10\n `\n )\n .all(this.projectId, cutoffTime) as any[];\n\n return rows.map((row) => ({\n operation: row.operation,\n count: row.count,\n lastOccurrence: row.lastOccurrence * 1000,\n successRate: row.count > 0 ? row.successCount / row.count : 0,\n }));\n } catch {\n return [];\n }\n }\n\n private getFilesTouched(cutoffTime: number): FileSummary[] {\n try {\n // Extract file paths from event payloads\n const rows = this.db\n .prepare(\n `\n SELECT \n json_extract(e.payload, '$.file_path') as path,\n e.event_type as operation,\n MAX(e.ts) as lastModified\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ? \n AND e.ts >= ?\n AND json_extract(e.payload, '$.file_path') IS NOT NULL\n GROUP BY json_extract(e.payload, '$.file_path'), e.event_type\n `\n )\n .all(this.projectId, cutoffTime) as any[];\n\n // Aggregate by file\n const fileMap = new Map<string, FileSummary>();\n for (const row of rows) {\n if (!row.path) continue;\n\n const existing = fileMap.get(row.path);\n if (existing) {\n existing.operationCount++;\n existing.operations.push(row.operation);\n existing.lastModified = Math.max(\n existing.lastModified,\n row.lastModified * 1000\n );\n } else {\n fileMap.set(row.path, {\n path: row.path,\n operationCount: 1,\n lastModified: row.lastModified * 1000,\n operations: [row.operation],\n });\n }\n }\n\n return Array.from(fileMap.values())\n .sort((a, b) => b.operationCount - a.operationCount)\n .slice(0, 20);\n } catch {\n return [];\n }\n }\n\n private getErrorsEncountered(cutoffTime: number): ErrorSummary[] {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT \n json_extract(e.payload, '$.error_type') as errorType,\n json_extract(e.payload, '$.error') as message,\n COUNT(*) as count,\n MAX(e.ts) as lastOccurrence\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ? \n AND e.ts >= ?\n AND (json_extract(e.payload, '$.error') IS NOT NULL \n OR json_extract(e.payload, '$.error_type') IS NOT NULL)\n GROUP BY json_extract(e.payload, '$.error_type'), json_extract(e.payload, '$.error')\n ORDER BY count DESC\n LIMIT 15\n `\n )\n .all(this.projectId, cutoffTime) as any[];\n\n return rows.map((row) => ({\n errorType: row.errorType || 'unknown',\n message: row.message || '',\n count: row.count,\n lastOccurrence: row.lastOccurrence * 1000,\n resolved: false, // Would need more context to determine\n }));\n } catch {\n return [];\n }\n }\n\n // Helper methods for historical patterns\n\n private getTopicFrameCounts(): Record<string, number> {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT type, COUNT(*) as count\n FROM frames\n WHERE project_id = ?\n GROUP BY type\n `\n )\n .all(this.projectId) as any[];\n\n const counts: Record<string, number> = {};\n for (const row of rows) {\n counts[row.type] = row.count;\n }\n return counts;\n } catch {\n return {};\n }\n }\n\n private getKeyDecisions(): DecisionSummary[] {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT \n a.anchor_id as id,\n a.text,\n a.frame_id as frameId,\n a.created_at as timestamp,\n a.priority\n FROM anchors a\n JOIN frames f ON a.frame_id = f.frame_id\n WHERE f.project_id = ? AND a.type = 'DECISION'\n ORDER BY a.priority DESC, a.created_at DESC\n LIMIT 20\n `\n )\n .all(this.projectId) as any[];\n\n return rows.map((row) => ({\n id: row.id,\n text: row.text,\n frameId: row.frameId,\n timestamp: row.timestamp * 1000,\n impact:\n row.priority >= 7 ? 'high' : row.priority >= 4 ? 'medium' : 'low',\n }));\n } catch {\n return [];\n }\n }\n\n private getRecurringIssues(): IssueSummary[] {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT \n json_extract(e.payload, '$.error_type') as issueType,\n COUNT(*) as occurrenceCount,\n MAX(e.ts) as lastSeen\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ? \n AND json_extract(e.payload, '$.error_type') IS NOT NULL\n GROUP BY json_extract(e.payload, '$.error_type')\n HAVING COUNT(*) > 1\n ORDER BY occurrenceCount DESC\n LIMIT 10\n `\n )\n .all(this.projectId) as any[];\n\n return rows.map((row) => ({\n issueType: row.issueType,\n occurrenceCount: row.occurrenceCount,\n lastSeen: row.lastSeen * 1000,\n resolutionRate: 0.5, // Would need resolution tracking\n }));\n } catch {\n return [];\n }\n }\n\n private getCommonToolSequences(): ToolSequence[] {\n // Use trace detector if available\n if (this.traceDetector) {\n const stats = this.traceDetector.getStatistics();\n const sequences: ToolSequence[] = [];\n\n for (const [type, count] of Object.entries(stats.tracesByType)) {\n sequences.push({\n pattern: type,\n frequency: count,\n avgDuration: 0, // Would need more data\n successRate: 0.8, // Estimate\n });\n }\n\n return sequences;\n }\n\n return [];\n }\n\n private getActivityPatterns(): ActivityPattern[] {\n try {\n // Get hourly distribution\n const hourlyRows = this.db\n .prepare(\n `\n SELECT \n strftime('%H', datetime(e.ts, 'unixepoch')) as hour,\n COUNT(*) as count\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ?\n GROUP BY hour\n ORDER BY count DESC\n `\n )\n .all(this.projectId) as any[];\n\n const peakHours = hourlyRows.slice(0, 3).map((r) => `${r.hour}:00`);\n const totalEvents = hourlyRows.reduce((sum, r) => sum + r.count, 0);\n\n return [\n {\n periodType: 'hourly',\n peakPeriods: peakHours,\n avgEventsPerPeriod: totalEvents / 24,\n },\n ];\n } catch {\n return [];\n }\n }\n\n // Helper methods for queryable indices\n\n private indexByErrorType(): Record<string, string[]> {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT DISTINCT\n json_extract(e.payload, '$.error_type') as errorType,\n f.frame_id\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ? \n AND json_extract(e.payload, '$.error_type') IS NOT NULL\n `\n )\n .all(this.projectId) as any[];\n\n const index: Record<string, string[]> = {};\n for (const row of rows) {\n if (!row.errorType) continue;\n if (!index[row.errorType]) index[row.errorType] = [];\n if (!index[row.errorType].includes(row.frame_id)) {\n index[row.errorType].push(row.frame_id);\n }\n }\n return index;\n } catch {\n return {};\n }\n }\n\n private indexByTimeframe(): Record<string, string[]> {\n try {\n const now = Math.floor(Date.now() / 1000);\n const timeframes = {\n last_hour: now - 3600,\n last_day: now - 86400,\n last_week: now - 604800,\n last_month: now - 2592000,\n };\n\n const index: Record<string, string[]> = {};\n\n for (const [label, cutoff] of Object.entries(timeframes)) {\n const rows = this.db\n .prepare(\n `\n SELECT frame_id FROM frames\n WHERE project_id = ? AND created_at >= ?\n `\n )\n .all(this.projectId, cutoff) as any[];\n\n index[label] = rows.map((r) => r.frame_id);\n }\n\n return index;\n } catch {\n return {};\n }\n }\n\n private indexByContributor(): Record<string, string[]> {\n // Would need user tracking - return empty for now\n return {};\n }\n\n private indexByTopic(): Record<string, string[]> {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT frame_id, type, name FROM frames\n WHERE project_id = ?\n `\n )\n .all(this.projectId) as any[];\n\n const index: Record<string, string[]> = {};\n\n for (const row of rows) {\n // Index by frame type\n if (!index[row.type]) index[row.type] = [];\n index[row.type].push(row.frame_id);\n\n // Index by keywords in name\n const keywords = this.extractKeywords(row.name);\n for (const keyword of keywords) {\n if (!index[keyword]) index[keyword] = [];\n if (!index[keyword].includes(row.frame_id)) {\n index[keyword].push(row.frame_id);\n }\n }\n }\n\n return index;\n } catch {\n return {};\n }\n }\n\n private indexByFile(): Record<string, string[]> {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT DISTINCT\n json_extract(e.payload, '$.file_path') as filePath,\n f.frame_id\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ? \n AND json_extract(e.payload, '$.file_path') IS NOT NULL\n `\n )\n .all(this.projectId) as any[];\n\n const index: Record<string, string[]> = {};\n for (const row of rows) {\n if (!row.filePath) continue;\n if (!index[row.filePath]) index[row.filePath] = [];\n if (!index[row.filePath].includes(row.frame_id)) {\n index[row.filePath].push(row.frame_id);\n }\n }\n return index;\n } catch {\n return {};\n }\n }\n\n private extractKeywords(text: string): string[] {\n const stopWords = new Set([\n 'the',\n 'a',\n 'an',\n 'and',\n 'or',\n 'but',\n 'in',\n 'on',\n 'at',\n 'to',\n 'for',\n ]);\n return text\n .toLowerCase()\n .split(/\\W+/)\n .filter((word) => word.length > 2 && !stopWords.has(word));\n }\n\n /**\n * Clear the cache\n */\n public clearCache(): void {\n this.cache.clear();\n logger.debug('Summary cache cleared', { projectId: this.projectId });\n }\n}\n"],
4
+ "sourcesContent": ["/**\n * Compressed Summary Generator\n * Creates compact summaries of project memory for LLM analysis\n */\n\nimport Database from 'better-sqlite3';\nimport {\n FrameManager,\n Frame,\n Anchor,\n Event,\n} from '../context/index.js';\nimport { TraceDetector } from '../trace/trace-detector.js';\nimport {\n CompressedSummary,\n RecentSessionSummary,\n HistoricalPatterns,\n QueryableIndices,\n SummaryStats,\n FrameSummary,\n OperationSummary,\n FileSummary,\n ErrorSummary,\n DecisionSummary,\n IssueSummary,\n ToolSequence,\n ActivityPattern,\n RetrievalConfig,\n DEFAULT_RETRIEVAL_CONFIG,\n} from './types.js';\nimport { logger } from '../monitoring/logger.js';\n\nexport class CompressedSummaryGenerator {\n private db: Database.Database;\n private frameManager: FrameManager;\n private traceDetector?: TraceDetector;\n private projectId: string;\n private config: RetrievalConfig;\n private cache: Map<\n string,\n { summary: CompressedSummary; expiresAt: number }\n > = new Map();\n\n constructor(\n db: Database.Database,\n frameManager: FrameManager,\n projectId: string,\n config: Partial<RetrievalConfig> = {},\n traceDetector?: TraceDetector\n ) {\n this.db = db;\n this.frameManager = frameManager;\n this.projectId = projectId;\n this.config = { ...DEFAULT_RETRIEVAL_CONFIG, ...config };\n this.traceDetector = traceDetector;\n }\n\n /**\n * Generate a compressed summary for LLM analysis\n */\n public generateSummary(\n options: {\n maxFrames?: number;\n timeRangeHours?: number;\n forceRefresh?: boolean;\n } = {}\n ): CompressedSummary {\n const cacheKey = `summary_${this.projectId}_${options.maxFrames || this.config.maxSummaryFrames}`;\n\n // Check cache\n if (!options.forceRefresh) {\n const cached = this.cache.get(cacheKey);\n if (cached && cached.expiresAt > Date.now()) {\n logger.debug('Using cached summary', { projectId: this.projectId });\n return cached.summary;\n }\n }\n\n const startTime = Date.now();\n const maxFrames = options.maxFrames || this.config.maxSummaryFrames;\n const timeRangeHours = options.timeRangeHours || 24;\n\n // Generate all components\n const recentSession = this.generateRecentSessionSummary(\n maxFrames,\n timeRangeHours\n );\n const historicalPatterns = this.generateHistoricalPatterns();\n const queryableIndices = this.generateQueryableIndices();\n const stats = this.generateStats();\n\n const summary: CompressedSummary = {\n projectId: this.projectId,\n generatedAt: Date.now(),\n recentSession,\n historicalPatterns,\n queryableIndices,\n stats,\n };\n\n // Cache the result\n this.cache.set(cacheKey, {\n summary,\n expiresAt: Date.now() + this.config.cacheTtlSeconds * 1000,\n });\n\n logger.info('Generated compressed summary', {\n projectId: this.projectId,\n frames: recentSession.frames.length,\n generationTimeMs: Date.now() - startTime,\n });\n\n return summary;\n }\n\n /**\n * Generate recent session summary\n */\n private generateRecentSessionSummary(\n maxFrames: number,\n timeRangeHours: number\n ): RecentSessionSummary {\n const cutoffTime = Math.floor(Date.now() / 1000) - timeRangeHours * 3600;\n\n // Get recent frames\n const frames = this.getRecentFrames(maxFrames, cutoffTime);\n const frameSummaries = frames.map((f) => this.summarizeFrame(f));\n\n // Get dominant operations\n const dominantOperations = this.getDominantOperations(cutoffTime);\n\n // Get files touched\n const filesTouched = this.getFilesTouched(cutoffTime);\n\n // Get errors encountered\n const errorsEncountered = this.getErrorsEncountered(cutoffTime);\n\n // Calculate time range\n const timestamps = frames.map((f) => f.created_at).filter((t) => t);\n const start = timestamps.length > 0 ? Math.min(...timestamps) : cutoffTime;\n const end =\n timestamps.length > 0\n ? Math.max(...timestamps)\n : Math.floor(Date.now() / 1000);\n\n return {\n frames: frameSummaries,\n dominantOperations,\n filesTouched,\n errorsEncountered,\n timeRange: {\n start: start * 1000,\n end: end * 1000,\n durationMs: (end - start) * 1000,\n },\n };\n }\n\n /**\n * Generate historical patterns\n */\n private generateHistoricalPatterns(): HistoricalPatterns {\n return {\n topicFrameCounts: this.getTopicFrameCounts(),\n keyDecisions: this.getKeyDecisions(),\n recurringIssues: this.getRecurringIssues(),\n commonToolSequences: this.getCommonToolSequences(),\n activityPatterns: this.getActivityPatterns(),\n };\n }\n\n /**\n * Generate queryable indices\n */\n private generateQueryableIndices(): QueryableIndices {\n return {\n byErrorType: this.indexByErrorType(),\n byTimeframe: this.indexByTimeframe(),\n byContributor: this.indexByContributor(),\n byTopic: this.indexByTopic(),\n byFile: this.indexByFile(),\n };\n }\n\n /**\n * Generate summary statistics\n */\n private generateStats(): SummaryStats {\n try {\n const frameStats =\n (this.db\n .prepare(\n `\n SELECT \n COUNT(*) as totalFrames,\n MIN(created_at) as oldestFrame,\n MAX(created_at) as newestFrame,\n AVG(depth) as avgDepth\n FROM frames\n WHERE project_id = ?\n `\n )\n .get(this.projectId) as any) || {};\n\n const eventCount = (this.db\n .prepare(\n `\n SELECT COUNT(*) as count FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ?\n `\n )\n .get(this.projectId) as any) || { count: 0 };\n\n const anchorCount = (this.db\n .prepare(\n `\n SELECT COUNT(*) as count FROM anchors a\n JOIN frames f ON a.frame_id = f.frame_id\n WHERE f.project_id = ?\n `\n )\n .get(this.projectId) as any) || { count: 0 };\n\n const decisionCount = (this.db\n .prepare(\n `\n SELECT COUNT(*) as count FROM anchors a\n JOIN frames f ON a.frame_id = f.frame_id\n WHERE f.project_id = ? AND a.type = 'DECISION'\n `\n )\n .get(this.projectId) as any) || { count: 0 };\n\n const totalFrames = frameStats.totalFrames || 0;\n\n return {\n totalFrames,\n totalEvents: eventCount.count || 0,\n totalAnchors: anchorCount.count || 0,\n totalDecisions: decisionCount.count || 0,\n oldestFrame: (frameStats.oldestFrame || 0) * 1000,\n newestFrame: (frameStats.newestFrame || 0) * 1000,\n avgFrameDepth: frameStats.avgDepth || 0,\n avgEventsPerFrame:\n totalFrames > 0 ? (eventCount.count || 0) / totalFrames : 0,\n };\n } catch (error: unknown) {\n logger.warn('Error generating stats, using defaults', { error });\n return {\n totalFrames: 0,\n totalEvents: 0,\n totalAnchors: 0,\n totalDecisions: 0,\n oldestFrame: 0,\n newestFrame: 0,\n avgFrameDepth: 0,\n avgEventsPerFrame: 0,\n };\n }\n }\n\n // Helper methods for recent session\n\n private getRecentFrames(limit: number, cutoffTime: number): Frame[] {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT * FROM frames\n WHERE project_id = ? AND created_at >= ?\n ORDER BY created_at DESC\n LIMIT ?\n `\n )\n .all(this.projectId, cutoffTime, limit) as any[];\n\n return rows.map((row) => ({\n ...row,\n inputs: JSON.parse(row.inputs || '{}'),\n outputs: JSON.parse(row.outputs || '{}'),\n digest_json: JSON.parse(row.digest_json || '{}'),\n }));\n } catch {\n return [];\n }\n }\n\n private summarizeFrame(frame: Frame): FrameSummary {\n // Get event count\n const eventCount = (this.db\n .prepare(\n `\n SELECT COUNT(*) as count FROM events WHERE frame_id = ?\n `\n )\n .get(frame.frame_id) as any) || { count: 0 };\n\n // Get anchor count\n const anchorCount = (this.db\n .prepare(\n `\n SELECT COUNT(*) as count FROM anchors WHERE frame_id = ?\n `\n )\n .get(frame.frame_id) as any) || { count: 0 };\n\n // Calculate score based on activity\n const score = this.calculateFrameScore(\n frame,\n eventCount.count,\n anchorCount.count\n );\n\n return {\n frameId: frame.frame_id,\n name: frame.name,\n type: frame.type,\n depth: frame.depth,\n eventCount: eventCount.count,\n anchorCount: anchorCount.count,\n score,\n createdAt: frame.created_at * 1000,\n closedAt: frame.closed_at ? frame.closed_at * 1000 : undefined,\n digestPreview: frame.digest_text?.substring(0, 100),\n };\n }\n\n private calculateFrameScore(\n frame: Frame,\n eventCount: number,\n anchorCount: number\n ): number {\n let score = 0.3; // Base score\n\n // Activity bonus\n score += Math.min(eventCount / 50, 0.3);\n score += Math.min(anchorCount / 10, 0.2);\n\n // Recency bonus\n const ageHours = (Date.now() / 1000 - frame.created_at) / 3600;\n if (ageHours < 1) score += 0.2;\n else if (ageHours < 6) score += 0.1;\n\n // Open frame bonus\n if (frame.state === 'active') score += 0.1;\n\n return Math.min(score, 1.0);\n }\n\n private getDominantOperations(cutoffTime: number): OperationSummary[] {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT \n e.event_type as operation,\n COUNT(*) as count,\n MAX(e.ts) as lastOccurrence,\n SUM(CASE WHEN json_extract(e.payload, '$.success') = 1 THEN 1 ELSE 0 END) as successCount\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ? AND e.ts >= ?\n GROUP BY e.event_type\n ORDER BY count DESC\n LIMIT 10\n `\n )\n .all(this.projectId, cutoffTime) as any[];\n\n return rows.map((row) => ({\n operation: row.operation,\n count: row.count,\n lastOccurrence: row.lastOccurrence * 1000,\n successRate: row.count > 0 ? row.successCount / row.count : 0,\n }));\n } catch {\n return [];\n }\n }\n\n private getFilesTouched(cutoffTime: number): FileSummary[] {\n try {\n // Extract file paths from event payloads\n const rows = this.db\n .prepare(\n `\n SELECT \n json_extract(e.payload, '$.file_path') as path,\n e.event_type as operation,\n MAX(e.ts) as lastModified\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ? \n AND e.ts >= ?\n AND json_extract(e.payload, '$.file_path') IS NOT NULL\n GROUP BY json_extract(e.payload, '$.file_path'), e.event_type\n `\n )\n .all(this.projectId, cutoffTime) as any[];\n\n // Aggregate by file\n const fileMap = new Map<string, FileSummary>();\n for (const row of rows) {\n if (!row.path) continue;\n\n const existing = fileMap.get(row.path);\n if (existing) {\n existing.operationCount++;\n existing.operations.push(row.operation);\n existing.lastModified = Math.max(\n existing.lastModified,\n row.lastModified * 1000\n );\n } else {\n fileMap.set(row.path, {\n path: row.path,\n operationCount: 1,\n lastModified: row.lastModified * 1000,\n operations: [row.operation],\n });\n }\n }\n\n return Array.from(fileMap.values())\n .sort((a, b) => b.operationCount - a.operationCount)\n .slice(0, 20);\n } catch {\n return [];\n }\n }\n\n private getErrorsEncountered(cutoffTime: number): ErrorSummary[] {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT \n json_extract(e.payload, '$.error_type') as errorType,\n json_extract(e.payload, '$.error') as message,\n COUNT(*) as count,\n MAX(e.ts) as lastOccurrence\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ? \n AND e.ts >= ?\n AND (json_extract(e.payload, '$.error') IS NOT NULL \n OR json_extract(e.payload, '$.error_type') IS NOT NULL)\n GROUP BY json_extract(e.payload, '$.error_type'), json_extract(e.payload, '$.error')\n ORDER BY count DESC\n LIMIT 15\n `\n )\n .all(this.projectId, cutoffTime) as any[];\n\n return rows.map((row) => ({\n errorType: row.errorType || 'unknown',\n message: row.message || '',\n count: row.count,\n lastOccurrence: row.lastOccurrence * 1000,\n resolved: false, // Would need more context to determine\n }));\n } catch {\n return [];\n }\n }\n\n // Helper methods for historical patterns\n\n private getTopicFrameCounts(): Record<string, number> {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT type, COUNT(*) as count\n FROM frames\n WHERE project_id = ?\n GROUP BY type\n `\n )\n .all(this.projectId) as any[];\n\n const counts: Record<string, number> = {};\n for (const row of rows) {\n counts[row.type] = row.count;\n }\n return counts;\n } catch {\n return {};\n }\n }\n\n private getKeyDecisions(): DecisionSummary[] {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT \n a.anchor_id as id,\n a.text,\n a.frame_id as frameId,\n a.created_at as timestamp,\n a.priority\n FROM anchors a\n JOIN frames f ON a.frame_id = f.frame_id\n WHERE f.project_id = ? AND a.type = 'DECISION'\n ORDER BY a.priority DESC, a.created_at DESC\n LIMIT 20\n `\n )\n .all(this.projectId) as any[];\n\n return rows.map((row) => ({\n id: row.id,\n text: row.text,\n frameId: row.frameId,\n timestamp: row.timestamp * 1000,\n impact:\n row.priority >= 7 ? 'high' : row.priority >= 4 ? 'medium' : 'low',\n }));\n } catch {\n return [];\n }\n }\n\n private getRecurringIssues(): IssueSummary[] {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT \n json_extract(e.payload, '$.error_type') as issueType,\n COUNT(*) as occurrenceCount,\n MAX(e.ts) as lastSeen\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ? \n AND json_extract(e.payload, '$.error_type') IS NOT NULL\n GROUP BY json_extract(e.payload, '$.error_type')\n HAVING COUNT(*) > 1\n ORDER BY occurrenceCount DESC\n LIMIT 10\n `\n )\n .all(this.projectId) as any[];\n\n return rows.map((row) => ({\n issueType: row.issueType,\n occurrenceCount: row.occurrenceCount,\n lastSeen: row.lastSeen * 1000,\n resolutionRate: 0.5, // Would need resolution tracking\n }));\n } catch {\n return [];\n }\n }\n\n private getCommonToolSequences(): ToolSequence[] {\n // Use trace detector if available\n if (this.traceDetector) {\n const stats = this.traceDetector.getStatistics();\n const sequences: ToolSequence[] = [];\n\n for (const [type, count] of Object.entries(stats.tracesByType)) {\n sequences.push({\n pattern: type,\n frequency: count,\n avgDuration: 0, // Would need more data\n successRate: 0.8, // Estimate\n });\n }\n\n return sequences;\n }\n\n return [];\n }\n\n private getActivityPatterns(): ActivityPattern[] {\n try {\n // Get hourly distribution\n const hourlyRows = this.db\n .prepare(\n `\n SELECT \n strftime('%H', datetime(e.ts, 'unixepoch')) as hour,\n COUNT(*) as count\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ?\n GROUP BY hour\n ORDER BY count DESC\n `\n )\n .all(this.projectId) as any[];\n\n const peakHours = hourlyRows.slice(0, 3).map((r) => `${r.hour}:00`);\n const totalEvents = hourlyRows.reduce((sum, r) => sum + r.count, 0);\n\n return [\n {\n periodType: 'hourly',\n peakPeriods: peakHours,\n avgEventsPerPeriod: totalEvents / 24,\n },\n ];\n } catch {\n return [];\n }\n }\n\n // Helper methods for queryable indices\n\n private indexByErrorType(): Record<string, string[]> {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT DISTINCT\n json_extract(e.payload, '$.error_type') as errorType,\n f.frame_id\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ? \n AND json_extract(e.payload, '$.error_type') IS NOT NULL\n `\n )\n .all(this.projectId) as any[];\n\n const index: Record<string, string[]> = {};\n for (const row of rows) {\n if (!row.errorType) continue;\n if (!index[row.errorType]) index[row.errorType] = [];\n if (!index[row.errorType].includes(row.frame_id)) {\n index[row.errorType].push(row.frame_id);\n }\n }\n return index;\n } catch {\n return {};\n }\n }\n\n private indexByTimeframe(): Record<string, string[]> {\n try {\n const now = Math.floor(Date.now() / 1000);\n const timeframes = {\n last_hour: now - 3600,\n last_day: now - 86400,\n last_week: now - 604800,\n last_month: now - 2592000,\n };\n\n const index: Record<string, string[]> = {};\n\n for (const [label, cutoff] of Object.entries(timeframes)) {\n const rows = this.db\n .prepare(\n `\n SELECT frame_id FROM frames\n WHERE project_id = ? AND created_at >= ?\n `\n )\n .all(this.projectId, cutoff) as any[];\n\n index[label] = rows.map((r) => r.frame_id);\n }\n\n return index;\n } catch {\n return {};\n }\n }\n\n private indexByContributor(): Record<string, string[]> {\n // Would need user tracking - return empty for now\n return {};\n }\n\n private indexByTopic(): Record<string, string[]> {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT frame_id, type, name FROM frames\n WHERE project_id = ?\n `\n )\n .all(this.projectId) as any[];\n\n const index: Record<string, string[]> = {};\n\n for (const row of rows) {\n // Index by frame type\n if (!index[row.type]) index[row.type] = [];\n index[row.type].push(row.frame_id);\n\n // Index by keywords in name\n const keywords = this.extractKeywords(row.name);\n for (const keyword of keywords) {\n if (!index[keyword]) index[keyword] = [];\n if (!index[keyword].includes(row.frame_id)) {\n index[keyword].push(row.frame_id);\n }\n }\n }\n\n return index;\n } catch {\n return {};\n }\n }\n\n private indexByFile(): Record<string, string[]> {\n try {\n const rows = this.db\n .prepare(\n `\n SELECT DISTINCT\n json_extract(e.payload, '$.file_path') as filePath,\n f.frame_id\n FROM events e\n JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.project_id = ? \n AND json_extract(e.payload, '$.file_path') IS NOT NULL\n `\n )\n .all(this.projectId) as any[];\n\n const index: Record<string, string[]> = {};\n for (const row of rows) {\n if (!row.filePath) continue;\n if (!index[row.filePath]) index[row.filePath] = [];\n if (!index[row.filePath].includes(row.frame_id)) {\n index[row.filePath].push(row.frame_id);\n }\n }\n return index;\n } catch {\n return {};\n }\n }\n\n private extractKeywords(text: string): string[] {\n const stopWords = new Set([\n 'the',\n 'a',\n 'an',\n 'and',\n 'or',\n 'but',\n 'in',\n 'on',\n 'at',\n 'to',\n 'for',\n ]);\n return text\n .toLowerCase()\n .split(/\\W+/)\n .filter((word) => word.length > 2 && !stopWords.has(word));\n }\n\n /**\n * Clear the cache\n */\n public clearCache(): void {\n this.cache.clear();\n logger.debug('Summary cache cleared', { projectId: this.projectId });\n }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/retrieval/types.ts"],
4
- "sourcesContent": ["/**\n * Types for LLM-Driven Context Retrieval System\n * Implements intelligent context selection based on compressed summaries\n */\n\nimport { Frame, Anchor, Event } from '../context/frame-manager.js';\nimport { StackMemoryQuery } from '../query/query-parser.js';\n\n/**\n * Compressed summary of recent session activity\n */\nexport interface RecentSessionSummary {\n /** Recent frames with their key attributes */\n frames: FrameSummary[];\n /** Dominant operations performed */\n dominantOperations: OperationSummary[];\n /** Files that were touched */\n filesTouched: FileSummary[];\n /** Errors encountered */\n errorsEncountered: ErrorSummary[];\n /** Time range covered */\n timeRange: {\n start: number;\n end: number;\n durationMs: number;\n };\n}\n\nexport interface FrameSummary {\n frameId: string;\n name: string;\n type: string;\n depth: number;\n eventCount: number;\n anchorCount: number;\n score: number;\n createdAt: number;\n closedAt?: number;\n digestPreview?: string;\n}\n\nexport interface OperationSummary {\n operation: string;\n count: number;\n lastOccurrence: number;\n successRate: number;\n}\n\nexport interface FileSummary {\n path: string;\n operationCount: number;\n lastModified: number;\n operations: string[];\n}\n\nexport interface ErrorSummary {\n errorType: string;\n message: string;\n count: number;\n lastOccurrence: number;\n resolved: boolean;\n}\n\n/**\n * Historical patterns extracted from memory\n */\nexport interface HistoricalPatterns {\n /** Frame counts by topic */\n topicFrameCounts: Record<string, number>;\n /** Key decisions made */\n keyDecisions: DecisionSummary[];\n /** Recurring issues */\n recurringIssues: IssueSummary[];\n /** Common tool sequences */\n commonToolSequences: ToolSequence[];\n /** Time-based activity patterns */\n activityPatterns: ActivityPattern[];\n}\n\nexport interface DecisionSummary {\n id: string;\n text: string;\n frameId: string;\n timestamp: number;\n impact: 'low' | 'medium' | 'high';\n relatedFiles?: string[];\n}\n\nexport interface IssueSummary {\n issueType: string;\n occurrenceCount: number;\n lastSeen: number;\n resolutionRate: number;\n commonFixes?: string[];\n}\n\nexport interface ToolSequence {\n pattern: string;\n frequency: number;\n avgDuration: number;\n successRate: number;\n}\n\nexport interface ActivityPattern {\n periodType: 'hourly' | 'daily' | 'weekly';\n peakPeriods: string[];\n avgEventsPerPeriod: number;\n}\n\n/**\n * Queryable indices for fast retrieval\n */\nexport interface QueryableIndices {\n /** Index by error type */\n byErrorType: Record<string, string[]>; // errorType -> frameIds\n /** Index by timeframe */\n byTimeframe: Record<string, string[]>; // timeKey -> frameIds\n /** Index by contributor */\n byContributor: Record<string, string[]>; // userId -> frameIds\n /** Index by topic */\n byTopic: Record<string, string[]>; // topic -> frameIds\n /** Index by file */\n byFile: Record<string, string[]>; // filePath -> frameIds\n}\n\n/**\n * Complete compressed summary for LLM analysis\n */\nexport interface CompressedSummary {\n /** Project identifier */\n projectId: string;\n /** Generation timestamp */\n generatedAt: number;\n /** Recent session summary */\n recentSession: RecentSessionSummary;\n /** Historical patterns */\n historicalPatterns: HistoricalPatterns;\n /** Queryable indices */\n queryableIndices: QueryableIndices;\n /** Summary statistics */\n stats: SummaryStats;\n}\n\nexport interface SummaryStats {\n totalFrames: number;\n totalEvents: number;\n totalAnchors: number;\n totalDecisions: number;\n oldestFrame: number;\n newestFrame: number;\n avgFrameDepth: number;\n avgEventsPerFrame: number;\n}\n\n/**\n * LLM analysis request\n */\nexport interface LLMAnalysisRequest {\n /** Current user query */\n currentQuery: string;\n /** Parsed structured query */\n parsedQuery?: StackMemoryQuery;\n /** Compressed summary */\n compressedSummary: CompressedSummary;\n /** Token budget for context */\n tokenBudget: number;\n /** Optional hints for retrieval */\n hints?: RetrievalHints;\n}\n\nexport interface RetrievalHints {\n /** Prefer recent frames */\n preferRecent?: boolean;\n /** Focus on specific topics */\n focusTopics?: string[];\n /** Include error context */\n includeErrors?: boolean;\n /** Include decision history */\n includeDecisions?: boolean;\n /** Minimum relevance score */\n minRelevance?: number;\n}\n\n/**\n * LLM analysis response\n */\nexport interface LLMAnalysisResponse {\n /** Reasoning for the retrieval decision (auditable) */\n reasoning: string;\n /** Frames to retrieve with priority order */\n framesToRetrieve: FrameRetrievalPlan[];\n /** Confidence score (0.0 - 1.0) */\n confidenceScore: number;\n /** Additional context recommendations */\n recommendations: ContextRecommendation[];\n /** Analysis metadata */\n metadata: AnalysisMetadata;\n}\n\nexport interface FrameRetrievalPlan {\n frameId: string;\n priority: number; // 1-10, higher = more important\n reason: string;\n includeEvents: boolean;\n includeAnchors: boolean;\n includeDigest: boolean;\n estimatedTokens: number;\n}\n\nexport interface ContextRecommendation {\n type: 'include' | 'exclude' | 'summarize';\n target: string; // frameId, anchorId, or description\n reason: string;\n impact: 'low' | 'medium' | 'high';\n}\n\nexport interface AnalysisMetadata {\n analysisTimeMs: number;\n summaryTokens: number;\n queryComplexity: 'simple' | 'moderate' | 'complex';\n matchedPatterns: string[];\n fallbackUsed: boolean;\n}\n\n/**\n * Retrieved context result\n */\nexport interface RetrievedContext {\n /** Assembled context string */\n context: string;\n /** Frames included */\n frames: Frame[];\n /** Anchors included */\n anchors: Anchor[];\n /** Events included */\n events: Event[];\n /** LLM analysis that drove retrieval */\n analysis: LLMAnalysisResponse;\n /** Token usage */\n tokenUsage: {\n budget: number;\n used: number;\n remaining: number;\n };\n /** Retrieval metadata */\n metadata: RetrievalMetadata;\n}\n\nexport interface RetrievalMetadata {\n retrievalTimeMs: number;\n cacheHit: boolean;\n framesScanned: number;\n framesIncluded: number;\n compressionRatio: number;\n}\n\n/**\n * Configuration for the retrieval system\n */\nexport interface RetrievalConfig {\n /** Maximum frames to include in summary */\n maxSummaryFrames: number;\n /** Default token budget */\n defaultTokenBudget: number;\n /** Cache TTL in seconds */\n cacheTtlSeconds: number;\n /** Minimum confidence to use LLM suggestions */\n minConfidenceThreshold: number;\n /** Enable fallback to heuristic retrieval */\n enableFallback: boolean;\n /** LLM provider configuration */\n llmConfig: {\n provider: 'anthropic' | 'openai' | 'local';\n model: string;\n maxTokens: number;\n temperature: number;\n };\n}\n\nexport const DEFAULT_RETRIEVAL_CONFIG: RetrievalConfig = {\n maxSummaryFrames: 15,\n defaultTokenBudget: 8000,\n cacheTtlSeconds: 300,\n minConfidenceThreshold: 0.6,\n enableFallback: true,\n llmConfig: {\n provider: 'anthropic',\n model: 'claude-3-haiku-20240307',\n maxTokens: 1024,\n temperature: 0.3,\n },\n};\n"],
4
+ "sourcesContent": ["/**\n * Types for LLM-Driven Context Retrieval System\n * Implements intelligent context selection based on compressed summaries\n */\n\nimport { Frame, Anchor, Event } from '../context/index.js';\nimport { StackMemoryQuery } from '../query/query-parser.js';\n\n/**\n * Compressed summary of recent session activity\n */\nexport interface RecentSessionSummary {\n /** Recent frames with their key attributes */\n frames: FrameSummary[];\n /** Dominant operations performed */\n dominantOperations: OperationSummary[];\n /** Files that were touched */\n filesTouched: FileSummary[];\n /** Errors encountered */\n errorsEncountered: ErrorSummary[];\n /** Time range covered */\n timeRange: {\n start: number;\n end: number;\n durationMs: number;\n };\n}\n\nexport interface FrameSummary {\n frameId: string;\n name: string;\n type: string;\n depth: number;\n eventCount: number;\n anchorCount: number;\n score: number;\n createdAt: number;\n closedAt?: number;\n digestPreview?: string;\n}\n\nexport interface OperationSummary {\n operation: string;\n count: number;\n lastOccurrence: number;\n successRate: number;\n}\n\nexport interface FileSummary {\n path: string;\n operationCount: number;\n lastModified: number;\n operations: string[];\n}\n\nexport interface ErrorSummary {\n errorType: string;\n message: string;\n count: number;\n lastOccurrence: number;\n resolved: boolean;\n}\n\n/**\n * Historical patterns extracted from memory\n */\nexport interface HistoricalPatterns {\n /** Frame counts by topic */\n topicFrameCounts: Record<string, number>;\n /** Key decisions made */\n keyDecisions: DecisionSummary[];\n /** Recurring issues */\n recurringIssues: IssueSummary[];\n /** Common tool sequences */\n commonToolSequences: ToolSequence[];\n /** Time-based activity patterns */\n activityPatterns: ActivityPattern[];\n}\n\nexport interface DecisionSummary {\n id: string;\n text: string;\n frameId: string;\n timestamp: number;\n impact: 'low' | 'medium' | 'high';\n relatedFiles?: string[];\n}\n\nexport interface IssueSummary {\n issueType: string;\n occurrenceCount: number;\n lastSeen: number;\n resolutionRate: number;\n commonFixes?: string[];\n}\n\nexport interface ToolSequence {\n pattern: string;\n frequency: number;\n avgDuration: number;\n successRate: number;\n}\n\nexport interface ActivityPattern {\n periodType: 'hourly' | 'daily' | 'weekly';\n peakPeriods: string[];\n avgEventsPerPeriod: number;\n}\n\n/**\n * Queryable indices for fast retrieval\n */\nexport interface QueryableIndices {\n /** Index by error type */\n byErrorType: Record<string, string[]>; // errorType -> frameIds\n /** Index by timeframe */\n byTimeframe: Record<string, string[]>; // timeKey -> frameIds\n /** Index by contributor */\n byContributor: Record<string, string[]>; // userId -> frameIds\n /** Index by topic */\n byTopic: Record<string, string[]>; // topic -> frameIds\n /** Index by file */\n byFile: Record<string, string[]>; // filePath -> frameIds\n}\n\n/**\n * Complete compressed summary for LLM analysis\n */\nexport interface CompressedSummary {\n /** Project identifier */\n projectId: string;\n /** Generation timestamp */\n generatedAt: number;\n /** Recent session summary */\n recentSession: RecentSessionSummary;\n /** Historical patterns */\n historicalPatterns: HistoricalPatterns;\n /** Queryable indices */\n queryableIndices: QueryableIndices;\n /** Summary statistics */\n stats: SummaryStats;\n}\n\nexport interface SummaryStats {\n totalFrames: number;\n totalEvents: number;\n totalAnchors: number;\n totalDecisions: number;\n oldestFrame: number;\n newestFrame: number;\n avgFrameDepth: number;\n avgEventsPerFrame: number;\n}\n\n/**\n * LLM analysis request\n */\nexport interface LLMAnalysisRequest {\n /** Current user query */\n currentQuery: string;\n /** Parsed structured query */\n parsedQuery?: StackMemoryQuery;\n /** Compressed summary */\n compressedSummary: CompressedSummary;\n /** Token budget for context */\n tokenBudget: number;\n /** Optional hints for retrieval */\n hints?: RetrievalHints;\n}\n\nexport interface RetrievalHints {\n /** Prefer recent frames */\n preferRecent?: boolean;\n /** Focus on specific topics */\n focusTopics?: string[];\n /** Include error context */\n includeErrors?: boolean;\n /** Include decision history */\n includeDecisions?: boolean;\n /** Minimum relevance score */\n minRelevance?: number;\n}\n\n/**\n * LLM analysis response\n */\nexport interface LLMAnalysisResponse {\n /** Reasoning for the retrieval decision (auditable) */\n reasoning: string;\n /** Frames to retrieve with priority order */\n framesToRetrieve: FrameRetrievalPlan[];\n /** Confidence score (0.0 - 1.0) */\n confidenceScore: number;\n /** Additional context recommendations */\n recommendations: ContextRecommendation[];\n /** Analysis metadata */\n metadata: AnalysisMetadata;\n}\n\nexport interface FrameRetrievalPlan {\n frameId: string;\n priority: number; // 1-10, higher = more important\n reason: string;\n includeEvents: boolean;\n includeAnchors: boolean;\n includeDigest: boolean;\n estimatedTokens: number;\n}\n\nexport interface ContextRecommendation {\n type: 'include' | 'exclude' | 'summarize';\n target: string; // frameId, anchorId, or description\n reason: string;\n impact: 'low' | 'medium' | 'high';\n}\n\nexport interface AnalysisMetadata {\n analysisTimeMs: number;\n summaryTokens: number;\n queryComplexity: 'simple' | 'moderate' | 'complex';\n matchedPatterns: string[];\n fallbackUsed: boolean;\n}\n\n/**\n * Retrieved context result\n */\nexport interface RetrievedContext {\n /** Assembled context string */\n context: string;\n /** Frames included */\n frames: Frame[];\n /** Anchors included */\n anchors: Anchor[];\n /** Events included */\n events: Event[];\n /** LLM analysis that drove retrieval */\n analysis: LLMAnalysisResponse;\n /** Token usage */\n tokenUsage: {\n budget: number;\n used: number;\n remaining: number;\n };\n /** Retrieval metadata */\n metadata: RetrievalMetadata;\n}\n\nexport interface RetrievalMetadata {\n retrievalTimeMs: number;\n cacheHit: boolean;\n framesScanned: number;\n framesIncluded: number;\n compressionRatio: number;\n}\n\n/**\n * Configuration for the retrieval system\n */\nexport interface RetrievalConfig {\n /** Maximum frames to include in summary */\n maxSummaryFrames: number;\n /** Default token budget */\n defaultTokenBudget: number;\n /** Cache TTL in seconds */\n cacheTtlSeconds: number;\n /** Minimum confidence to use LLM suggestions */\n minConfidenceThreshold: number;\n /** Enable fallback to heuristic retrieval */\n enableFallback: boolean;\n /** LLM provider configuration */\n llmConfig: {\n provider: 'anthropic' | 'openai' | 'local';\n model: string;\n maxTokens: number;\n temperature: number;\n };\n}\n\nexport const DEFAULT_RETRIEVAL_CONFIG: RetrievalConfig = {\n maxSummaryFrames: 15,\n defaultTokenBudget: 8000,\n cacheTtlSeconds: 300,\n minConfidenceThreshold: 0.6,\n enableFallback: true,\n llmConfig: {\n provider: 'anthropic',\n model: 'claude-3-haiku-20240307',\n maxTokens: 1024,\n temperature: 0.3,\n },\n};\n"],
5
5
  "mappings": ";;;;AAuRO,MAAM,2BAA4C;AAAA,EACvD,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,iBAAiB;AAAA,EACjB,wBAAwB;AAAA,EACxB,gBAAgB;AAAA,EAChB,WAAW;AAAA,IACT,UAAU;AAAA,IACV,OAAO;AAAA,IACP,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/storage/chromadb-adapter.ts"],
4
- "sourcesContent": ["/**\n * ChromaDB Storage Adapter for StackMemory\n *\n * Provides vector storage and semantic search capabilities for context data\n * using ChromaDB cloud service with user and team segmentation.\n */\n\nimport { CloudClient, Collection } from 'chromadb';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Frame } from '../context/frame-manager.js';\nimport { Logger } from '../monitoring/logger.js';\n\ninterface ChromaDocument {\n id: string;\n document: string;\n metadata: {\n user_id: string;\n team_id?: string;\n frame_id: string;\n session_id: string;\n project_name: string;\n timestamp: number;\n type: 'frame' | 'decision' | 'observation' | 'context';\n score?: number;\n tags?: string[];\n };\n}\n\ninterface ChromaConfig {\n apiKey: string;\n tenant: string;\n database: string;\n collectionName?: string;\n}\n\nexport class ChromaDBAdapter {\n private client: CloudClient;\n private collection: Collection | null = null;\n private logger: Logger;\n private config: ChromaConfig;\n private userId: string;\n private teamId?: string;\n\n constructor(config: ChromaConfig, userId: string, teamId?: string) {\n this.config = config;\n this.userId = userId;\n this.teamId = teamId;\n this.logger = new Logger('ChromaDBAdapter');\n\n // Initialize ChromaDB client\n this.client = new CloudClient({\n apiKey: config.apiKey,\n tenant: config.tenant,\n database: config.database,\n });\n }\n\n async initialize(): Promise<void> {\n try {\n const collectionName =\n this.config.collectionName || 'stackmemory_contexts';\n\n // Get or create collection with metadata for filtering\n this.collection = await this.client.getOrCreateCollection({\n name: collectionName,\n metadata: {\n description: 'StackMemory context storage',\n version: '1.0.0',\n created_at: new Date().toISOString(),\n },\n });\n\n this.logger.info(`ChromaDB collection '${collectionName}' initialized`);\n } catch (error: unknown) {\n this.logger.error('Failed to initialize ChromaDB collection', error);\n throw error;\n }\n }\n\n /**\n * Store a frame in ChromaDB\n */\n async storeFrame(frame: Frame): Promise<void> {\n if (!this.collection) {\n throw new Error('ChromaDB not initialized');\n }\n\n try {\n // Prepare document from frame\n const frameMetadata: any = {\n user_id: this.userId,\n frame_id: frame.frameId,\n session_id: frame.sessionId || 'unknown',\n project_name: frame.projectName || 'default',\n timestamp: frame.timestamp,\n type: 'frame',\n score: frame.score,\n tags: frame.tags || [],\n };\n\n // Only add team_id if it exists\n if (this.teamId) {\n frameMetadata.team_id = this.teamId;\n }\n\n const document: ChromaDocument = {\n id: `frame_${frame.frameId}_${this.userId}`,\n document: this.frameToDocument(frame),\n metadata: frameMetadata,\n };\n\n // Add to ChromaDB\n await this.collection.add({\n ids: [document.id],\n documents: [document.document],\n metadatas: [document.metadata],\n });\n\n this.logger.debug(\n `Stored frame ${frame.frameId} for user ${this.userId}`\n );\n } catch (error: unknown) {\n this.logger.error(`Failed to store frame ${frame.frameId}`, error);\n throw error;\n }\n }\n\n /**\n * Store a decision or observation\n */\n async storeContext(\n type: 'decision' | 'observation',\n content: string,\n metadata?: Record<string, any>\n ): Promise<void> {\n if (!this.collection) {\n throw new Error('ChromaDB not initialized');\n }\n\n try {\n const contextId = `${type}_${uuidv4()}_${this.userId}`;\n\n const documentMetadata: any = {\n user_id: this.userId,\n frame_id: metadata?.frame_id || 'none',\n session_id: metadata?.session_id || 'unknown',\n project_name: metadata?.project_name || 'default',\n timestamp: Date.now(),\n type,\n ...metadata,\n };\n\n // Only add team_id if it exists (ChromaDB doesn't accept undefined values)\n if (this.teamId) {\n documentMetadata.team_id = this.teamId;\n }\n\n const document: ChromaDocument = {\n id: contextId,\n document: content,\n metadata: documentMetadata,\n };\n\n await this.collection.add({\n ids: [document.id],\n documents: [document.document],\n metadatas: [document.metadata],\n });\n\n this.logger.debug(`Stored ${type} for user ${this.userId}`);\n } catch (error: unknown) {\n this.logger.error(`Failed to store ${type}`, error);\n throw error;\n }\n }\n\n /**\n * Query contexts by semantic similarity\n */\n async queryContexts(\n query: string,\n limit: number = 10,\n filters?: {\n type?: string[];\n projectName?: string;\n sessionId?: string;\n startTime?: number;\n endTime?: number;\n }\n ): Promise<Array<{ content: string; metadata: any; distance: number }>> {\n if (!this.collection) {\n throw new Error('ChromaDB not initialized');\n }\n\n try {\n // Build where clause for filtering\n const whereClause: any = {\n user_id: this.userId,\n };\n\n // Add team filter if applicable\n if (this.teamId) {\n whereClause['$or'] = [\n { team_id: this.teamId },\n { user_id: this.userId },\n ];\n }\n\n // Add additional filters\n if (filters?.type && filters.type.length > 0) {\n whereClause.type = { $in: filters.type };\n }\n\n if (filters?.projectName) {\n whereClause.project_name = filters.projectName;\n }\n\n if (filters?.sessionId) {\n whereClause.session_id = filters.sessionId;\n }\n\n if (filters?.startTime || filters?.endTime) {\n whereClause.timestamp = {};\n if (filters.startTime) {\n whereClause.timestamp.$gte = filters.startTime;\n }\n if (filters.endTime) {\n whereClause.timestamp.$lte = filters.endTime;\n }\n }\n\n // Query ChromaDB\n const results = await this.collection.query({\n queryTexts: [query],\n nResults: limit,\n where: whereClause,\n include: ['documents', 'metadatas', 'distances'],\n });\n\n // Format results\n const contexts: Array<{\n content: string;\n metadata: any;\n distance: number;\n }> = [];\n\n if (results.documents && results.documents[0]) {\n for (let i = 0; i < results.documents[0].length; i++) {\n contexts.push({\n content: results.documents[0][i] || '',\n metadata: results.metadatas?.[0]?.[i] || {},\n distance: results.distances?.[0]?.[i] || 0,\n });\n }\n }\n\n this.logger.debug(`Found ${contexts.length} contexts for query`);\n return contexts;\n } catch (error: unknown) {\n this.logger.error('Failed to query contexts', error);\n throw error;\n }\n }\n\n /**\n * Get user's recent contexts\n */\n async getRecentContexts(\n limit: number = 20,\n type?: string\n ): Promise<Array<{ content: string; metadata: any }>> {\n if (!this.collection) {\n throw new Error('ChromaDB not initialized');\n }\n\n try {\n const whereClause: any = {\n user_id: this.userId,\n };\n\n if (type) {\n whereClause.type = type;\n }\n\n // Get all documents for the user (ChromaDB doesn't support direct ordering)\n const results = await this.collection.get({\n where: whereClause,\n include: ['documents', 'metadatas'],\n });\n\n // Sort by timestamp and limit\n const contexts: Array<{ content: string; metadata: any }> = [];\n\n if (results.documents) {\n const indexed = results.documents.map((doc, i) => ({\n content: doc || '',\n metadata: results.metadatas?.[i] || {},\n }));\n\n // Sort by timestamp descending\n indexed.sort(\n (a, b) => (b.metadata.timestamp || 0) - (a.metadata.timestamp || 0)\n );\n\n // Take limit\n contexts.push(...indexed.slice(0, limit));\n }\n\n return contexts;\n } catch (error: unknown) {\n this.logger.error('Failed to get recent contexts', error);\n throw error;\n }\n }\n\n /**\n * Delete old contexts (retention policy)\n */\n async deleteOldContexts(olderThanDays: number = 30): Promise<number> {\n if (!this.collection) {\n throw new Error('ChromaDB not initialized');\n }\n\n try {\n const cutoffTime = Date.now() - olderThanDays * 24 * 60 * 60 * 1000;\n\n // Get old documents\n const results = await this.collection.get({\n where: {\n user_id: this.userId,\n timestamp: { $lt: cutoffTime },\n },\n include: ['ids'],\n });\n\n if (!results.ids || results.ids.length === 0) {\n return 0;\n }\n\n // Delete old documents\n await this.collection.delete({\n ids: results.ids,\n });\n\n this.logger.info(`Deleted ${results.ids.length} old contexts`);\n return results.ids.length;\n } catch (error: unknown) {\n this.logger.error('Failed to delete old contexts', error);\n throw error;\n }\n }\n\n /**\n * Get team contexts (if user is part of a team)\n */\n async getTeamContexts(\n limit: number = 20\n ): Promise<Array<{ content: string; metadata: any }>> {\n if (!this.collection || !this.teamId) {\n return [];\n }\n\n try {\n const results = await this.collection.get({\n where: {\n team_id: this.teamId,\n },\n include: ['documents', 'metadatas'],\n limit,\n });\n\n const contexts: Array<{ content: string; metadata: any }> = [];\n\n if (results.documents) {\n for (let i = 0; i < results.documents.length; i++) {\n contexts.push({\n content: results.documents[i] || '',\n metadata: results.metadatas?.[i] || {},\n });\n }\n }\n\n return contexts;\n } catch (error: unknown) {\n this.logger.error('Failed to get team contexts', error);\n return [];\n }\n }\n\n /**\n * Convert frame to searchable document\n */\n private frameToDocument(frame: Frame): string {\n const parts = [\n `Frame: ${frame.title}`,\n `Type: ${frame.type}`,\n `Status: ${frame.status}`,\n ];\n\n if (frame.description) {\n parts.push(`Description: ${frame.description}`);\n }\n\n if (frame.inputs && frame.inputs.length > 0) {\n parts.push(`Inputs: ${frame.inputs.join(', ')}`);\n }\n\n if (frame.outputs && frame.outputs.length > 0) {\n parts.push(`Outputs: ${frame.outputs.join(', ')}`);\n }\n\n if (frame.tags && frame.tags.length > 0) {\n parts.push(`Tags: ${frame.tags.join(', ')}`);\n }\n\n if (frame.digest_json) {\n try {\n const digest = JSON.parse(frame.digest_json);\n if (digest.summary) {\n parts.push(`Summary: ${digest.summary}`);\n }\n if (digest.keyDecisions) {\n parts.push(`Decisions: ${digest.keyDecisions.join('. ')}`);\n }\n } catch {\n // Ignore parse errors\n }\n }\n\n return parts.join('\\n');\n }\n\n /**\n * Update team ID for a user\n */\n async updateTeamId(newTeamId: string): Promise<void> {\n this.teamId = newTeamId;\n this.logger.info(`Updated team ID to ${newTeamId} for user ${this.userId}`);\n }\n\n /**\n * Get storage statistics\n */\n async getStats(): Promise<{\n totalDocuments: number;\n userDocuments: number;\n teamDocuments?: number;\n documentsByType: Record<string, number>;\n }> {\n if (!this.collection) {\n throw new Error('ChromaDB not initialized');\n }\n\n try {\n // Get user documents\n const userResults = await this.collection.get({\n where: { user_id: this.userId },\n include: ['metadatas'],\n });\n\n const stats: any = {\n totalDocuments: 0,\n userDocuments: userResults.ids?.length || 0,\n documentsByType: {},\n };\n\n // Count by type\n if (userResults.metadatas) {\n for (const metadata of userResults.metadatas) {\n const type = metadata?.type || 'unknown';\n stats.documentsByType[type] = (stats.documentsByType[type] || 0) + 1;\n }\n }\n\n // Get team documents if applicable\n if (this.teamId) {\n const teamResults = await this.collection.get({\n where: { team_id: this.teamId },\n include: ['ids'],\n });\n stats.teamDocuments = teamResults.ids?.length || 0;\n }\n\n stats.totalDocuments = stats.userDocuments + (stats.teamDocuments || 0);\n\n return stats;\n } catch (error: unknown) {\n this.logger.error('Failed to get stats', error);\n throw error;\n }\n }\n}\n"],
4
+ "sourcesContent": ["/**\n * ChromaDB Storage Adapter for StackMemory\n *\n * Provides vector storage and semantic search capabilities for context data\n * using ChromaDB cloud service with user and team segmentation.\n */\n\nimport { CloudClient, Collection } from 'chromadb';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Frame } from '../context/index.js';\nimport { Logger } from '../monitoring/logger.js';\n\ninterface ChromaDocument {\n id: string;\n document: string;\n metadata: {\n user_id: string;\n team_id?: string;\n frame_id: string;\n session_id: string;\n project_name: string;\n timestamp: number;\n type: 'frame' | 'decision' | 'observation' | 'context';\n score?: number;\n tags?: string[];\n };\n}\n\ninterface ChromaConfig {\n apiKey: string;\n tenant: string;\n database: string;\n collectionName?: string;\n}\n\nexport class ChromaDBAdapter {\n private client: CloudClient;\n private collection: Collection | null = null;\n private logger: Logger;\n private config: ChromaConfig;\n private userId: string;\n private teamId?: string;\n\n constructor(config: ChromaConfig, userId: string, teamId?: string) {\n this.config = config;\n this.userId = userId;\n this.teamId = teamId;\n this.logger = new Logger('ChromaDBAdapter');\n\n // Initialize ChromaDB client\n this.client = new CloudClient({\n apiKey: config.apiKey,\n tenant: config.tenant,\n database: config.database,\n });\n }\n\n async initialize(): Promise<void> {\n try {\n const collectionName =\n this.config.collectionName || 'stackmemory_contexts';\n\n // Get or create collection with metadata for filtering\n this.collection = await this.client.getOrCreateCollection({\n name: collectionName,\n metadata: {\n description: 'StackMemory context storage',\n version: '1.0.0',\n created_at: new Date().toISOString(),\n },\n });\n\n this.logger.info(`ChromaDB collection '${collectionName}' initialized`);\n } catch (error: unknown) {\n this.logger.error('Failed to initialize ChromaDB collection', error);\n throw error;\n }\n }\n\n /**\n * Store a frame in ChromaDB\n */\n async storeFrame(frame: Frame): Promise<void> {\n if (!this.collection) {\n throw new Error('ChromaDB not initialized');\n }\n\n try {\n // Prepare document from frame\n const frameMetadata: any = {\n user_id: this.userId,\n frame_id: frame.frameId,\n session_id: frame.sessionId || 'unknown',\n project_name: frame.projectName || 'default',\n timestamp: frame.timestamp,\n type: 'frame',\n score: frame.score,\n tags: frame.tags || [],\n };\n\n // Only add team_id if it exists\n if (this.teamId) {\n frameMetadata.team_id = this.teamId;\n }\n\n const document: ChromaDocument = {\n id: `frame_${frame.frameId}_${this.userId}`,\n document: this.frameToDocument(frame),\n metadata: frameMetadata,\n };\n\n // Add to ChromaDB\n await this.collection.add({\n ids: [document.id],\n documents: [document.document],\n metadatas: [document.metadata],\n });\n\n this.logger.debug(\n `Stored frame ${frame.frameId} for user ${this.userId}`\n );\n } catch (error: unknown) {\n this.logger.error(`Failed to store frame ${frame.frameId}`, error);\n throw error;\n }\n }\n\n /**\n * Store a decision or observation\n */\n async storeContext(\n type: 'decision' | 'observation',\n content: string,\n metadata?: Record<string, any>\n ): Promise<void> {\n if (!this.collection) {\n throw new Error('ChromaDB not initialized');\n }\n\n try {\n const contextId = `${type}_${uuidv4()}_${this.userId}`;\n\n const documentMetadata: any = {\n user_id: this.userId,\n frame_id: metadata?.frame_id || 'none',\n session_id: metadata?.session_id || 'unknown',\n project_name: metadata?.project_name || 'default',\n timestamp: Date.now(),\n type,\n ...metadata,\n };\n\n // Only add team_id if it exists (ChromaDB doesn't accept undefined values)\n if (this.teamId) {\n documentMetadata.team_id = this.teamId;\n }\n\n const document: ChromaDocument = {\n id: contextId,\n document: content,\n metadata: documentMetadata,\n };\n\n await this.collection.add({\n ids: [document.id],\n documents: [document.document],\n metadatas: [document.metadata],\n });\n\n this.logger.debug(`Stored ${type} for user ${this.userId}`);\n } catch (error: unknown) {\n this.logger.error(`Failed to store ${type}`, error);\n throw error;\n }\n }\n\n /**\n * Query contexts by semantic similarity\n */\n async queryContexts(\n query: string,\n limit: number = 10,\n filters?: {\n type?: string[];\n projectName?: string;\n sessionId?: string;\n startTime?: number;\n endTime?: number;\n }\n ): Promise<Array<{ content: string; metadata: any; distance: number }>> {\n if (!this.collection) {\n throw new Error('ChromaDB not initialized');\n }\n\n try {\n // Build where clause for filtering\n const whereClause: any = {\n user_id: this.userId,\n };\n\n // Add team filter if applicable\n if (this.teamId) {\n whereClause['$or'] = [\n { team_id: this.teamId },\n { user_id: this.userId },\n ];\n }\n\n // Add additional filters\n if (filters?.type && filters.type.length > 0) {\n whereClause.type = { $in: filters.type };\n }\n\n if (filters?.projectName) {\n whereClause.project_name = filters.projectName;\n }\n\n if (filters?.sessionId) {\n whereClause.session_id = filters.sessionId;\n }\n\n if (filters?.startTime || filters?.endTime) {\n whereClause.timestamp = {};\n if (filters.startTime) {\n whereClause.timestamp.$gte = filters.startTime;\n }\n if (filters.endTime) {\n whereClause.timestamp.$lte = filters.endTime;\n }\n }\n\n // Query ChromaDB\n const results = await this.collection.query({\n queryTexts: [query],\n nResults: limit,\n where: whereClause,\n include: ['documents', 'metadatas', 'distances'],\n });\n\n // Format results\n const contexts: Array<{\n content: string;\n metadata: any;\n distance: number;\n }> = [];\n\n if (results.documents && results.documents[0]) {\n for (let i = 0; i < results.documents[0].length; i++) {\n contexts.push({\n content: results.documents[0][i] || '',\n metadata: results.metadatas?.[0]?.[i] || {},\n distance: results.distances?.[0]?.[i] || 0,\n });\n }\n }\n\n this.logger.debug(`Found ${contexts.length} contexts for query`);\n return contexts;\n } catch (error: unknown) {\n this.logger.error('Failed to query contexts', error);\n throw error;\n }\n }\n\n /**\n * Get user's recent contexts\n */\n async getRecentContexts(\n limit: number = 20,\n type?: string\n ): Promise<Array<{ content: string; metadata: any }>> {\n if (!this.collection) {\n throw new Error('ChromaDB not initialized');\n }\n\n try {\n const whereClause: any = {\n user_id: this.userId,\n };\n\n if (type) {\n whereClause.type = type;\n }\n\n // Get all documents for the user (ChromaDB doesn't support direct ordering)\n const results = await this.collection.get({\n where: whereClause,\n include: ['documents', 'metadatas'],\n });\n\n // Sort by timestamp and limit\n const contexts: Array<{ content: string; metadata: any }> = [];\n\n if (results.documents) {\n const indexed = results.documents.map((doc, i) => ({\n content: doc || '',\n metadata: results.metadatas?.[i] || {},\n }));\n\n // Sort by timestamp descending\n indexed.sort(\n (a, b) => (b.metadata.timestamp || 0) - (a.metadata.timestamp || 0)\n );\n\n // Take limit\n contexts.push(...indexed.slice(0, limit));\n }\n\n return contexts;\n } catch (error: unknown) {\n this.logger.error('Failed to get recent contexts', error);\n throw error;\n }\n }\n\n /**\n * Delete old contexts (retention policy)\n */\n async deleteOldContexts(olderThanDays: number = 30): Promise<number> {\n if (!this.collection) {\n throw new Error('ChromaDB not initialized');\n }\n\n try {\n const cutoffTime = Date.now() - olderThanDays * 24 * 60 * 60 * 1000;\n\n // Get old documents\n const results = await this.collection.get({\n where: {\n user_id: this.userId,\n timestamp: { $lt: cutoffTime },\n },\n include: ['ids'],\n });\n\n if (!results.ids || results.ids.length === 0) {\n return 0;\n }\n\n // Delete old documents\n await this.collection.delete({\n ids: results.ids,\n });\n\n this.logger.info(`Deleted ${results.ids.length} old contexts`);\n return results.ids.length;\n } catch (error: unknown) {\n this.logger.error('Failed to delete old contexts', error);\n throw error;\n }\n }\n\n /**\n * Get team contexts (if user is part of a team)\n */\n async getTeamContexts(\n limit: number = 20\n ): Promise<Array<{ content: string; metadata: any }>> {\n if (!this.collection || !this.teamId) {\n return [];\n }\n\n try {\n const results = await this.collection.get({\n where: {\n team_id: this.teamId,\n },\n include: ['documents', 'metadatas'],\n limit,\n });\n\n const contexts: Array<{ content: string; metadata: any }> = [];\n\n if (results.documents) {\n for (let i = 0; i < results.documents.length; i++) {\n contexts.push({\n content: results.documents[i] || '',\n metadata: results.metadatas?.[i] || {},\n });\n }\n }\n\n return contexts;\n } catch (error: unknown) {\n this.logger.error('Failed to get team contexts', error);\n return [];\n }\n }\n\n /**\n * Convert frame to searchable document\n */\n private frameToDocument(frame: Frame): string {\n const parts = [\n `Frame: ${frame.title}`,\n `Type: ${frame.type}`,\n `Status: ${frame.status}`,\n ];\n\n if (frame.description) {\n parts.push(`Description: ${frame.description}`);\n }\n\n if (frame.inputs && frame.inputs.length > 0) {\n parts.push(`Inputs: ${frame.inputs.join(', ')}`);\n }\n\n if (frame.outputs && frame.outputs.length > 0) {\n parts.push(`Outputs: ${frame.outputs.join(', ')}`);\n }\n\n if (frame.tags && frame.tags.length > 0) {\n parts.push(`Tags: ${frame.tags.join(', ')}`);\n }\n\n if (frame.digest_json) {\n try {\n const digest = JSON.parse(frame.digest_json);\n if (digest.summary) {\n parts.push(`Summary: ${digest.summary}`);\n }\n if (digest.keyDecisions) {\n parts.push(`Decisions: ${digest.keyDecisions.join('. ')}`);\n }\n } catch {\n // Ignore parse errors\n }\n }\n\n return parts.join('\\n');\n }\n\n /**\n * Update team ID for a user\n */\n async updateTeamId(newTeamId: string): Promise<void> {\n this.teamId = newTeamId;\n this.logger.info(`Updated team ID to ${newTeamId} for user ${this.userId}`);\n }\n\n /**\n * Get storage statistics\n */\n async getStats(): Promise<{\n totalDocuments: number;\n userDocuments: number;\n teamDocuments?: number;\n documentsByType: Record<string, number>;\n }> {\n if (!this.collection) {\n throw new Error('ChromaDB not initialized');\n }\n\n try {\n // Get user documents\n const userResults = await this.collection.get({\n where: { user_id: this.userId },\n include: ['metadatas'],\n });\n\n const stats: any = {\n totalDocuments: 0,\n userDocuments: userResults.ids?.length || 0,\n documentsByType: {},\n };\n\n // Count by type\n if (userResults.metadatas) {\n for (const metadata of userResults.metadatas) {\n const type = metadata?.type || 'unknown';\n stats.documentsByType[type] = (stats.documentsByType[type] || 0) + 1;\n }\n }\n\n // Get team documents if applicable\n if (this.teamId) {\n const teamResults = await this.collection.get({\n where: { team_id: this.teamId },\n include: ['ids'],\n });\n stats.teamDocuments = teamResults.ids?.length || 0;\n }\n\n stats.totalDocuments = stats.userDocuments + (stats.teamDocuments || 0);\n\n return stats;\n } catch (error: unknown) {\n this.logger.error('Failed to get stats', error);\n throw error;\n }\n }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }