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.
Files changed (38) hide show
  1. package/.claude/skills/skills-manifest.json +1 -1
  2. package/CHANGELOG.md +27 -0
  3. package/dist/cli/bundle.js +1187 -508
  4. package/dist/cli/commands/prove.d.ts +60 -0
  5. package/dist/cli/commands/prove.js +167 -0
  6. package/dist/cli/index.js +2 -0
  7. package/dist/domains/test-generation/pattern-injection/edge-case-injector.d.ts +6 -0
  8. package/dist/domains/test-generation/pattern-injection/edge-case-injector.js +30 -0
  9. package/dist/kernel/unified-memory-schemas.d.ts +2 -2
  10. package/dist/kernel/unified-memory-schemas.js +26 -1
  11. package/dist/kernel/unified-memory.js +32 -0
  12. package/dist/learning/aqe-learning-engine.js +2 -1
  13. package/dist/learning/daily-log.d.ts +43 -0
  14. package/dist/learning/daily-log.js +91 -0
  15. package/dist/learning/experience-capture.d.ts +42 -0
  16. package/dist/learning/experience-capture.js +94 -4
  17. package/dist/learning/index.d.ts +4 -0
  18. package/dist/learning/index.js +8 -0
  19. package/dist/learning/opd-remediation.d.ts +55 -0
  20. package/dist/learning/opd-remediation.js +130 -0
  21. package/dist/learning/pattern-lifecycle.d.ts +12 -1
  22. package/dist/learning/pattern-lifecycle.js +18 -2
  23. package/dist/learning/pattern-store.d.ts +12 -4
  24. package/dist/learning/pattern-store.js +178 -19
  25. package/dist/learning/qe-hooks.d.ts +1 -0
  26. package/dist/learning/qe-hooks.js +30 -0
  27. package/dist/learning/qe-patterns.d.ts +6 -0
  28. package/dist/learning/qe-patterns.js +10 -1
  29. package/dist/learning/sqlite-persistence.d.ts +40 -0
  30. package/dist/learning/sqlite-persistence.js +228 -1
  31. package/dist/mcp/bundle.js +647 -20
  32. package/dist/mcp/handlers/core-handlers.d.ts +5 -0
  33. package/dist/mcp/handlers/core-handlers.js +11 -0
  34. package/dist/mcp/index.d.ts +1 -0
  35. package/dist/mcp/index.js +2 -0
  36. package/dist/mcp/tool-scoping.d.ts +36 -0
  37. package/dist/mcp/tool-scoping.js +129 -0
  38. package/package.json +1 -1
@@ -16,6 +16,7 @@ import { v4 as uuidv4 } from 'uuid';
16
16
  import { safeJsonParse } from '../shared/safe-json.js';
17
17
  import { toErrorMessage } from '../shared/error-utils.js';
18
18
  import { getUnifiedMemory } from '../kernel/unified-memory.js';
19
+ import { computeBatchEmbeddings, getEmbeddingDimension } from './real-embeddings.js';
19
20
  export const DEFAULT_SQLITE_CONFIG = {
20
21
  // LEGACY: Ignored when useUnified=true (the default). All data goes to memory.db
21
22
  dbPath: '.agentic-qe/memory.db',
@@ -25,6 +26,32 @@ export const DEFAULT_SQLITE_CONFIG = {
25
26
  foreignKeys: true,
26
27
  useUnified: true, // ADR-046: Use unified storage (memory.db) by default
27
28
  };
29
+ /**
30
+ * Hash-based embedding generation (no ONNX dependency).
31
+ * Deterministic: same text always produces the same embedding.
32
+ * FALLBACK ONLY — real embeddings use all-MiniLM-L6-v2 via computeBatchEmbeddings().
33
+ * Kept for environments where ONNX/@xenova/transformers is unavailable.
34
+ */
35
+ export function hashEmbedding(text, dimension = 384) {
36
+ const embedding = new Array(dimension).fill(0);
37
+ const normalized = text.toLowerCase().trim();
38
+ // Multiple hash passes for better distribution
39
+ for (let pass = 0; pass < 3; pass++) {
40
+ for (let i = 0; i < normalized.length; i++) {
41
+ const charCode = normalized.charCodeAt(i);
42
+ const idx = (charCode * (i + 1) * (pass + 1)) % dimension;
43
+ embedding[idx] += Math.sin(charCode * (pass + 1)) / (i + 1);
44
+ }
45
+ }
46
+ // Normalize to unit vector
47
+ const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
48
+ if (magnitude > 0) {
49
+ for (let i = 0; i < dimension; i++) {
50
+ embedding[i] /= magnitude;
51
+ }
52
+ }
53
+ return embedding;
54
+ }
28
55
  /**
29
56
  * SQLite-based pattern persistence
30
57
  */
@@ -160,6 +187,31 @@ export class SQLitePatternStore {
160
187
  CREATE UNIQUE INDEX IF NOT EXISTS idx_patterns_unique_name_domain_type
161
188
  ON qe_patterns(name, qe_domain, pattern_type);
162
189
 
190
+ -- FTS5 full-text search index for hybrid vector/text search
191
+ CREATE VIRTUAL TABLE IF NOT EXISTS qe_patterns_fts USING fts5(
192
+ name, description, pattern_type, qe_domain,
193
+ content='qe_patterns',
194
+ content_rowid='rowid'
195
+ );
196
+
197
+ -- FTS5 triggers to keep index in sync
198
+ CREATE TRIGGER IF NOT EXISTS qe_patterns_fts_insert AFTER INSERT ON qe_patterns BEGIN
199
+ INSERT INTO qe_patterns_fts(rowid, name, description, pattern_type, qe_domain)
200
+ VALUES (new.rowid, new.name, new.description, new.pattern_type, new.qe_domain);
201
+ END;
202
+
203
+ CREATE TRIGGER IF NOT EXISTS qe_patterns_fts_delete AFTER DELETE ON qe_patterns BEGIN
204
+ INSERT INTO qe_patterns_fts(qe_patterns_fts, rowid, name, description, pattern_type, qe_domain)
205
+ VALUES ('delete', old.rowid, old.name, old.description, old.pattern_type, old.qe_domain);
206
+ END;
207
+
208
+ CREATE TRIGGER IF NOT EXISTS qe_patterns_fts_update AFTER UPDATE ON qe_patterns BEGIN
209
+ INSERT INTO qe_patterns_fts(qe_patterns_fts, rowid, name, description, pattern_type, qe_domain)
210
+ VALUES ('delete', old.rowid, old.name, old.description, old.pattern_type, old.qe_domain);
211
+ INSERT INTO qe_patterns_fts(rowid, name, description, pattern_type, qe_domain)
212
+ VALUES (new.rowid, new.name, new.description, new.pattern_type, new.qe_domain);
213
+ END;
214
+
163
215
  -- Indexes for performance
164
216
  CREATE INDEX IF NOT EXISTS idx_patterns_domain ON qe_patterns(qe_domain);
165
217
  CREATE INDEX IF NOT EXISTS idx_patterns_type ON qe_patterns(pattern_type);
@@ -228,11 +280,26 @@ export class SQLitePatternStore {
228
280
  prepareStatements() {
229
281
  if (!this.db)
230
282
  throw new Error('Database not initialized');
283
+ // Two insert statements to handle both conflict types:
284
+ // 1. Primary key conflict (same id) — update metadata only, preserve stats
285
+ // 2. Unique index conflict (same name+domain+type, different id) — update metadata only
231
286
  this.prepared.set('insertPattern', this.db.prepare(`
232
- INSERT OR REPLACE INTO qe_patterns (
287
+ INSERT INTO qe_patterns (
233
288
  id, pattern_type, qe_domain, domain, name, description,
234
289
  confidence, tier, template_json, context_json
235
290
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
291
+ ON CONFLICT(id) DO UPDATE SET
292
+ confidence = excluded.confidence,
293
+ tier = excluded.tier,
294
+ template_json = excluded.template_json,
295
+ context_json = excluded.context_json,
296
+ updated_at = datetime('now')
297
+ ON CONFLICT(name, qe_domain, pattern_type) DO UPDATE SET
298
+ confidence = excluded.confidence,
299
+ tier = excluded.tier,
300
+ template_json = excluded.template_json,
301
+ context_json = excluded.context_json,
302
+ updated_at = datetime('now')
236
303
  `));
237
304
  this.prepared.set('insertEmbedding', this.db.prepare(`
238
305
  INSERT OR REPLACE INTO qe_pattern_embeddings (pattern_id, embedding, dimension, model)
@@ -338,6 +405,67 @@ export class SQLitePatternStore {
338
405
  }
339
406
  return rows.map(row => this.rowToPattern(row));
340
407
  }
408
+ /**
409
+ * FTS5 full-text search for patterns.
410
+ * Returns pattern IDs with BM25 relevance scores.
411
+ */
412
+ searchFTS(query, limit = 20) {
413
+ if (!this.db)
414
+ throw new Error('Database not initialized');
415
+ if (!query.trim())
416
+ return [];
417
+ // Sanitize: wrap in double quotes to force literal phrase match,
418
+ // escaping any internal double quotes to prevent FTS5 syntax injection
419
+ const sanitized = '"' + query.replace(/"/g, '""') + '"';
420
+ try {
421
+ const rows = this.db.prepare(`
422
+ SELECT p.id, rank AS fts_score
423
+ FROM qe_patterns_fts fts
424
+ JOIN qe_patterns p ON p.rowid = fts.rowid
425
+ WHERE qe_patterns_fts MATCH ?
426
+ ORDER BY rank
427
+ LIMIT ?
428
+ `).all(sanitized, limit);
429
+ // FTS5 rank is negative (lower = better), normalize to 0..1
430
+ // Use Math.max(maxScore, 1.0) to prevent single-result inflation:
431
+ // without this, a single FTS5 result always normalizes to 1.0
432
+ const maxAbsScore = Math.max(...rows.map(r => Math.abs(r.fts_score)), 1.0);
433
+ return rows.map(r => ({
434
+ id: r.id,
435
+ ftsScore: Math.abs(r.fts_score) / maxAbsScore,
436
+ }));
437
+ }
438
+ catch {
439
+ // FTS5 table may not exist yet (unified DB migrated before schema update)
440
+ return [];
441
+ }
442
+ }
443
+ /**
444
+ * Ghost pattern check: find patterns in SQLite that have no embeddings.
445
+ * Used by aqe_health to detect data integrity issues.
446
+ */
447
+ getGhostPatternCount() {
448
+ if (!this.db)
449
+ throw new Error('Database not initialized');
450
+ const total = this.db.prepare(`SELECT COUNT(*) as c FROM qe_patterns WHERE id NOT LIKE 'bench-%'`).get().c;
451
+ const ghostCount = this.db.prepare(`
452
+ SELECT COUNT(*) as c FROM qe_patterns p
453
+ WHERE p.id NOT LIKE 'bench-%'
454
+ AND p.id NOT IN (SELECT pattern_id FROM qe_pattern_embeddings)
455
+ `).get().c;
456
+ // Sample up to 10 ghost IDs for diagnostics
457
+ const sampleGhosts = this.db.prepare(`
458
+ SELECT p.id FROM qe_patterns p
459
+ WHERE p.id NOT LIKE 'bench-%'
460
+ AND p.id NOT IN (SELECT pattern_id FROM qe_pattern_embeddings)
461
+ LIMIT 10
462
+ `).all();
463
+ return {
464
+ total,
465
+ withoutEmbeddings: ghostCount,
466
+ sampleGhostIds: sampleGhosts.map(g => g.id),
467
+ };
468
+ }
341
469
  /**
342
470
  * Get all embeddings for HNSW indexing
343
471
  */
@@ -671,6 +799,105 @@ export class SQLitePatternStore {
671
799
  };
672
800
  }
673
801
  }
802
+ /**
803
+ * Backfill embeddings for patterns that don't have them.
804
+ * Uses real all-MiniLM-L6-v2 transformer embeddings via @xenova/transformers.
805
+ * Falls back to hash-based embeddings if ONNX is unavailable.
806
+ * Skips bench/test patterns (id LIKE 'bench-%').
807
+ *
808
+ * @param batchSize - Patterns per inference batch (default: 32, matches model batch size)
809
+ * @returns Stats about the backfill operation
810
+ */
811
+ async backfillEmbeddings(batchSize = 32) {
812
+ if (!this.db)
813
+ throw new Error('Database not initialized');
814
+ const dimension = getEmbeddingDimension(); // 384
815
+ // Find patterns without embeddings (skip bench patterns)
816
+ const patternsWithout = this.db.prepare(`
817
+ SELECT p.id, p.name, p.description, p.pattern_type, p.qe_domain
818
+ FROM qe_patterns p
819
+ WHERE p.id NOT IN (SELECT pattern_id FROM qe_pattern_embeddings)
820
+ AND p.id NOT LIKE 'bench-%'
821
+ ORDER BY p.quality_score DESC
822
+ `).all();
823
+ const alreadyHad = this.db.prepare(`
824
+ SELECT COUNT(*) as c FROM qe_pattern_embeddings
825
+ `).get();
826
+ if (patternsWithout.length === 0) {
827
+ console.log(`[SQLitePatternStore] Backfill: all patterns already have embeddings (${alreadyHad.c} total)`);
828
+ return { processed: 0, skipped: 0, errors: 0, alreadyHad: alreadyHad.c, method: 'transformer' };
829
+ }
830
+ console.log(`[SQLitePatternStore] Backfill: ${patternsWithout.length} patterns need embeddings (${alreadyHad.c} already have)`);
831
+ const insertEmbedding = this.prepared.get('insertEmbedding');
832
+ if (!insertEmbedding) {
833
+ throw new Error('Prepared statements not ready');
834
+ }
835
+ let processed = 0;
836
+ let skipped = 0;
837
+ let errors = 0;
838
+ let method = 'transformer';
839
+ // Process in batches: compute embeddings async, then insert in sync transaction
840
+ for (let i = 0; i < patternsWithout.length; i += batchSize) {
841
+ const batch = patternsWithout.slice(i, i + batchSize);
842
+ // Build text for each pattern in the batch
843
+ const textsWithIds = [];
844
+ for (const pattern of batch) {
845
+ const text = [
846
+ pattern.name,
847
+ pattern.description,
848
+ pattern.pattern_type,
849
+ pattern.qe_domain,
850
+ ].filter(Boolean).join(' ');
851
+ if (!text.trim()) {
852
+ skipped++;
853
+ continue;
854
+ }
855
+ textsWithIds.push({ id: pattern.id, text });
856
+ }
857
+ if (textsWithIds.length === 0)
858
+ continue;
859
+ // Compute real embeddings via all-MiniLM-L6-v2 (async ONNX inference)
860
+ let embeddings;
861
+ try {
862
+ embeddings = await computeBatchEmbeddings(textsWithIds.map(t => t.text));
863
+ }
864
+ catch (e) {
865
+ // Transformer unavailable — fall back to hash embeddings for remaining patterns
866
+ console.warn(`[SQLitePatternStore] Transformer unavailable, falling back to hash embeddings: ${toErrorMessage(e)}`);
867
+ method = 'hash-fallback';
868
+ embeddings = textsWithIds.map(t => hashEmbedding(t.text, dimension));
869
+ }
870
+ // Insert computed embeddings in a sync transaction
871
+ const embeddingTag = method === 'transformer' ? 'transformer-backfill' : 'hash-backfill';
872
+ const insertBatch = this.db.transaction(() => {
873
+ for (let j = 0; j < textsWithIds.length; j++) {
874
+ try {
875
+ const embedding = embeddings[j];
876
+ if (!embedding || embedding.length !== dimension) {
877
+ errors++;
878
+ continue;
879
+ }
880
+ const buffer = Buffer.from(new Float32Array(embedding).buffer);
881
+ insertEmbedding.run(textsWithIds[j].id, buffer, dimension, embeddingTag);
882
+ processed++;
883
+ }
884
+ catch (e) {
885
+ errors++;
886
+ if (errors <= 3) {
887
+ console.warn(`[SQLitePatternStore] Backfill error for ${textsWithIds[j].id}:`, toErrorMessage(e));
888
+ }
889
+ }
890
+ }
891
+ });
892
+ insertBatch();
893
+ const progress = Math.min(i + batchSize, patternsWithout.length);
894
+ if (progress % 100 === 0 || progress >= patternsWithout.length) {
895
+ console.log(`[SQLitePatternStore] Backfill progress: ${progress}/${patternsWithout.length}`);
896
+ }
897
+ }
898
+ console.log(`[SQLitePatternStore] Backfill complete (${method}): ${processed} processed, ${skipped} skipped, ${errors} errors`);
899
+ return { processed, skipped, errors, alreadyHad: alreadyHad.c, method };
900
+ }
674
901
  /**
675
902
  * Close the database
676
903
  */