CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
memory.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| d0bc8c2 | 1 | /** |
| 2 | * Platform memory store. | |
| 3 | * | |
| 4 | * Persistent key-value memory for the AI subsystem. Stores facts, | |
| 5 | * decisions, and learned context that should survive across requests | |
| 6 | * and server restarts. Uses the database as backing store. | |
| 7 | * | |
| 8 | * Categories: | |
| 9 | * - "pattern" — learned code review patterns | |
| 10 | * - "decision" — architectural decisions and their rationale | |
| 11 | * - "context" — project-specific context (conventions, tech debt, etc.) | |
| 12 | * - "metric" — performance baselines and thresholds | |
| 13 | * - "feedback" — user feedback signals | |
| 14 | */ | |
| 15 | ||
| 16 | import { eq, and, desc, gte, ilike, sql } from "drizzle-orm"; | |
| 17 | import { db } from "../db"; | |
| 18 | import { reviewPatterns } from "../db/schema"; | |
| 19 | ||
| 20 | interface MemoryEntry { | |
| 21 | key: string; | |
| 22 | value: string; | |
| 23 | category: string; | |
| 24 | confidence: number; | |
| 25 | createdAt: Date; | |
| 26 | updatedAt: Date; | |
| 27 | } | |
| 28 | ||
| 29 | // In-memory cache for hot-path lookups (pattern context injection) | |
| 30 | const memoryCache = new Map<string, { value: string; expiresAt: number }>(); | |
| 31 | const CACHE_TTL = 5 * 60 * 1000; // 5 minutes | |
| 32 | ||
| 33 | /** | |
| 34 | * Store a memory entry. Upserts by key. | |
| 35 | */ | |
| 36 | export async function memoryStore( | |
| 37 | key: string, | |
| 38 | value: string, | |
| 39 | opts: { | |
| 40 | category?: string; | |
| 41 | confidence?: number; | |
| 42 | scope?: string; | |
| 43 | language?: string; | |
| 44 | } = {} | |
| 45 | ): Promise<void> { | |
| 46 | const category = opts.category ?? "context"; | |
| 47 | const confidence = opts.confidence ?? 70; | |
| 48 | ||
| 49 | try { | |
| 50 | const existing = await db | |
| 51 | .select() | |
| 52 | .from(reviewPatterns) | |
| 53 | .where( | |
| 54 | and( | |
| 55 | eq(reviewPatterns.pattern, key), | |
| 56 | eq(reviewPatterns.category, category) | |
| 57 | ) | |
| 58 | ) | |
| 59 | .limit(1); | |
| 60 | ||
| 61 | if (existing.length > 0) { | |
| 62 | await db | |
| 63 | .update(reviewPatterns) | |
| 64 | .set({ | |
| 65 | pattern: value, | |
| 66 | confidence, | |
| 67 | lastSeenAt: new Date(), | |
| 68 | }) | |
| 69 | .where(eq(reviewPatterns.id, existing[0].id)); | |
| 70 | } else { | |
| 71 | await db.insert(reviewPatterns).values({ | |
| 72 | scope: opts.scope ?? "global", | |
| 73 | language: opts.language ?? null, | |
| 74 | category, | |
| 75 | pattern: value, | |
| 76 | confidence, | |
| 77 | evidenceCount: 1, | |
| 78 | }); | |
| 79 | } | |
| 80 | ||
| 81 | // Update cache | |
| 82 | memoryCache.set(`${category}:${key}`, { | |
| 83 | value, | |
| 84 | expiresAt: Date.now() + CACHE_TTL, | |
| 85 | }); | |
| 86 | } catch (err) { | |
| 87 | console.error("[memory] store failed:", err); | |
| 88 | } | |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 92 | * Recall memory entries by category or keyword search. | |
| 93 | */ | |
| 94 | export async function memoryRecall( | |
| 95 | query: string, | |
| 96 | opts: { | |
| 97 | category?: string; | |
| 98 | limit?: number; | |
| 99 | minConfidence?: number; | |
| 100 | } = {} | |
| 101 | ): Promise<Array<{ pattern: string; category: string; confidence: number }>> { | |
| 102 | const limit = opts.limit ?? 10; | |
| 103 | const minConfidence = opts.minConfidence ?? 30; | |
| 104 | ||
| 105 | try { | |
| 106 | const conditions = [ | |
| 107 | eq(reviewPatterns.active, true), | |
| 108 | gte(reviewPatterns.confidence, minConfidence), | |
| 109 | ]; | |
| 110 | ||
| 111 | if (opts.category) { | |
| 112 | conditions.push(eq(reviewPatterns.category, opts.category)); | |
| 113 | } | |
| 114 | ||
| 115 | if (query && query !== "*") { | |
| 116 | conditions.push(ilike(reviewPatterns.pattern, `%${query}%`)); | |
| 117 | } | |
| 118 | ||
| 119 | const results = await db | |
| 120 | .select({ | |
| 121 | pattern: reviewPatterns.pattern, | |
| 122 | category: reviewPatterns.category, | |
| 123 | confidence: reviewPatterns.confidence, | |
| 124 | }) | |
| 125 | .from(reviewPatterns) | |
| 126 | .where(and(...conditions)) | |
| 127 | .orderBy(desc(reviewPatterns.confidence)) | |
| 128 | .limit(limit); | |
| 129 | ||
| 130 | return results; | |
| 131 | } catch (err) { | |
| 132 | console.error("[memory] recall failed:", err); | |
| 133 | return []; | |
| 134 | } | |
| 135 | } | |
| 136 | ||
| 137 | /** | |
| 138 | * Get a specific memory by category:key from cache, falling back to DB. | |
| 139 | */ | |
| 140 | export async function memoryGet( | |
| 141 | category: string, | |
| 142 | key: string | |
| 143 | ): Promise<string | null> { | |
| 144 | const cacheKey = `${category}:${key}`; | |
| 145 | const cached = memoryCache.get(cacheKey); | |
| 146 | if (cached && cached.expiresAt > Date.now()) { | |
| 147 | return cached.value; | |
| 148 | } | |
| 149 | ||
| 150 | try { | |
| 151 | const [row] = await db | |
| 152 | .select() | |
| 153 | .from(reviewPatterns) | |
| 154 | .where( | |
| 155 | and( | |
| 156 | eq(reviewPatterns.category, category), | |
| 157 | ilike(reviewPatterns.pattern, `%${key}%`), | |
| 158 | eq(reviewPatterns.active, true) | |
| 159 | ) | |
| 160 | ) | |
| 161 | .orderBy(desc(reviewPatterns.confidence)) | |
| 162 | .limit(1); | |
| 163 | ||
| 164 | if (row) { | |
| 165 | memoryCache.set(cacheKey, { | |
| 166 | value: row.pattern, | |
| 167 | expiresAt: Date.now() + CACHE_TTL, | |
| 168 | }); | |
| 169 | return row.pattern; | |
| 170 | } | |
| 171 | return null; | |
| 172 | } catch { | |
| 173 | return null; | |
| 174 | } | |
| 175 | } | |
| 176 | ||
| 177 | /** | |
| 178 | * Get a summary of all stored memories by category. | |
| 179 | */ | |
| 180 | export async function memorySummary(): Promise< | |
| 181 | Array<{ category: string; count: number; avgConfidence: number }> | |
| 182 | > { | |
| 183 | try { | |
| 184 | const results = await db | |
| 185 | .select({ | |
| 186 | category: reviewPatterns.category, | |
| 187 | count: sql<number>`count(*)::int`, | |
| 188 | avgConfidence: sql<number>`avg(${reviewPatterns.confidence})::int`, | |
| 189 | }) | |
| 190 | .from(reviewPatterns) | |
| 191 | .where(eq(reviewPatterns.active, true)) | |
| 192 | .groupBy(reviewPatterns.category); | |
| 193 | ||
| 194 | return results; | |
| 195 | } catch { | |
| 196 | return []; | |
| 197 | } | |
| 198 | } |