@cleocode/core 2026.4.36 → 2026.4.38
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.
- package/dist/hooks/handlers/task-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/task-hooks.js +11 -0
- package/dist/hooks/handlers/task-hooks.js.map +1 -1
- package/dist/index.js +647 -34
- package/dist/index.js.map +4 -4
- package/dist/internal.d.ts +3 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +3 -1
- package/dist/internal.js.map +1 -1
- package/dist/memory/decisions.d.ts.map +1 -1
- package/dist/memory/decisions.js +18 -0
- package/dist/memory/decisions.js.map +1 -1
- package/dist/memory/engine-compat.d.ts +17 -0
- package/dist/memory/engine-compat.d.ts.map +1 -1
- package/dist/memory/engine-compat.js +36 -0
- package/dist/memory/engine-compat.js.map +1 -1
- package/dist/memory/graph-memory-bridge.d.ts +158 -0
- package/dist/memory/graph-memory-bridge.d.ts.map +1 -0
- package/dist/memory/graph-memory-bridge.js +519 -0
- package/dist/memory/graph-memory-bridge.js.map +1 -0
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +2 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/learnings.d.ts.map +1 -1
- package/dist/memory/learnings.js +18 -0
- package/dist/memory/learnings.js.map +1 -1
- package/dist/memory/llm-extraction.d.ts.map +1 -1
- package/dist/memory/llm-extraction.js.map +1 -1
- package/dist/memory/patterns.d.ts.map +1 -1
- package/dist/memory/patterns.js +18 -0
- package/dist/memory/patterns.js.map +1 -1
- package/dist/memory/quality-feedback.d.ts +129 -0
- package/dist/memory/quality-feedback.d.ts.map +1 -0
- package/dist/memory/quality-feedback.js +449 -0
- package/dist/memory/quality-feedback.js.map +1 -0
- package/dist/memory/sleep-consolidation.d.ts +98 -0
- package/dist/memory/sleep-consolidation.d.ts.map +1 -0
- package/dist/memory/sleep-consolidation.js +706 -0
- package/dist/memory/sleep-consolidation.js.map +1 -0
- package/dist/memory/temporal-supersession.d.ts +155 -0
- package/dist/memory/temporal-supersession.d.ts.map +1 -0
- package/dist/memory/temporal-supersession.js +406 -0
- package/dist/memory/temporal-supersession.js.map +1 -0
- package/package.json +6 -6
- package/src/hooks/handlers/task-hooks.ts +11 -0
- package/src/internal.ts +12 -0
- package/src/memory/__tests__/graph-memory-bridge.test.ts +357 -0
- package/src/memory/__tests__/llm-extraction.test.ts +17 -0
- package/src/memory/__tests__/quality-feedback.test.ts +418 -0
- package/src/memory/__tests__/sleep-consolidation.test.ts +790 -0
- package/src/memory/__tests__/temporal-supersession.test.ts +534 -0
- package/src/memory/decisions.ts +24 -0
- package/src/memory/engine-compat.ts +37 -0
- package/src/memory/graph-memory-bridge.ts +751 -0
- package/src/memory/index.ts +2 -0
- package/src/memory/learnings.ts +24 -0
- package/src/memory/patterns.ts +24 -0
- package/src/memory/quality-feedback.ts +640 -0
- package/src/memory/sleep-consolidation.ts +932 -0
- package/src/memory/temporal-supersession.ts +568 -0
- package/src/store/__tests__/performance-safety.test.ts +4 -4
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for memory quality feedback loop (quality-feedback.ts).
|
|
3
|
+
*
|
|
4
|
+
* Covers: trackMemoryUsage, correlateOutcomes, getMemoryQualityReport.
|
|
5
|
+
*
|
|
6
|
+
* @task T555
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { mkdir, mkdtemp, rm } from 'node:fs/promises';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
13
|
+
|
|
14
|
+
let tempDir: string;
|
|
15
|
+
let cleoDir: string;
|
|
16
|
+
|
|
17
|
+
describe('Memory Quality Feedback', () => {
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
tempDir = await mkdtemp(join(tmpdir(), 'cleo-quality-feedback-'));
|
|
20
|
+
cleoDir = join(tempDir, '.cleo');
|
|
21
|
+
await mkdir(cleoDir, { recursive: true });
|
|
22
|
+
process.env['CLEO_DIR'] = cleoDir;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
// Close DB handles so temp dir can be removed on Windows
|
|
27
|
+
try {
|
|
28
|
+
const { closeBrainDb } = await import('../../store/brain-sqlite.js');
|
|
29
|
+
closeBrainDb();
|
|
30
|
+
} catch {
|
|
31
|
+
/* may not be loaded */
|
|
32
|
+
}
|
|
33
|
+
delete process.env['CLEO_DIR'];
|
|
34
|
+
await Promise.race([
|
|
35
|
+
rm(tempDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 300 }).catch(() => {}),
|
|
36
|
+
new Promise<void>((resolve) => setTimeout(resolve, 8_000)),
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// ==========================================================================
|
|
41
|
+
// trackMemoryUsage
|
|
42
|
+
// ==========================================================================
|
|
43
|
+
|
|
44
|
+
describe('trackMemoryUsage', () => {
|
|
45
|
+
it('should insert a usage log row without throwing', async () => {
|
|
46
|
+
const { trackMemoryUsage } = await import('../quality-feedback.js');
|
|
47
|
+
const { getBrainDb, getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
48
|
+
|
|
49
|
+
// Initialise brain DB
|
|
50
|
+
await getBrainDb(tempDir);
|
|
51
|
+
|
|
52
|
+
await expect(
|
|
53
|
+
trackMemoryUsage(tempDir, 'O-test001', true, 'T100', 'success'),
|
|
54
|
+
).resolves.toBeUndefined();
|
|
55
|
+
|
|
56
|
+
const nativeDb = getBrainNativeDb();
|
|
57
|
+
expect(nativeDb).toBeTruthy();
|
|
58
|
+
|
|
59
|
+
const rows = nativeDb!
|
|
60
|
+
.prepare('SELECT * FROM brain_usage_log WHERE entry_id = ?')
|
|
61
|
+
.all('O-test001') as Array<{ entry_id: string; used: number; outcome: string }>;
|
|
62
|
+
|
|
63
|
+
expect(rows).toHaveLength(1);
|
|
64
|
+
expect(rows[0].used).toBe(1);
|
|
65
|
+
expect(rows[0].outcome).toBe('success');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should handle used=false and default outcome', async () => {
|
|
69
|
+
const { trackMemoryUsage } = await import('../quality-feedback.js');
|
|
70
|
+
const { getBrainDb, getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
71
|
+
|
|
72
|
+
await getBrainDb(tempDir);
|
|
73
|
+
await trackMemoryUsage(tempDir, 'O-test002', false);
|
|
74
|
+
|
|
75
|
+
const nativeDb = getBrainNativeDb();
|
|
76
|
+
const rows = nativeDb!
|
|
77
|
+
.prepare('SELECT * FROM brain_usage_log WHERE entry_id = ?')
|
|
78
|
+
.all('O-test002') as Array<{ used: number; outcome: string }>;
|
|
79
|
+
|
|
80
|
+
expect(rows[0].used).toBe(0);
|
|
81
|
+
expect(rows[0].outcome).toBe('unknown');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should silently ignore empty memoryId', async () => {
|
|
85
|
+
const { trackMemoryUsage } = await import('../quality-feedback.js');
|
|
86
|
+
const { getBrainDb } = await import('../../store/brain-sqlite.js');
|
|
87
|
+
await getBrainDb(tempDir);
|
|
88
|
+
await expect(trackMemoryUsage(tempDir, '', true)).resolves.toBeUndefined();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// ==========================================================================
|
|
93
|
+
// correlateOutcomes
|
|
94
|
+
// ==========================================================================
|
|
95
|
+
|
|
96
|
+
describe('correlateOutcomes', () => {
|
|
97
|
+
it('should return zero counts when DB is empty', async () => {
|
|
98
|
+
const { correlateOutcomes } = await import('../quality-feedback.js');
|
|
99
|
+
const { getBrainDb } = await import('../../store/brain-sqlite.js');
|
|
100
|
+
await getBrainDb(tempDir);
|
|
101
|
+
|
|
102
|
+
const result = await correlateOutcomes(tempDir);
|
|
103
|
+
expect(result.boosted).toBe(0);
|
|
104
|
+
expect(result.penalized).toBe(0);
|
|
105
|
+
expect(result.flaggedForPruning).toBe(0);
|
|
106
|
+
expect(result.ranAt).toBeTruthy();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should boost quality score for entries used in successful tasks', async () => {
|
|
110
|
+
const { trackMemoryUsage, correlateOutcomes } = await import('../quality-feedback.js');
|
|
111
|
+
const { getBrainDb, getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
112
|
+
const { getBrainAccessor } = await import('../../store/brain-accessor.js');
|
|
113
|
+
|
|
114
|
+
await getBrainDb(tempDir);
|
|
115
|
+
const accessor = await getBrainAccessor(tempDir);
|
|
116
|
+
|
|
117
|
+
// Insert an observation with known quality score
|
|
118
|
+
await accessor.addObservation({
|
|
119
|
+
id: 'O-boost001',
|
|
120
|
+
type: 'discovery',
|
|
121
|
+
title: 'Test boost observation',
|
|
122
|
+
narrative: 'Some observation text for boost test',
|
|
123
|
+
contentHash: 'aabb001',
|
|
124
|
+
project: null,
|
|
125
|
+
sourceSessionId: null,
|
|
126
|
+
sourceType: 'agent',
|
|
127
|
+
agent: null,
|
|
128
|
+
qualityScore: 0.6,
|
|
129
|
+
createdAt: new Date().toISOString().replace('T', ' ').slice(0, 19),
|
|
130
|
+
memoryTier: 'short',
|
|
131
|
+
memoryType: 'episodic',
|
|
132
|
+
sourceConfidence: 'agent',
|
|
133
|
+
verified: false,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Record usage with success outcome
|
|
137
|
+
await trackMemoryUsage(tempDir, 'O-boost001', true, 'T200', 'success');
|
|
138
|
+
|
|
139
|
+
const result = await correlateOutcomes(tempDir);
|
|
140
|
+
expect(result.boosted).toBeGreaterThanOrEqual(1);
|
|
141
|
+
|
|
142
|
+
// Quality score should have increased
|
|
143
|
+
const nativeDb = getBrainNativeDb();
|
|
144
|
+
const row = nativeDb!
|
|
145
|
+
.prepare('SELECT quality_score FROM brain_observations WHERE id = ?')
|
|
146
|
+
.get('O-boost001') as { quality_score: number };
|
|
147
|
+
|
|
148
|
+
expect(row.quality_score).toBeCloseTo(0.65, 2);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should penalise quality score for entries used in failed tasks', async () => {
|
|
152
|
+
const { trackMemoryUsage, correlateOutcomes } = await import('../quality-feedback.js');
|
|
153
|
+
const { getBrainDb, getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
154
|
+
const { getBrainAccessor } = await import('../../store/brain-accessor.js');
|
|
155
|
+
|
|
156
|
+
await getBrainDb(tempDir);
|
|
157
|
+
const accessor = await getBrainAccessor(tempDir);
|
|
158
|
+
|
|
159
|
+
await accessor.addObservation({
|
|
160
|
+
id: 'O-penalise001',
|
|
161
|
+
type: 'discovery',
|
|
162
|
+
title: 'Test penalty observation',
|
|
163
|
+
narrative: 'Some observation for penalty test',
|
|
164
|
+
contentHash: 'aabb002',
|
|
165
|
+
project: null,
|
|
166
|
+
sourceSessionId: null,
|
|
167
|
+
sourceType: 'agent',
|
|
168
|
+
agent: null,
|
|
169
|
+
qualityScore: 0.6,
|
|
170
|
+
createdAt: new Date().toISOString().replace('T', ' ').slice(0, 19),
|
|
171
|
+
memoryTier: 'short',
|
|
172
|
+
memoryType: 'episodic',
|
|
173
|
+
sourceConfidence: 'agent',
|
|
174
|
+
verified: false,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await trackMemoryUsage(tempDir, 'O-penalise001', true, 'T201', 'failure');
|
|
178
|
+
|
|
179
|
+
const result = await correlateOutcomes(tempDir);
|
|
180
|
+
expect(result.penalized).toBeGreaterThanOrEqual(1);
|
|
181
|
+
|
|
182
|
+
const nativeDb = getBrainNativeDb();
|
|
183
|
+
const row = nativeDb!
|
|
184
|
+
.prepare('SELECT quality_score FROM brain_observations WHERE id = ?')
|
|
185
|
+
.get('O-penalise001') as { quality_score: number };
|
|
186
|
+
|
|
187
|
+
expect(row.quality_score).toBeCloseTo(0.55, 2);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should clamp quality score between 0.0 and 1.0', async () => {
|
|
191
|
+
const { trackMemoryUsage, correlateOutcomes } = await import('../quality-feedback.js');
|
|
192
|
+
const { getBrainDb, getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
193
|
+
const { getBrainAccessor } = await import('../../store/brain-accessor.js');
|
|
194
|
+
|
|
195
|
+
await getBrainDb(tempDir);
|
|
196
|
+
const accessor = await getBrainAccessor(tempDir);
|
|
197
|
+
|
|
198
|
+
await accessor.addObservation({
|
|
199
|
+
id: 'O-clamp001',
|
|
200
|
+
type: 'discovery',
|
|
201
|
+
title: 'Clamp test near 1.0',
|
|
202
|
+
narrative: 'High quality score clamp test',
|
|
203
|
+
contentHash: 'aabb003',
|
|
204
|
+
project: null,
|
|
205
|
+
sourceSessionId: null,
|
|
206
|
+
sourceType: 'agent',
|
|
207
|
+
agent: null,
|
|
208
|
+
qualityScore: 0.99,
|
|
209
|
+
createdAt: new Date().toISOString().replace('T', ' ').slice(0, 19),
|
|
210
|
+
memoryTier: 'short',
|
|
211
|
+
memoryType: 'episodic',
|
|
212
|
+
sourceConfidence: 'agent',
|
|
213
|
+
verified: false,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
await trackMemoryUsage(tempDir, 'O-clamp001', true, 'T202', 'success');
|
|
217
|
+
|
|
218
|
+
await correlateOutcomes(tempDir);
|
|
219
|
+
|
|
220
|
+
const nativeDb = getBrainNativeDb();
|
|
221
|
+
const row = nativeDb!
|
|
222
|
+
.prepare('SELECT quality_score FROM brain_observations WHERE id = ?')
|
|
223
|
+
.get('O-clamp001') as { quality_score: number };
|
|
224
|
+
|
|
225
|
+
expect(row.quality_score).toBeLessThanOrEqual(1.0);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should flag old zero-citation entries as prune_candidates', async () => {
|
|
229
|
+
const { correlateOutcomes } = await import('../quality-feedback.js');
|
|
230
|
+
const { getBrainDb, getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
231
|
+
const { getBrainAccessor } = await import('../../store/brain-accessor.js');
|
|
232
|
+
|
|
233
|
+
await getBrainDb(tempDir);
|
|
234
|
+
const nativeDb = getBrainNativeDb();
|
|
235
|
+
const accessor = await getBrainAccessor(tempDir);
|
|
236
|
+
|
|
237
|
+
// Insert observation with a very old date (35 days ago)
|
|
238
|
+
const oldDate = new Date(Date.now() - 35 * 24 * 60 * 60 * 1000)
|
|
239
|
+
.toISOString()
|
|
240
|
+
.replace('T', ' ')
|
|
241
|
+
.slice(0, 19);
|
|
242
|
+
|
|
243
|
+
await accessor.addObservation({
|
|
244
|
+
id: 'O-stale001',
|
|
245
|
+
type: 'discovery',
|
|
246
|
+
title: 'Stale observation for pruning',
|
|
247
|
+
narrative: 'This observation should be flagged for pruning',
|
|
248
|
+
contentHash: 'aabb004',
|
|
249
|
+
project: null,
|
|
250
|
+
sourceSessionId: null,
|
|
251
|
+
sourceType: 'agent',
|
|
252
|
+
agent: null,
|
|
253
|
+
qualityScore: 0.4,
|
|
254
|
+
createdAt: oldDate,
|
|
255
|
+
memoryTier: 'short',
|
|
256
|
+
memoryType: 'episodic',
|
|
257
|
+
sourceConfidence: 'agent',
|
|
258
|
+
verified: false,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Force created_at to old value (addObservation may overwrite with NOW)
|
|
262
|
+
nativeDb!
|
|
263
|
+
.prepare('UPDATE brain_observations SET created_at = ?, citation_count = 0 WHERE id = ?')
|
|
264
|
+
.run(oldDate, 'O-stale001');
|
|
265
|
+
|
|
266
|
+
const result = await correlateOutcomes(tempDir);
|
|
267
|
+
expect(result.flaggedForPruning).toBeGreaterThanOrEqual(1);
|
|
268
|
+
|
|
269
|
+
const row = nativeDb!
|
|
270
|
+
.prepare('SELECT prune_candidate FROM brain_observations WHERE id = ?')
|
|
271
|
+
.get('O-stale001') as { prune_candidate: number };
|
|
272
|
+
expect(row.prune_candidate).toBe(1);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// ==========================================================================
|
|
277
|
+
// getMemoryQualityReport
|
|
278
|
+
// ==========================================================================
|
|
279
|
+
|
|
280
|
+
describe('getMemoryQualityReport', () => {
|
|
281
|
+
it('should return zeroed report when DB is empty', async () => {
|
|
282
|
+
const { getMemoryQualityReport } = await import('../quality-feedback.js');
|
|
283
|
+
const { getBrainDb } = await import('../../store/brain-sqlite.js');
|
|
284
|
+
await getBrainDb(tempDir);
|
|
285
|
+
|
|
286
|
+
const report = await getMemoryQualityReport(tempDir);
|
|
287
|
+
|
|
288
|
+
expect(report.totalRetrievals).toBe(0);
|
|
289
|
+
expect(report.uniqueEntriesRetrieved).toBe(0);
|
|
290
|
+
expect(report.usageRate).toBe(0);
|
|
291
|
+
expect(report.topRetrieved).toHaveLength(0);
|
|
292
|
+
expect(report.neverRetrieved).toHaveLength(0);
|
|
293
|
+
expect(report.noiseRatio).toBe(0);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should compute quality distribution from existing entries', async () => {
|
|
297
|
+
const { getMemoryQualityReport } = await import('../quality-feedback.js');
|
|
298
|
+
const { getBrainDb } = await import('../../store/brain-sqlite.js');
|
|
299
|
+
const { getBrainAccessor } = await import('../../store/brain-accessor.js');
|
|
300
|
+
|
|
301
|
+
await getBrainDb(tempDir);
|
|
302
|
+
const accessor = await getBrainAccessor(tempDir);
|
|
303
|
+
const now = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
304
|
+
|
|
305
|
+
// Insert one entry per quality bucket
|
|
306
|
+
await accessor.addObservation({
|
|
307
|
+
id: 'O-low001',
|
|
308
|
+
type: 'discovery',
|
|
309
|
+
title: 'Low quality entry',
|
|
310
|
+
narrative: 'Low',
|
|
311
|
+
contentHash: 'qq001',
|
|
312
|
+
project: null,
|
|
313
|
+
sourceSessionId: null,
|
|
314
|
+
sourceType: 'agent',
|
|
315
|
+
agent: null,
|
|
316
|
+
qualityScore: 0.2,
|
|
317
|
+
createdAt: now,
|
|
318
|
+
memoryTier: 'short',
|
|
319
|
+
memoryType: 'episodic',
|
|
320
|
+
sourceConfidence: 'agent',
|
|
321
|
+
verified: false,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
await accessor.addObservation({
|
|
325
|
+
id: 'O-med001',
|
|
326
|
+
type: 'discovery',
|
|
327
|
+
title: 'Medium quality entry',
|
|
328
|
+
narrative: 'Medium',
|
|
329
|
+
contentHash: 'qq002',
|
|
330
|
+
project: null,
|
|
331
|
+
sourceSessionId: null,
|
|
332
|
+
sourceType: 'agent',
|
|
333
|
+
agent: null,
|
|
334
|
+
qualityScore: 0.5,
|
|
335
|
+
createdAt: now,
|
|
336
|
+
memoryTier: 'medium',
|
|
337
|
+
memoryType: 'semantic',
|
|
338
|
+
sourceConfidence: 'agent',
|
|
339
|
+
verified: false,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
await accessor.addObservation({
|
|
343
|
+
id: 'O-high001',
|
|
344
|
+
type: 'discovery',
|
|
345
|
+
title: 'High quality entry',
|
|
346
|
+
narrative: 'High',
|
|
347
|
+
contentHash: 'qq003',
|
|
348
|
+
project: null,
|
|
349
|
+
sourceSessionId: null,
|
|
350
|
+
sourceType: 'agent',
|
|
351
|
+
agent: null,
|
|
352
|
+
qualityScore: 0.9,
|
|
353
|
+
createdAt: now,
|
|
354
|
+
memoryTier: 'long',
|
|
355
|
+
memoryType: 'procedural',
|
|
356
|
+
sourceConfidence: 'owner',
|
|
357
|
+
verified: true,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const report = await getMemoryQualityReport(tempDir);
|
|
361
|
+
|
|
362
|
+
expect(report.qualityDistribution.low).toBeGreaterThanOrEqual(1);
|
|
363
|
+
expect(report.qualityDistribution.medium).toBeGreaterThanOrEqual(1);
|
|
364
|
+
expect(report.qualityDistribution.high).toBeGreaterThanOrEqual(1);
|
|
365
|
+
expect(report.noiseRatio).toBeGreaterThan(0);
|
|
366
|
+
expect(report.noiseRatio).toBeLessThan(1);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('should list entries with zero citation_count in neverRetrieved', async () => {
|
|
370
|
+
const { getMemoryQualityReport } = await import('../quality-feedback.js');
|
|
371
|
+
const { getBrainDb } = await import('../../store/brain-sqlite.js');
|
|
372
|
+
const { getBrainAccessor } = await import('../../store/brain-accessor.js');
|
|
373
|
+
|
|
374
|
+
await getBrainDb(tempDir);
|
|
375
|
+
const accessor = await getBrainAccessor(tempDir);
|
|
376
|
+
const now = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
377
|
+
|
|
378
|
+
await accessor.addObservation({
|
|
379
|
+
id: 'O-zero001',
|
|
380
|
+
type: 'discovery',
|
|
381
|
+
title: 'Never retrieved entry',
|
|
382
|
+
narrative: 'This was never retrieved',
|
|
383
|
+
contentHash: 'qq010',
|
|
384
|
+
project: null,
|
|
385
|
+
sourceSessionId: null,
|
|
386
|
+
sourceType: 'agent',
|
|
387
|
+
agent: null,
|
|
388
|
+
qualityScore: 0.4,
|
|
389
|
+
createdAt: now,
|
|
390
|
+
memoryTier: 'short',
|
|
391
|
+
memoryType: 'episodic',
|
|
392
|
+
sourceConfidence: 'agent',
|
|
393
|
+
verified: false,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const report = await getMemoryQualityReport(tempDir);
|
|
397
|
+
const found = report.neverRetrieved.find((e) => e.id === 'O-zero001');
|
|
398
|
+
expect(found).toBeDefined();
|
|
399
|
+
expect(found?.qualityScore).toBeCloseTo(0.4, 2);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('should compute usageRate from brain_usage_log', async () => {
|
|
403
|
+
const { trackMemoryUsage, getMemoryQualityReport } = await import('../quality-feedback.js');
|
|
404
|
+
const { getBrainDb } = await import('../../store/brain-sqlite.js');
|
|
405
|
+
|
|
406
|
+
await getBrainDb(tempDir);
|
|
407
|
+
|
|
408
|
+
// 3 used, 1 not used → rate = 0.75
|
|
409
|
+
await trackMemoryUsage(tempDir, 'O-r001', true);
|
|
410
|
+
await trackMemoryUsage(tempDir, 'O-r002', true);
|
|
411
|
+
await trackMemoryUsage(tempDir, 'O-r003', true);
|
|
412
|
+
await trackMemoryUsage(tempDir, 'O-r004', false);
|
|
413
|
+
|
|
414
|
+
const report = await getMemoryQualityReport(tempDir);
|
|
415
|
+
expect(report.usageRate).toBeCloseTo(0.75, 2);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
});
|