codecritique 1.2.1 → 1.2.3
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/package.json +1 -1
- package/src/content-retrieval.js +20 -16
- package/src/custom-documents.js +19 -19
- package/src/custom-documents.test.js +11 -9
- package/src/feedback-loader.js +31 -31
- package/src/index.js +99 -119
- package/src/llm.js +4 -3
- package/src/project-analyzer.js +32 -39
- package/src/project-analyzer.test.js +3 -5
- package/src/prompt-cache.js +1 -0
- package/src/rag-analyzer.js +199 -178
- package/src/rag-analyzer.test.js +57 -0
- package/src/rag-review.js +105 -74
- package/src/rag-review.test.js +115 -3
- package/src/zero-shot-classifier-open.js +3 -2
package/package.json
CHANGED
package/src/content-retrieval.js
CHANGED
|
@@ -23,7 +23,7 @@ import { calculateCosineSimilarity, calculatePathSimilarity } from './embeddings
|
|
|
23
23
|
import { inferContextFromDocumentContent } from './utils/context-inference.js';
|
|
24
24
|
import { isGenericDocument, getGenericDocumentContext } from './utils/document-detection.js';
|
|
25
25
|
import { isDocumentationFile } from './utils/file-validation.js';
|
|
26
|
-
import { debug } from './utils/logging.js';
|
|
26
|
+
import { debug, verboseLog } from './utils/logging.js';
|
|
27
27
|
|
|
28
28
|
const FILE_EMBEDDINGS_TABLE = TABLE_NAMES.FILE_EMBEDDINGS;
|
|
29
29
|
const DOCUMENT_CHUNK_TABLE = TABLE_NAMES.DOCUMENT_CHUNK;
|
|
@@ -79,7 +79,8 @@ export class ContentRetriever {
|
|
|
79
79
|
return [];
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
verboseLog(
|
|
83
|
+
options,
|
|
83
84
|
chalk.cyan(`Native hybrid documentation search - limit: ${limit}, threshold: ${similarityThreshold}, reranking: ${useReranking}`)
|
|
84
85
|
);
|
|
85
86
|
|
|
@@ -91,7 +92,7 @@ export class ContentRetriever {
|
|
|
91
92
|
return [];
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
verboseLog(options, chalk.cyan('Performing native hybrid search for documentation...'));
|
|
95
96
|
let query = table.search(queryText).nearestToText(queryText);
|
|
96
97
|
|
|
97
98
|
const resolvedProjectPath = path.resolve(projectPath);
|
|
@@ -106,7 +107,7 @@ export class ContentRetriever {
|
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
const results = await query.limit(Math.max(limit * 3, 20)).toArray();
|
|
109
|
-
|
|
110
|
+
verboseLog(options, chalk.green(`Native hybrid search returned ${results.length} documentation results`));
|
|
110
111
|
|
|
111
112
|
// OPTIMIZATION: Enhanced batch file existence checks with parallel processing
|
|
112
113
|
const docsToCheck = [];
|
|
@@ -168,7 +169,7 @@ export class ContentRetriever {
|
|
|
168
169
|
// Filter results based on project match using the map
|
|
169
170
|
const projectFilteredResults = results.filter((result, index) => docProjectMatchMap.get(index) === true);
|
|
170
171
|
|
|
171
|
-
|
|
172
|
+
verboseLog(options, chalk.blue(`Filtered to ${projectFilteredResults.length} documentation results from current project`));
|
|
172
173
|
let finalResults = projectFilteredResults.map((result) => {
|
|
173
174
|
let similarity;
|
|
174
175
|
if (result._distance !== undefined) {
|
|
@@ -197,7 +198,7 @@ export class ContentRetriever {
|
|
|
197
198
|
|
|
198
199
|
let queryEmbedding = null;
|
|
199
200
|
if (useReranking && queryContextForReranking && finalResults.length >= 3) {
|
|
200
|
-
|
|
201
|
+
verboseLog(options, chalk.cyan('Applying sophisticated contextual reranking to documentation...'));
|
|
201
202
|
const WEIGHT_INITIAL_SIM = 0.3;
|
|
202
203
|
const WEIGHT_H1_CHUNK_RERANK = 0.15;
|
|
203
204
|
const HEAVY_BOOST_SAME_AREA = 0.4;
|
|
@@ -416,7 +417,7 @@ export class ContentRetriever {
|
|
|
416
417
|
finalResults = finalResults.slice(0, limit);
|
|
417
418
|
}
|
|
418
419
|
|
|
419
|
-
|
|
420
|
+
verboseLog(options, chalk.green(`Returning ${finalResults.length} documentation results`));
|
|
420
421
|
|
|
421
422
|
return finalResults;
|
|
422
423
|
} catch (error) {
|
|
@@ -443,7 +444,10 @@ export class ContentRetriever {
|
|
|
443
444
|
precomputedQueryEmbedding = null,
|
|
444
445
|
} = options;
|
|
445
446
|
|
|
446
|
-
|
|
447
|
+
verboseLog(
|
|
448
|
+
options,
|
|
449
|
+
chalk.cyan(`Native hybrid code search - limit: ${limit}, threshold: ${similarityThreshold}, isTestFile: ${isTestFile}`)
|
|
450
|
+
);
|
|
447
451
|
|
|
448
452
|
try {
|
|
449
453
|
if (!queryText?.trim()) {
|
|
@@ -460,7 +464,7 @@ export class ContentRetriever {
|
|
|
460
464
|
}
|
|
461
465
|
|
|
462
466
|
// Native hybrid search with automatic vector + FTS + RRF
|
|
463
|
-
|
|
467
|
+
verboseLog(options, chalk.cyan('Performing native hybrid search for code...'));
|
|
464
468
|
let query = table.search(queryText).nearestToText(queryText);
|
|
465
469
|
|
|
466
470
|
// Add filtering conditions
|
|
@@ -472,13 +476,13 @@ export class ContentRetriever {
|
|
|
472
476
|
if (isTestFile) {
|
|
473
477
|
// Only include test files
|
|
474
478
|
conditions.push(`(path LIKE '%.test.%' OR path LIKE '%.spec.%' OR path LIKE '%_test.py' OR path LIKE 'test_%.py')`);
|
|
475
|
-
|
|
479
|
+
verboseLog(options, chalk.blue(`Filtering to include only test files.`));
|
|
476
480
|
} else {
|
|
477
481
|
// Exclude test files
|
|
478
482
|
conditions.push(
|
|
479
483
|
`(path NOT LIKE '%.test.%' AND path NOT LIKE '%.spec.%' AND path NOT LIKE '%_test.py' AND path NOT LIKE 'test_%.py')`
|
|
480
484
|
);
|
|
481
|
-
|
|
485
|
+
verboseLog(options, chalk.blue(`Filtering to exclude test files.`));
|
|
482
486
|
}
|
|
483
487
|
}
|
|
484
488
|
|
|
@@ -526,7 +530,7 @@ export class ContentRetriever {
|
|
|
526
530
|
|
|
527
531
|
const results = await query.limit(Math.max(limit * 3, 20)).toArray();
|
|
528
532
|
|
|
529
|
-
|
|
533
|
+
verboseLog(options, chalk.green(`Native hybrid search returned ${results.length} results`));
|
|
530
534
|
|
|
531
535
|
// OPTIMIZATION: Batch file existence checks for better performance
|
|
532
536
|
const resultsToCheck = [];
|
|
@@ -595,7 +599,7 @@ export class ContentRetriever {
|
|
|
595
599
|
// Filter results based on project match using the map
|
|
596
600
|
const projectFilteredResults = results.filter((result, index) => projectMatchMap.get(index) === true);
|
|
597
601
|
|
|
598
|
-
|
|
602
|
+
verboseLog(options, chalk.blue(`Filtered to ${projectFilteredResults.length} results from current project`));
|
|
599
603
|
|
|
600
604
|
// Map results to expected format
|
|
601
605
|
let finalResults = projectFilteredResults.map((result) => {
|
|
@@ -683,7 +687,7 @@ export class ContentRetriever {
|
|
|
683
687
|
finalResults = finalResults.slice(0, limit);
|
|
684
688
|
}
|
|
685
689
|
|
|
686
|
-
|
|
690
|
+
verboseLog(options, chalk.green(`Returning ${finalResults.length} optimized hybrid search results`));
|
|
687
691
|
return finalResults;
|
|
688
692
|
} catch (error) {
|
|
689
693
|
console.error(chalk.red(`Error in optimized findSimilarCode: ${error.message}`), error);
|
|
@@ -712,7 +716,7 @@ export class ContentRetriever {
|
|
|
712
716
|
this.h1EmbeddingCache.clear();
|
|
713
717
|
this.documentContextCache.clear();
|
|
714
718
|
this.documentContextPromiseCache.clear();
|
|
715
|
-
|
|
719
|
+
verboseLog({}, chalk.green('ContentRetriever caches cleared'));
|
|
716
720
|
}
|
|
717
721
|
|
|
718
722
|
/**
|
|
@@ -739,7 +743,7 @@ export class ContentRetriever {
|
|
|
739
743
|
parallelRerankingTime: 0,
|
|
740
744
|
};
|
|
741
745
|
|
|
742
|
-
|
|
746
|
+
verboseLog({}, chalk.green('ContentRetriever cleanup complete'));
|
|
743
747
|
} finally {
|
|
744
748
|
this.cleaningUp = false;
|
|
745
749
|
}
|
package/src/custom-documents.js
CHANGED
|
@@ -18,7 +18,7 @@ import { CacheManager } from './embeddings/cache-manager.js';
|
|
|
18
18
|
import { EmbeddingError, ValidationError } from './embeddings/errors.js';
|
|
19
19
|
import { ModelManager } from './embeddings/model-manager.js';
|
|
20
20
|
import { calculateCosineSimilarity, calculatePathSimilarity } from './embeddings/similarity-calculator.js';
|
|
21
|
-
import { debug } from './utils/logging.js';
|
|
21
|
+
import { debug, verboseLog } from './utils/logging.js';
|
|
22
22
|
import { slugify } from './utils/string-utils.js';
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -140,7 +140,7 @@ export class CustomDocumentProcessor {
|
|
|
140
140
|
this.performanceMetrics.averageChunkSize = chunks.reduce((sum, chunk) => sum + chunk.content.length, 0) / chunks.length;
|
|
141
141
|
this.performanceMetrics.processingTime += Date.now() - startTime;
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
verboseLog({}, chalk.gray(` Chunked document "${documentTitle}" into ${chunks.length} chunks`));
|
|
144
144
|
return chunks;
|
|
145
145
|
} catch (error) {
|
|
146
146
|
console.error(chalk.red(`Error chunking document: ${error.message}`));
|
|
@@ -159,18 +159,18 @@ export class CustomDocumentProcessor {
|
|
|
159
159
|
|
|
160
160
|
try {
|
|
161
161
|
if (!customDocs || customDocs.length === 0) {
|
|
162
|
-
|
|
162
|
+
verboseLog({}, chalk.gray('No custom documents to process'));
|
|
163
163
|
return [];
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
verboseLog({}, chalk.cyan(`Processing ${customDocs.length} custom documents into chunks...`));
|
|
167
167
|
|
|
168
168
|
const allChunks = [];
|
|
169
169
|
let totalBatchAttempts = 0;
|
|
170
170
|
let successfulBatches = 0;
|
|
171
171
|
|
|
172
172
|
for (const doc of customDocs) {
|
|
173
|
-
|
|
173
|
+
verboseLog({}, chalk.gray(` Processing document: ${doc.title}`));
|
|
174
174
|
|
|
175
175
|
// Chunk the document
|
|
176
176
|
const chunks = this.chunkDocument(doc);
|
|
@@ -205,12 +205,12 @@ export class CustomDocumentProcessor {
|
|
|
205
205
|
const validChunks = chunksWithEmbeddings.filter((chunk) => chunk !== null);
|
|
206
206
|
allChunks.push(...validChunks);
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
verboseLog({}, chalk.gray(` Generated embeddings for ${validChunks.length}/${chunks.length} chunks`));
|
|
209
209
|
this.performanceMetrics.embeddingsCalculated += validChunks.length;
|
|
210
210
|
} catch (error) {
|
|
211
211
|
console.error(chalk.red(`Error in batch embedding generation for document ${doc.title}: ${error.message}`));
|
|
212
212
|
// Fallback to individual processing for this document
|
|
213
|
-
|
|
213
|
+
verboseLog({}, chalk.yellow(` Falling back to individual processing for ${doc.title}`));
|
|
214
214
|
|
|
215
215
|
const chunksWithEmbeddings = await Promise.all(
|
|
216
216
|
chunks.map(async (chunk) => {
|
|
@@ -235,7 +235,7 @@ export class CustomDocumentProcessor {
|
|
|
235
235
|
const validChunks = chunksWithEmbeddings.filter((chunk) => chunk !== null);
|
|
236
236
|
allChunks.push(...validChunks);
|
|
237
237
|
|
|
238
|
-
|
|
238
|
+
verboseLog({}, chalk.gray(` Generated embeddings for ${validChunks.length}/${chunks.length} chunks (fallback)`));
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
|
|
@@ -252,7 +252,7 @@ export class CustomDocumentProcessor {
|
|
|
252
252
|
this.performanceMetrics.documentsProcessed += customDocs.length;
|
|
253
253
|
this.performanceMetrics.processingTime += Date.now() - startTime;
|
|
254
254
|
|
|
255
|
-
|
|
255
|
+
verboseLog({}, chalk.green(`Successfully processed ${allChunks.length} custom document chunks (${Date.now() - startTime}ms)`));
|
|
256
256
|
return allChunks;
|
|
257
257
|
} catch (error) {
|
|
258
258
|
console.error(chalk.red(`Error processing custom documents: ${error.message}`));
|
|
@@ -285,11 +285,11 @@ export class CustomDocumentProcessor {
|
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
if (!chunks || chunks.length === 0) {
|
|
288
|
-
|
|
288
|
+
verboseLog({}, chalk.gray('No custom document chunks available for search'));
|
|
289
289
|
return [];
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
|
|
292
|
+
verboseLog({}, chalk.cyan(`Searching ${chunks.length} custom document chunks...`));
|
|
293
293
|
|
|
294
294
|
// OPTIMIZATION: Use pre-computed query embedding if available
|
|
295
295
|
let queryEmbedding = precomputedQueryEmbedding;
|
|
@@ -319,7 +319,7 @@ export class CustomDocumentProcessor {
|
|
|
319
319
|
filteredResults = filteredResults.slice(0, limit);
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
|
|
322
|
+
verboseLog({}, chalk.green(`Found ${filteredResults.length} relevant custom document chunks (${Date.now() - startTime}ms)`));
|
|
323
323
|
|
|
324
324
|
// Log top results for debugging
|
|
325
325
|
if (filteredResults.length > 0) {
|
|
@@ -350,7 +350,7 @@ export class CustomDocumentProcessor {
|
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
// Try cache manager
|
|
353
|
-
const cachedChunks =
|
|
353
|
+
const cachedChunks = this.cacheManager.getCustomDocumentChunks(resolvedProjectPath);
|
|
354
354
|
if (cachedChunks && cachedChunks.length > 0) {
|
|
355
355
|
// Restore to memory
|
|
356
356
|
this.customDocumentChunks.set(resolvedProjectPath, cachedChunks);
|
|
@@ -371,7 +371,7 @@ export class CustomDocumentProcessor {
|
|
|
371
371
|
* @private
|
|
372
372
|
*/
|
|
373
373
|
async _applyParallelReranking(filteredResults, queryText, queryContextForReranking, queryFilePath, queryEmbedding) {
|
|
374
|
-
|
|
374
|
+
verboseLog({}, chalk.cyan('Applying optimized parallel contextual reranking to custom document chunks...'));
|
|
375
375
|
|
|
376
376
|
const WEIGHT_INITIAL_SIM = 0.4;
|
|
377
377
|
const WEIGHT_DOCUMENT_TITLE_MATCH = 0.2;
|
|
@@ -466,7 +466,7 @@ export class CustomDocumentProcessor {
|
|
|
466
466
|
// Wait for all reranking calculations to complete in parallel
|
|
467
467
|
await Promise.all(rerankingPromises);
|
|
468
468
|
|
|
469
|
-
|
|
469
|
+
verboseLog({}, chalk.cyan(`Parallel reranking completed for ${filteredResults.length} chunks`));
|
|
470
470
|
|
|
471
471
|
// Log debug info for first few results
|
|
472
472
|
for (let i = 0; i < Math.min(3, filteredResults.length); i++) {
|
|
@@ -520,8 +520,8 @@ export class CustomDocumentProcessor {
|
|
|
520
520
|
try {
|
|
521
521
|
const resolvedProjectPath = path.resolve(projectPath);
|
|
522
522
|
this.customDocumentChunks.delete(resolvedProjectPath);
|
|
523
|
-
|
|
524
|
-
|
|
523
|
+
this.cacheManager.customDocumentChunks.delete(resolvedProjectPath);
|
|
524
|
+
verboseLog({}, chalk.green(`Cleared custom document chunks for project: ${resolvedProjectPath}`));
|
|
525
525
|
} catch (error) {
|
|
526
526
|
console.error(chalk.red(`Error clearing project chunks: ${error.message}`));
|
|
527
527
|
}
|
|
@@ -561,7 +561,7 @@ export class CustomDocumentProcessor {
|
|
|
561
561
|
clearCaches() {
|
|
562
562
|
this.h1EmbeddingCache.clear();
|
|
563
563
|
this.customDocumentChunks.clear();
|
|
564
|
-
|
|
564
|
+
verboseLog({}, chalk.green('CustomDocumentProcessor caches cleared'));
|
|
565
565
|
}
|
|
566
566
|
|
|
567
567
|
/**
|
|
@@ -589,7 +589,7 @@ export class CustomDocumentProcessor {
|
|
|
589
589
|
processingTime: 0,
|
|
590
590
|
};
|
|
591
591
|
|
|
592
|
-
|
|
592
|
+
verboseLog({}, chalk.green('CustomDocumentProcessor cleanup complete'));
|
|
593
593
|
} finally {
|
|
594
594
|
this.cleaningUp = false;
|
|
595
595
|
}
|
|
@@ -11,8 +11,7 @@ vi.mock('./embeddings/model-manager.js', () => ({
|
|
|
11
11
|
vi.mock('./embeddings/cache-manager.js', () => ({
|
|
12
12
|
CacheManager: class {
|
|
13
13
|
storeCustomDocuments = vi.fn().mockResolvedValue(undefined);
|
|
14
|
-
|
|
15
|
-
clearCustomDocuments = vi.fn().mockResolvedValue(undefined);
|
|
14
|
+
getCustomDocumentChunks = vi.fn().mockReturnValue([]);
|
|
16
15
|
},
|
|
17
16
|
}));
|
|
18
17
|
|
|
@@ -32,8 +31,7 @@ describe('CustomDocumentProcessor', () => {
|
|
|
32
31
|
|
|
33
32
|
mockCacheManager = {
|
|
34
33
|
storeCustomDocuments: vi.fn().mockResolvedValue(undefined),
|
|
35
|
-
|
|
36
|
-
clearCustomDocuments: vi.fn().mockResolvedValue(undefined),
|
|
34
|
+
getCustomDocumentChunks: vi.fn().mockReturnValue([]),
|
|
37
35
|
};
|
|
38
36
|
|
|
39
37
|
processor = new CustomDocumentProcessor({
|
|
@@ -319,7 +317,7 @@ describe('CustomDocumentProcessor', () => {
|
|
|
319
317
|
|
|
320
318
|
it('should return chunks from cache if not in memory', async () => {
|
|
321
319
|
const cachedChunks = [{ id: 'cached', content: 'from cache' }];
|
|
322
|
-
mockCacheManager.
|
|
320
|
+
mockCacheManager.getCustomDocumentChunks.mockReturnValue(cachedChunks);
|
|
323
321
|
|
|
324
322
|
const result = await processor.getExistingChunks('/project');
|
|
325
323
|
|
|
@@ -328,7 +326,7 @@ describe('CustomDocumentProcessor', () => {
|
|
|
328
326
|
|
|
329
327
|
it('should restore cached chunks to memory', async () => {
|
|
330
328
|
const cachedChunks = [{ id: 'cached', content: 'from cache' }];
|
|
331
|
-
mockCacheManager.
|
|
329
|
+
mockCacheManager.getCustomDocumentChunks.mockReturnValue(cachedChunks);
|
|
332
330
|
|
|
333
331
|
await processor.getExistingChunks('/project');
|
|
334
332
|
|
|
@@ -336,7 +334,7 @@ describe('CustomDocumentProcessor', () => {
|
|
|
336
334
|
});
|
|
337
335
|
|
|
338
336
|
it('should return empty array when no chunks exist', async () => {
|
|
339
|
-
mockCacheManager.
|
|
337
|
+
mockCacheManager.getCustomDocumentChunks.mockReturnValue([]);
|
|
340
338
|
|
|
341
339
|
const result = await processor.getExistingChunks('/project');
|
|
342
340
|
|
|
@@ -353,10 +351,14 @@ describe('CustomDocumentProcessor', () => {
|
|
|
353
351
|
expect(processor.customDocumentChunks.has('/project')).toBe(false);
|
|
354
352
|
});
|
|
355
353
|
|
|
356
|
-
it('should clear chunks
|
|
354
|
+
it('should clear chunks for only the selected project', async () => {
|
|
355
|
+
processor.customDocumentChunks.set('/project', [{ id: 'chunk-a' }]);
|
|
356
|
+
processor.customDocumentChunks.set('/other', [{ id: 'chunk-b' }]);
|
|
357
|
+
|
|
357
358
|
await processor.clearProjectChunks('/project');
|
|
358
359
|
|
|
359
|
-
expect(
|
|
360
|
+
expect(processor.customDocumentChunks.has('/project')).toBe(false);
|
|
361
|
+
expect(processor.customDocumentChunks.has('/other')).toBe(true);
|
|
360
362
|
});
|
|
361
363
|
});
|
|
362
364
|
|
package/src/feedback-loader.js
CHANGED
|
@@ -15,39 +15,41 @@ import path from 'path';
|
|
|
15
15
|
import chalk from 'chalk';
|
|
16
16
|
import { getDefaultEmbeddingsSystem } from './embeddings/factory.js';
|
|
17
17
|
import { calculateCosineSimilarity } from './embeddings/similarity-calculator.js';
|
|
18
|
+
import { verboseLog } from './utils/logging.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Load feedback data from artifacts directory
|
|
21
22
|
*
|
|
22
23
|
* @param {string} feedbackPath - Path to feedback artifacts directory
|
|
23
24
|
* @param {Object} options - Loading options
|
|
25
|
+
* @param {boolean} [options.verbose=false] - Enable verbose progress logging
|
|
24
26
|
* @returns {Promise<Object>} Loaded feedback data
|
|
25
27
|
*/
|
|
26
28
|
export async function loadFeedbackData(feedbackPath, options = {}) {
|
|
27
29
|
const { verbose = false } = options;
|
|
28
30
|
|
|
29
31
|
if (!feedbackPath) {
|
|
30
|
-
|
|
32
|
+
verboseLog(verbose, chalk.gray('No feedback path provided'));
|
|
31
33
|
return {};
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
try {
|
|
35
37
|
if (!fs.existsSync(feedbackPath)) {
|
|
36
|
-
|
|
38
|
+
verboseLog(verbose, chalk.gray(`Feedback directory not found: ${feedbackPath}`));
|
|
37
39
|
return {};
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
verboseLog(verbose, chalk.cyan(`📁 Loading feedback from: ${feedbackPath}`));
|
|
41
43
|
|
|
42
44
|
// Look for feedback files in the directory
|
|
43
45
|
const feedbackFiles = fs.readdirSync(feedbackPath).filter((file) => file.startsWith('feedback-') && file.endsWith('.json'));
|
|
44
46
|
|
|
45
47
|
if (feedbackFiles.length === 0) {
|
|
46
|
-
|
|
48
|
+
verboseLog(verbose, chalk.gray('No feedback files found'));
|
|
47
49
|
return {};
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
verboseLog(verbose, chalk.cyan(`📥 Found ${feedbackFiles.length} feedback file(s)`));
|
|
51
53
|
|
|
52
54
|
// Load and merge all feedback files
|
|
53
55
|
const allFeedback = {};
|
|
@@ -64,25 +66,21 @@ export async function loadFeedbackData(feedbackPath, options = {}) {
|
|
|
64
66
|
Object.assign(allFeedback, feedbackData.feedback);
|
|
65
67
|
const itemCount = Object.keys(feedbackData.feedback).length;
|
|
66
68
|
totalItems += itemCount;
|
|
67
|
-
|
|
68
|
-
console.log(chalk.cyan(`📋 Loaded feedback from ${file}: ${itemCount} items`));
|
|
69
|
-
}
|
|
69
|
+
verboseLog(verbose, chalk.cyan(`📋 Loaded feedback from ${file}: ${itemCount} items`));
|
|
70
70
|
}
|
|
71
71
|
} catch (parseError) {
|
|
72
|
-
console.
|
|
72
|
+
console.warn(chalk.yellow(`⚠️ Error parsing feedback file ${file}: ${parseError.message}`));
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
if (totalItems > 0) {
|
|
77
|
-
|
|
78
|
-
console.log(chalk.green(`✅ Successfully loaded ${totalItems} feedback items total`));
|
|
79
|
-
}
|
|
77
|
+
verboseLog(verbose, chalk.green(`✅ Successfully loaded ${totalItems} feedback items total`));
|
|
80
78
|
return allFeedback;
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
return {};
|
|
84
82
|
} catch (error) {
|
|
85
|
-
console.
|
|
83
|
+
console.error(chalk.red(`❌ Error loading feedback data: ${error.message}`));
|
|
86
84
|
return {};
|
|
87
85
|
}
|
|
88
86
|
}
|
|
@@ -113,9 +111,9 @@ export async function initializeSemanticSimilarity() {
|
|
|
113
111
|
await embeddingsSystem.initialize();
|
|
114
112
|
semanticSimilarityInitialized = true;
|
|
115
113
|
semanticSimilarityAvailable = true;
|
|
116
|
-
|
|
114
|
+
verboseLog({}, chalk.green('[FeedbackLoader] Semantic similarity initialized using embeddings system'));
|
|
117
115
|
} catch (error) {
|
|
118
|
-
console.
|
|
116
|
+
console.warn(chalk.yellow(`[FeedbackLoader] Semantic similarity initialization failed: ${error.message}`));
|
|
119
117
|
semanticSimilarityAvailable = false;
|
|
120
118
|
}
|
|
121
119
|
}
|
|
@@ -157,7 +155,7 @@ async function calculateSemanticSimilarity(text1, text2) {
|
|
|
157
155
|
// Cosine similarity ranges from -1 to 1, normalize to 0-1
|
|
158
156
|
return (similarity + 1) / 2;
|
|
159
157
|
} catch (error) {
|
|
160
|
-
console.
|
|
158
|
+
console.warn(chalk.yellow(`[FeedbackLoader] Semantic similarity calculation failed: ${error.message}`));
|
|
161
159
|
return null;
|
|
162
160
|
}
|
|
163
161
|
}
|
|
@@ -173,9 +171,9 @@ async function calculateSemanticSimilarity(text1, text2) {
|
|
|
173
171
|
* @param {string} issueDescription - Description of the current issue
|
|
174
172
|
* @param {Object} feedbackData - Loaded feedback data
|
|
175
173
|
* @param {Object} options - Filtering options
|
|
176
|
-
* @param {number} options.similarityThreshold - Threshold for considering issues similar
|
|
177
|
-
* @param {boolean} options.verbose - Enable verbose logging
|
|
178
|
-
* @param {boolean} options.useSemanticSimilarity - Use semantic similarity when available
|
|
174
|
+
* @param {number} [options.similarityThreshold=0.7] - Threshold for considering issues similar
|
|
175
|
+
* @param {boolean} [options.verbose=false] - Enable verbose progress logging
|
|
176
|
+
* @param {boolean} [options.useSemanticSimilarity=true] - Use semantic similarity when available
|
|
179
177
|
* @returns {Promise<boolean>} True if issue should be skipped
|
|
180
178
|
*/
|
|
181
179
|
export async function shouldSkipSimilarIssue(issueDescription, feedbackData, options = {}) {
|
|
@@ -205,9 +203,7 @@ export async function shouldSkipSimilarIssue(issueDescription, feedbackData, opt
|
|
|
205
203
|
// Determine if we should use semantic similarity
|
|
206
204
|
const canUseSemanticSimilarity = useSemanticSimilarity && isSemanticSimilarityAvailable();
|
|
207
205
|
|
|
208
|
-
|
|
209
|
-
console.log(chalk.cyan('🔍 Using semantic similarity for issue comparison'));
|
|
210
|
-
}
|
|
206
|
+
verboseLog(verbose && canUseSemanticSimilarity, chalk.cyan('🔍 Using semantic similarity for issue comparison'));
|
|
211
207
|
|
|
212
208
|
// Check similarity with dismissed issues
|
|
213
209
|
for (const dismissed of dismissedIssues) {
|
|
@@ -233,11 +229,12 @@ export async function shouldSkipSimilarIssue(issueDescription, feedbackData, opt
|
|
|
233
229
|
}
|
|
234
230
|
|
|
235
231
|
if (similarity > similarityThreshold) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
232
|
+
verboseLog(
|
|
233
|
+
verbose,
|
|
234
|
+
chalk.yellow(`⏭️ Skipping similar dismissed issue (${(similarity * 100).toFixed(1)}% ${similarityMethod} similarity)`)
|
|
235
|
+
);
|
|
236
|
+
verboseLog(verbose, chalk.gray(` Current: ${issueDescription.substring(0, 80)}...`));
|
|
237
|
+
verboseLog(verbose, chalk.gray(` Previous: ${dismissed.originalIssue.substring(0, 80)}...`));
|
|
241
238
|
return true;
|
|
242
239
|
}
|
|
243
240
|
}
|
|
@@ -252,7 +249,7 @@ export async function shouldSkipSimilarIssue(issueDescription, feedbackData, opt
|
|
|
252
249
|
* @param {string} text1 - First text
|
|
253
250
|
* @param {string} text2 - Second text
|
|
254
251
|
* @param {Object} options - Options
|
|
255
|
-
* @param {boolean} options.useSemanticSimilarity - Use semantic similarity when available
|
|
252
|
+
* @param {boolean} [options.useSemanticSimilarity=true] - Use semantic similarity when available
|
|
256
253
|
* @returns {Promise<{similarity: number, method: string}>} Similarity result with method used
|
|
257
254
|
*/
|
|
258
255
|
export async function calculateIssueSimilarity(text1, text2, options = {}) {
|
|
@@ -327,6 +324,8 @@ export function calculateWordSimilarity(text1, text2) {
|
|
|
327
324
|
*
|
|
328
325
|
* @param {Object} feedbackData - Loaded feedback data
|
|
329
326
|
* @param {Object} options - Extraction options
|
|
327
|
+
* @param {number} [options.maxPatterns=10] - Maximum number of dismissed patterns to include
|
|
328
|
+
* @param {boolean} [options.verbose=false] - Enable verbose progress logging
|
|
330
329
|
* @returns {Array} Array of dismissed issue patterns
|
|
331
330
|
*/
|
|
332
331
|
export function extractDismissedPatterns(feedbackData, options = {}) {
|
|
@@ -355,9 +354,10 @@ export function extractDismissedPatterns(feedbackData, options = {}) {
|
|
|
355
354
|
}))
|
|
356
355
|
.slice(0, maxPatterns);
|
|
357
356
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
357
|
+
verboseLog(
|
|
358
|
+
verbose && dismissedIssues.length > 0,
|
|
359
|
+
chalk.cyan(`📋 Extracted ${dismissedIssues.length} dismissed issue patterns for LLM context`)
|
|
360
|
+
);
|
|
361
361
|
|
|
362
362
|
return dismissedIssues;
|
|
363
363
|
}
|