agentic-qe 3.7.14 → 3.7.15
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/.claude/skills/skills-manifest.json +1 -1
- package/CHANGELOG.md +27 -0
- package/dist/cli/bundle.js +1187 -508
- package/dist/cli/commands/prove.d.ts +60 -0
- package/dist/cli/commands/prove.js +167 -0
- package/dist/cli/index.js +2 -0
- package/dist/domains/test-generation/pattern-injection/edge-case-injector.d.ts +6 -0
- package/dist/domains/test-generation/pattern-injection/edge-case-injector.js +30 -0
- package/dist/kernel/unified-memory-schemas.d.ts +2 -2
- package/dist/kernel/unified-memory-schemas.js +26 -1
- package/dist/kernel/unified-memory.js +32 -0
- package/dist/learning/aqe-learning-engine.js +2 -1
- package/dist/learning/daily-log.d.ts +43 -0
- package/dist/learning/daily-log.js +91 -0
- package/dist/learning/experience-capture.d.ts +42 -0
- package/dist/learning/experience-capture.js +94 -4
- package/dist/learning/index.d.ts +4 -0
- package/dist/learning/index.js +8 -0
- package/dist/learning/opd-remediation.d.ts +55 -0
- package/dist/learning/opd-remediation.js +130 -0
- package/dist/learning/pattern-lifecycle.d.ts +12 -1
- package/dist/learning/pattern-lifecycle.js +18 -2
- package/dist/learning/pattern-store.d.ts +12 -4
- package/dist/learning/pattern-store.js +178 -19
- package/dist/learning/qe-hooks.d.ts +1 -0
- package/dist/learning/qe-hooks.js +30 -0
- package/dist/learning/qe-patterns.d.ts +6 -0
- package/dist/learning/qe-patterns.js +10 -1
- package/dist/learning/sqlite-persistence.d.ts +40 -0
- package/dist/learning/sqlite-persistence.js +228 -1
- package/dist/mcp/bundle.js +647 -20
- package/dist/mcp/handlers/core-handlers.d.ts +5 -0
- package/dist/mcp/handlers/core-handlers.js +11 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/tool-scoping.d.ts +36 -0
- package/dist/mcp/tool-scoping.js +129 -0
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { v4 as uuidv4 } from 'uuid';
|
|
9
9
|
import { ok, err } from '../shared/types/index.js';
|
|
10
10
|
import { toErrorMessage, toError } from '../shared/error-utils.js';
|
|
11
|
-
import { calculateQualityScore, shouldPromotePattern, validateQEPattern, mapQEDomainToAQE, } from './qe-patterns.js';
|
|
11
|
+
import { calculateQualityScore, shouldPromotePattern, validateQEPattern, mapQEDomainToAQE, PROMOTION_THRESHOLD, } from './qe-patterns.js';
|
|
12
12
|
/**
|
|
13
13
|
* Default pattern store configuration
|
|
14
14
|
*/
|
|
@@ -21,7 +21,7 @@ export const DEFAULT_PATTERN_STORE_CONFIG = {
|
|
|
21
21
|
efSearch: 100,
|
|
22
22
|
maxElements: 50000,
|
|
23
23
|
},
|
|
24
|
-
promotionThreshold:
|
|
24
|
+
promotionThreshold: PROMOTION_THRESHOLD,
|
|
25
25
|
minConfidence: 0.3,
|
|
26
26
|
maxPatternsPerDomain: 5000,
|
|
27
27
|
autoCleanup: true,
|
|
@@ -55,6 +55,7 @@ export class PatternStore {
|
|
|
55
55
|
cleanupTimer;
|
|
56
56
|
// Optional SQLite persistence delegate for delete/promote
|
|
57
57
|
sqliteStore = null;
|
|
58
|
+
loadingPromise = null;
|
|
58
59
|
// In-memory caches for fast access
|
|
59
60
|
patternCache = new Map();
|
|
60
61
|
domainIndex = new Map();
|
|
@@ -75,12 +76,23 @@ export class PatternStore {
|
|
|
75
76
|
this.config = { ...DEFAULT_PATTERN_STORE_CONFIG, ...config };
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
78
|
-
* Set SQLite persistence delegate
|
|
79
|
-
*
|
|
80
|
-
*
|
|
79
|
+
* Set SQLite persistence delegate and load patterns into memory.
|
|
80
|
+
*
|
|
81
|
+
* When set, PatternStore will:
|
|
82
|
+
* 1. Load existing patterns from SQLite into the in-memory cache
|
|
83
|
+
* 2. Forward create/delete/promote operations to SQLite for persistence
|
|
84
|
+
* 3. Persist embeddings alongside patterns on store()
|
|
81
85
|
*/
|
|
82
86
|
setSqliteStore(store) {
|
|
83
87
|
this.sqliteStore = store;
|
|
88
|
+
// Load patterns from SQLite if we're already initialized
|
|
89
|
+
// (setSqliteStore is called after initialize() in QEReasoningBank)
|
|
90
|
+
// Store promise so concurrent store/search calls can await it
|
|
91
|
+
if (this.initialized) {
|
|
92
|
+
this.loadingPromise = this.loadPatterns().catch((e) => console.warn('[PatternStore] Failed to load patterns after setSqliteStore:', e)).finally(() => {
|
|
93
|
+
this.loadingPromise = null;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
84
96
|
}
|
|
85
97
|
/**
|
|
86
98
|
* Initialize the pattern store
|
|
@@ -161,6 +173,47 @@ export class PatternStore {
|
|
|
161
173
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('HNSW init timeout')), timeoutMs));
|
|
162
174
|
await Promise.race([initPromise, timeoutPromise]);
|
|
163
175
|
this.hnswAvailable = this.hnswIndex.isNativeAvailable();
|
|
176
|
+
// Load existing embeddings from SQLite into HNSW index (capped to prevent timeout)
|
|
177
|
+
if (this.sqliteStore) {
|
|
178
|
+
try {
|
|
179
|
+
const embeddings = this.sqliteStore.getAllEmbeddings();
|
|
180
|
+
const maxBootstrap = this.config.hnsw.maxElements;
|
|
181
|
+
let loaded = 0;
|
|
182
|
+
for (const { patternId, embedding } of embeddings) {
|
|
183
|
+
if (loaded >= maxBootstrap)
|
|
184
|
+
break;
|
|
185
|
+
if (!embedding || embedding.length !== this.config.embeddingDimension)
|
|
186
|
+
continue;
|
|
187
|
+
const pattern = this.patternCache.get(patternId);
|
|
188
|
+
if (!pattern)
|
|
189
|
+
continue;
|
|
190
|
+
try {
|
|
191
|
+
await this.hnswIndex.insert(patternId, embedding, {
|
|
192
|
+
filePath: pattern.patternType,
|
|
193
|
+
lineCoverage: pattern.confidence * 100,
|
|
194
|
+
branchCoverage: pattern.qualityScore * 100,
|
|
195
|
+
functionCoverage: 0,
|
|
196
|
+
statementCoverage: 0,
|
|
197
|
+
uncoveredLineCount: 0,
|
|
198
|
+
uncoveredBranchCount: 0,
|
|
199
|
+
riskScore: 1 - pattern.confidence,
|
|
200
|
+
lastUpdated: Date.now(),
|
|
201
|
+
totalLines: 0,
|
|
202
|
+
});
|
|
203
|
+
loaded++;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// Duplicate or invalid — skip
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (loaded > 0) {
|
|
210
|
+
console.log(`[PatternStore] Loaded ${loaded} embeddings from SQLite into HNSW`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
console.warn('[PatternStore] Failed to load SQLite embeddings into HNSW:', toErrorMessage(error));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
164
217
|
console.log(`[PatternStore] HNSW lazy-initialized (native: ${this.hnswAvailable})`);
|
|
165
218
|
}
|
|
166
219
|
catch (error) {
|
|
@@ -170,13 +223,28 @@ export class PatternStore {
|
|
|
170
223
|
}
|
|
171
224
|
}
|
|
172
225
|
/**
|
|
173
|
-
* Load existing patterns from
|
|
226
|
+
* Load existing patterns from SQLite into in-memory cache.
|
|
227
|
+
*
|
|
228
|
+
* Previously this was a no-op after Issue #258 removed kv_store duplication,
|
|
229
|
+
* but that left 15,634 SQLite patterns invisible to search on every restart.
|
|
230
|
+
* Now properly loads from SQLitePatternStore when wired.
|
|
174
231
|
*/
|
|
175
232
|
async loadPatterns() {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
233
|
+
if (!this.sqliteStore) {
|
|
234
|
+
return; // SQLite not wired yet — will be loaded after setSqliteStore()
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const patterns = this.sqliteStore.getPatterns({ limit: 50000 });
|
|
238
|
+
for (const pattern of patterns) {
|
|
239
|
+
this.indexPattern(pattern);
|
|
240
|
+
}
|
|
241
|
+
if (patterns.length > 0) {
|
|
242
|
+
console.log(`[PatternStore] Loaded ${patterns.length} patterns from SQLite into memory cache`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
console.warn('[PatternStore] Failed to load patterns from SQLite:', toErrorMessage(error));
|
|
247
|
+
}
|
|
180
248
|
}
|
|
181
249
|
/**
|
|
182
250
|
* Index a pattern in local caches
|
|
@@ -193,8 +261,13 @@ export class PatternStore {
|
|
|
193
261
|
this.typeIndex.set(pattern.patternType, new Set());
|
|
194
262
|
}
|
|
195
263
|
this.typeIndex.get(pattern.patternType).add(pattern.id);
|
|
196
|
-
// Tier index
|
|
197
|
-
|
|
264
|
+
// Tier index (defensive: coerce unexpected tier values to 'short-term')
|
|
265
|
+
const tier = (pattern.tier === 'long-term') ? 'long-term' : 'short-term';
|
|
266
|
+
if (pattern.tier !== tier) {
|
|
267
|
+
// Pattern has invalid tier from SQLite — store corrected copy in cache
|
|
268
|
+
pattern.tier = tier;
|
|
269
|
+
}
|
|
270
|
+
this.tierIndex.get(tier).add(pattern.id);
|
|
198
271
|
}
|
|
199
272
|
/**
|
|
200
273
|
* Remove pattern from local indices
|
|
@@ -212,6 +285,9 @@ export class PatternStore {
|
|
|
212
285
|
if (!this.initialized) {
|
|
213
286
|
await this.initialize();
|
|
214
287
|
}
|
|
288
|
+
if (this.loadingPromise) {
|
|
289
|
+
await this.loadingPromise;
|
|
290
|
+
}
|
|
215
291
|
// Validate pattern
|
|
216
292
|
const validation = validateQEPattern(pattern);
|
|
217
293
|
if (!validation.valid) {
|
|
@@ -227,11 +303,17 @@ export class PatternStore {
|
|
|
227
303
|
// Run cleanup for this domain
|
|
228
304
|
await this.cleanupDomain(pattern.qeDomain);
|
|
229
305
|
}
|
|
230
|
-
//
|
|
231
|
-
// PatternStore only maintains in-memory cache + HNSW index for fast search.
|
|
232
|
-
// Previously this wrote to kv_store, causing 229MB bloat (Issue #258).
|
|
233
|
-
// Index locally
|
|
306
|
+
// Index in memory cache
|
|
234
307
|
this.indexPattern(pattern);
|
|
308
|
+
// Persist to SQLite (pattern + embedding atomically)
|
|
309
|
+
if (this.sqliteStore) {
|
|
310
|
+
try {
|
|
311
|
+
this.sqliteStore.storePattern(pattern, pattern.embedding);
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
console.warn(`[PatternStore] SQLite persist failed for ${pattern.id}:`, toErrorMessage(error));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
235
317
|
// Add to HNSW if embedding is available (lazy-load HNSW only when needed)
|
|
236
318
|
if (pattern.embedding) {
|
|
237
319
|
const hnsw = await this.ensureHNSW();
|
|
@@ -327,6 +409,9 @@ export class PatternStore {
|
|
|
327
409
|
if (!this.initialized) {
|
|
328
410
|
await this.initialize();
|
|
329
411
|
}
|
|
412
|
+
if (this.loadingPromise) {
|
|
413
|
+
await this.loadingPromise;
|
|
414
|
+
}
|
|
330
415
|
return this.patternCache.get(id) ?? null;
|
|
331
416
|
}
|
|
332
417
|
/**
|
|
@@ -336,6 +421,9 @@ export class PatternStore {
|
|
|
336
421
|
if (!this.initialized) {
|
|
337
422
|
await this.initialize();
|
|
338
423
|
}
|
|
424
|
+
if (this.loadingPromise) {
|
|
425
|
+
await this.loadingPromise;
|
|
426
|
+
}
|
|
339
427
|
const startTime = performance.now();
|
|
340
428
|
const limit = options.limit || 10;
|
|
341
429
|
const results = [];
|
|
@@ -362,10 +450,69 @@ export class PatternStore {
|
|
|
362
450
|
}
|
|
363
451
|
}
|
|
364
452
|
}
|
|
453
|
+
// FTS5 hybrid search: blend BM25 text relevance with vector similarity
|
|
454
|
+
// 75% vector score + 25% FTS5 score for patterns found by both
|
|
455
|
+
if (typeof query === 'string' && query.trim() && this.sqliteStore) {
|
|
456
|
+
try {
|
|
457
|
+
const ftsResults = this.sqliteStore.searchFTS(query, limit * 2);
|
|
458
|
+
if (ftsResults.length > 0) {
|
|
459
|
+
const ftsScoreMap = new Map(ftsResults.map(r => [r.id, r.ftsScore]));
|
|
460
|
+
const existingIds = new Set(results.map(r => r.pattern.id));
|
|
461
|
+
// Boost existing vector results that also match FTS5
|
|
462
|
+
for (const result of results) {
|
|
463
|
+
const ftsScore = ftsScoreMap.get(result.pattern.id);
|
|
464
|
+
if (ftsScore !== undefined) {
|
|
465
|
+
result.score = 0.75 * result.score + 0.25 * ftsScore;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// Add FTS5-only results not already in vector results
|
|
469
|
+
for (const ftsResult of ftsResults) {
|
|
470
|
+
if (existingIds.has(ftsResult.id))
|
|
471
|
+
continue;
|
|
472
|
+
const pattern = await this.get(ftsResult.id);
|
|
473
|
+
if (pattern && this.matchesFilters(pattern, options)) {
|
|
474
|
+
const reuseInfo = this.calculateReuseInfo(pattern, ftsResult.ftsScore);
|
|
475
|
+
results.push({
|
|
476
|
+
pattern,
|
|
477
|
+
score: 0.5 * ftsResult.ftsScore, // FTS-only: exact keyword match is valuable
|
|
478
|
+
matchType: 'exact',
|
|
479
|
+
similarity: ftsResult.ftsScore,
|
|
480
|
+
canReuse: reuseInfo.canReuse,
|
|
481
|
+
estimatedTokenSavings: reuseInfo.estimatedTokenSavings,
|
|
482
|
+
reuseConfidence: reuseInfo.reuseConfidence,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
catch {
|
|
489
|
+
// FTS5 unavailable, continue with text fallback
|
|
490
|
+
}
|
|
491
|
+
}
|
|
365
492
|
// Text search fallback or additional
|
|
366
493
|
if (typeof query === 'string' || results.length < limit) {
|
|
367
494
|
const textResults = await this.searchByText(typeof query === 'string' ? query : '', options, limit - results.length);
|
|
368
|
-
results
|
|
495
|
+
// Deduplicate: only add text results not already present
|
|
496
|
+
const existingIds = new Set(results.map(r => r.pattern.id));
|
|
497
|
+
for (const tr of textResults) {
|
|
498
|
+
if (!existingIds.has(tr.pattern.id)) {
|
|
499
|
+
results.push(tr);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// Apply temporal decay: boost recent patterns, penalize stale ones
|
|
504
|
+
// Half-life of 30 days — patterns used recently score higher
|
|
505
|
+
const TEMPORAL_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
506
|
+
const now = Date.now();
|
|
507
|
+
for (const result of results) {
|
|
508
|
+
const lastUsed = result.pattern.lastUsedAt?.getTime() ?? result.pattern.createdAt.getTime();
|
|
509
|
+
const ageMs = now - lastUsed;
|
|
510
|
+
const decayFactor = Math.pow(0.5, ageMs / TEMPORAL_HALF_LIFE_MS);
|
|
511
|
+
// Only boost patterns that have been used — new untested patterns get neutral score
|
|
512
|
+
const effectiveDecay = result.pattern.usageCount > 0 ? decayFactor : 0.5;
|
|
513
|
+
// Multiplicative decay: preserves relative ordering from search scoring
|
|
514
|
+
// while penalizing stale patterns (decayFactor is 0-1)
|
|
515
|
+
result.score = result.score * (0.7 + 0.3 * effectiveDecay);
|
|
369
516
|
}
|
|
370
517
|
// Sort by score and limit
|
|
371
518
|
results.sort((a, b) => b.score - a.score);
|
|
@@ -555,6 +702,15 @@ export class PatternStore {
|
|
|
555
702
|
qualityScore,
|
|
556
703
|
lastUsedAt: now,
|
|
557
704
|
};
|
|
705
|
+
// Persist usage to SQLite
|
|
706
|
+
if (this.sqliteStore) {
|
|
707
|
+
try {
|
|
708
|
+
this.sqliteStore.recordUsage(id, success);
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
console.warn(`[PatternStore] SQLite recordUsage failed for ${id}:`, toErrorMessage(error));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
558
714
|
// Check for promotion (ADR-052: shouldPromotePattern returns PromotionCheck object)
|
|
559
715
|
const promotionCheck = shouldPromotePattern(updated);
|
|
560
716
|
const shouldPromote = promotionCheck.meetsUsageCriteria &&
|
|
@@ -564,7 +720,7 @@ export class PatternStore {
|
|
|
564
720
|
await this.promote(id);
|
|
565
721
|
}
|
|
566
722
|
else {
|
|
567
|
-
// Update
|
|
723
|
+
// Update in-memory cache
|
|
568
724
|
this.patternCache.set(id, updated);
|
|
569
725
|
}
|
|
570
726
|
return ok(undefined);
|
|
@@ -699,7 +855,10 @@ export class PatternStore {
|
|
|
699
855
|
}
|
|
700
856
|
// Check for removal (short-term, old, low quality)
|
|
701
857
|
if (pattern.tier === 'short-term') {
|
|
702
|
-
const
|
|
858
|
+
const createdTime = pattern.createdAt instanceof Date
|
|
859
|
+
? pattern.createdAt.getTime()
|
|
860
|
+
: new Date(pattern.createdAt).getTime();
|
|
861
|
+
const ageMs = Date.now() - createdTime;
|
|
703
862
|
const isOld = ageMs > 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
704
863
|
const isLowQuality = pattern.qualityScore < 0.2;
|
|
705
864
|
const isUnused = pattern.usageCount === 0 && ageMs > 24 * 60 * 60 * 1000; // 1 day
|
|
@@ -25,6 +25,7 @@ export declare const QE_HOOK_EVENTS: {
|
|
|
25
25
|
readonly PatternLearned: "qe:pattern-learned";
|
|
26
26
|
readonly PatternApplied: "qe:pattern-applied";
|
|
27
27
|
readonly PatternPromoted: "qe:pattern-promoted";
|
|
28
|
+
readonly PreCompaction: "qe:pre-compaction";
|
|
28
29
|
};
|
|
29
30
|
export type QEHookEvent = (typeof QE_HOOK_EVENTS)[keyof typeof QE_HOOK_EVENTS];
|
|
30
31
|
/**
|
|
@@ -34,6 +34,8 @@ export const QE_HOOK_EVENTS = {
|
|
|
34
34
|
PatternLearned: 'qe:pattern-learned',
|
|
35
35
|
PatternApplied: 'qe:pattern-applied',
|
|
36
36
|
PatternPromoted: 'qe:pattern-promoted',
|
|
37
|
+
// Session lifecycle
|
|
38
|
+
PreCompaction: 'qe:pre-compaction',
|
|
37
39
|
};
|
|
38
40
|
// ============================================================================
|
|
39
41
|
// QE Hook Handlers
|
|
@@ -434,6 +436,34 @@ export function createQEHookHandlers(reasoningBank) {
|
|
|
434
436
|
data: { patternId, newTier },
|
|
435
437
|
};
|
|
436
438
|
},
|
|
439
|
+
// ========================================================================
|
|
440
|
+
// Session Lifecycle Hooks
|
|
441
|
+
// ========================================================================
|
|
442
|
+
[QE_HOOK_EVENTS.PreCompaction]: async (ctx) => {
|
|
443
|
+
const stats = { experiencesFlushed: 0, patternsPromoted: 0 };
|
|
444
|
+
// Flush pending experiences before context compaction
|
|
445
|
+
if (ctx.data?.experienceCaptureService) {
|
|
446
|
+
const service = ctx.data.experienceCaptureService;
|
|
447
|
+
const pendingCount = service.getPendingCount?.() ?? 0;
|
|
448
|
+
if (pendingCount > 0) {
|
|
449
|
+
const flushed = await service.flushPending?.();
|
|
450
|
+
stats.experiencesFlushed = flushed ?? pendingCount;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Promote eligible patterns before compaction
|
|
454
|
+
if (ctx.data?.patternLifecycleManager) {
|
|
455
|
+
const manager = ctx.data.patternLifecycleManager;
|
|
456
|
+
const promotionResult = manager.runPromotionSweep?.();
|
|
457
|
+
if (promotionResult) {
|
|
458
|
+
stats.patternsPromoted = promotionResult.promoted ?? 0;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
console.log('[QEHooks] Pre-compaction flush:', stats);
|
|
462
|
+
return {
|
|
463
|
+
success: true,
|
|
464
|
+
data: stats,
|
|
465
|
+
};
|
|
466
|
+
},
|
|
437
467
|
};
|
|
438
468
|
}
|
|
439
469
|
// ============================================================================
|
|
@@ -195,6 +195,12 @@ export declare function calculateQualityScore(pattern: {
|
|
|
195
195
|
/**
|
|
196
196
|
* Pattern promotion check result
|
|
197
197
|
*/
|
|
198
|
+
/**
|
|
199
|
+
* Shared promotion threshold: minimum successful uses before a short-term
|
|
200
|
+
* pattern can be promoted to long-term. Used by pattern-store, pattern-lifecycle,
|
|
201
|
+
* experience-capture, and shouldPromotePattern().
|
|
202
|
+
*/
|
|
203
|
+
export declare const PROMOTION_THRESHOLD = 3;
|
|
198
204
|
export interface PromotionCheck {
|
|
199
205
|
meetsUsageCriteria: boolean;
|
|
200
206
|
meetsQualityCriteria: boolean;
|
|
@@ -72,6 +72,15 @@ export function calculateQualityScore(pattern) {
|
|
|
72
72
|
const usageScore = Math.min(pattern.usageCount / 100, 1);
|
|
73
73
|
return (pattern.confidence * 0.3 + usageScore * 0.2 + pattern.successRate * 0.5);
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Pattern promotion check result
|
|
77
|
+
*/
|
|
78
|
+
/**
|
|
79
|
+
* Shared promotion threshold: minimum successful uses before a short-term
|
|
80
|
+
* pattern can be promoted to long-term. Used by pattern-store, pattern-lifecycle,
|
|
81
|
+
* experience-capture, and shouldPromotePattern().
|
|
82
|
+
*/
|
|
83
|
+
export const PROMOTION_THRESHOLD = 3;
|
|
75
84
|
/**
|
|
76
85
|
* Check if pattern should be promoted to long-term storage
|
|
77
86
|
* Requires 3+ successful uses as per ADR-021
|
|
@@ -82,7 +91,7 @@ export function calculateQualityScore(pattern) {
|
|
|
82
91
|
* @param coherenceThreshold - Threshold for coherence violation (default: 0.4)
|
|
83
92
|
*/
|
|
84
93
|
export function shouldPromotePattern(pattern, coherenceEnergy, coherenceThreshold = 0.4) {
|
|
85
|
-
const meetsUsageCriteria = pattern.tier === 'short-term' && pattern.successfulUses >=
|
|
94
|
+
const meetsUsageCriteria = pattern.tier === 'short-term' && pattern.successfulUses >= PROMOTION_THRESHOLD;
|
|
86
95
|
const meetsQualityCriteria = pattern.successRate >= 0.7 && pattern.confidence >= 0.6;
|
|
87
96
|
// NEW: Coherence criteria - only block if coherence energy is provided and exceeds threshold
|
|
88
97
|
const meetsCoherenceCriteria = coherenceEnergy === undefined || coherenceEnergy < coherenceThreshold;
|
|
@@ -36,6 +36,13 @@ export interface SQLitePersistenceConfig {
|
|
|
36
36
|
useUnified: boolean;
|
|
37
37
|
}
|
|
38
38
|
export declare const DEFAULT_SQLITE_CONFIG: SQLitePersistenceConfig;
|
|
39
|
+
/**
|
|
40
|
+
* Hash-based embedding generation (no ONNX dependency).
|
|
41
|
+
* Deterministic: same text always produces the same embedding.
|
|
42
|
+
* FALLBACK ONLY — real embeddings use all-MiniLM-L6-v2 via computeBatchEmbeddings().
|
|
43
|
+
* Kept for environments where ONNX/@xenova/transformers is unavailable.
|
|
44
|
+
*/
|
|
45
|
+
export declare function hashEmbedding(text: string, dimension?: number): number[];
|
|
39
46
|
/**
|
|
40
47
|
* SQLite-based pattern persistence
|
|
41
48
|
*/
|
|
@@ -78,6 +85,23 @@ export declare class SQLitePatternStore {
|
|
|
78
85
|
domain?: QEDomain;
|
|
79
86
|
limit?: number;
|
|
80
87
|
}): QEPattern[];
|
|
88
|
+
/**
|
|
89
|
+
* FTS5 full-text search for patterns.
|
|
90
|
+
* Returns pattern IDs with BM25 relevance scores.
|
|
91
|
+
*/
|
|
92
|
+
searchFTS(query: string, limit?: number): Array<{
|
|
93
|
+
id: string;
|
|
94
|
+
ftsScore: number;
|
|
95
|
+
}>;
|
|
96
|
+
/**
|
|
97
|
+
* Ghost pattern check: find patterns in SQLite that have no embeddings.
|
|
98
|
+
* Used by aqe_health to detect data integrity issues.
|
|
99
|
+
*/
|
|
100
|
+
getGhostPatternCount(): {
|
|
101
|
+
total: number;
|
|
102
|
+
withoutEmbeddings: number;
|
|
103
|
+
sampleGhostIds: string[];
|
|
104
|
+
};
|
|
81
105
|
/**
|
|
82
106
|
* Get all embeddings for HNSW indexing
|
|
83
107
|
*/
|
|
@@ -159,6 +183,22 @@ export declare class SQLitePatternStore {
|
|
|
159
183
|
avgSimilarity: number;
|
|
160
184
|
}>;
|
|
161
185
|
};
|
|
186
|
+
/**
|
|
187
|
+
* Backfill embeddings for patterns that don't have them.
|
|
188
|
+
* Uses real all-MiniLM-L6-v2 transformer embeddings via @xenova/transformers.
|
|
189
|
+
* Falls back to hash-based embeddings if ONNX is unavailable.
|
|
190
|
+
* Skips bench/test patterns (id LIKE 'bench-%').
|
|
191
|
+
*
|
|
192
|
+
* @param batchSize - Patterns per inference batch (default: 32, matches model batch size)
|
|
193
|
+
* @returns Stats about the backfill operation
|
|
194
|
+
*/
|
|
195
|
+
backfillEmbeddings(batchSize?: number): Promise<{
|
|
196
|
+
processed: number;
|
|
197
|
+
skipped: number;
|
|
198
|
+
errors: number;
|
|
199
|
+
alreadyHad: number;
|
|
200
|
+
method: 'transformer' | 'hash-fallback';
|
|
201
|
+
}>;
|
|
162
202
|
/**
|
|
163
203
|
* Close the database
|
|
164
204
|
*/
|