Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
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.

memory.tsBlame198 lines · 1 contributor
d0bc8c2Claude1/**
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
16import { eq, and, desc, gte, ilike, sql } from "drizzle-orm";
17import { db } from "../db";
18import { reviewPatterns } from "../db/schema";
19
20interface 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)
30const memoryCache = new Map<string, { value: string; expiresAt: number }>();
31const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
32
33/**
34 * Store a memory entry. Upserts by key.
35 */
36export 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 */
94export 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 */
140export 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 */
180export 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}