scai 0.1.45 → 0.1.46
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/commands/AskCmd.js +13 -6
- package/dist/db/fileIndex.js +19 -10
- package/package.json +3 -2
package/dist/commands/AskCmd.js
CHANGED
|
@@ -8,7 +8,8 @@ import { buildContextualPrompt } from '../utils/buildContextualPrompt.js';
|
|
|
8
8
|
import { generateFileTree } from '../utils/fileTree.js';
|
|
9
9
|
import { log } from '../utils/log.js';
|
|
10
10
|
import { PROMPT_LOG_PATH, SCAI_HOME, INDEX_DIR } from '../constants.js';
|
|
11
|
-
const MAX_RELATED_FILES =
|
|
11
|
+
const MAX_RELATED_FILES = 3;
|
|
12
|
+
const MAX_SUMMARY_LINES = 12;
|
|
12
13
|
export async function runAskCommand(query) {
|
|
13
14
|
if (!query) {
|
|
14
15
|
query = await promptOnce('🧠 Ask your question:\n> ');
|
|
@@ -37,7 +38,6 @@ export async function runAskCommand(query) {
|
|
|
37
38
|
// 🟩 STEP 2: Merge results (de-duplicate by full resolved path)
|
|
38
39
|
const seen = new Set();
|
|
39
40
|
const combinedResults = [];
|
|
40
|
-
// Merging results now ensures all have `id`
|
|
41
41
|
for (const file of semanticResults) {
|
|
42
42
|
const resolved = path.resolve(file.path);
|
|
43
43
|
seen.add(resolved);
|
|
@@ -48,7 +48,7 @@ export async function runAskCommand(query) {
|
|
|
48
48
|
if (!seen.has(resolved)) {
|
|
49
49
|
seen.add(resolved);
|
|
50
50
|
combinedResults.push({
|
|
51
|
-
id: file.id,
|
|
51
|
+
id: file.id,
|
|
52
52
|
path: file.path,
|
|
53
53
|
summary: file.summary || '',
|
|
54
54
|
score: 0.0,
|
|
@@ -73,9 +73,12 @@ export async function runAskCommand(query) {
|
|
|
73
73
|
let code = '';
|
|
74
74
|
let topSummary = topFile.summary || '(No summary available)';
|
|
75
75
|
let topFunctions = [];
|
|
76
|
-
//
|
|
76
|
+
// Truncate summary if needed
|
|
77
|
+
if (topSummary) {
|
|
78
|
+
topSummary = topSummary.split('\n').slice(0, MAX_SUMMARY_LINES).join('\n');
|
|
79
|
+
}
|
|
77
80
|
const allFileIds = combinedResults
|
|
78
|
-
.map(file => file.id)
|
|
81
|
+
.map(file => file.id)
|
|
79
82
|
.filter((id) => typeof id === 'number');
|
|
80
83
|
const allFunctionsMap = getFunctionsForFiles(allFileIds);
|
|
81
84
|
try {
|
|
@@ -89,9 +92,13 @@ export async function runAskCommand(query) {
|
|
|
89
92
|
// 🟩 STEP 5: Build relatedFiles with functions
|
|
90
93
|
const relatedFiles = combinedResults.slice(0, MAX_RELATED_FILES).map(file => {
|
|
91
94
|
const fileId = file.id;
|
|
95
|
+
let summary = file.summary || '(No summary available)';
|
|
96
|
+
if (summary) {
|
|
97
|
+
summary = summary.split('\n').slice(0, MAX_SUMMARY_LINES).join('\n');
|
|
98
|
+
}
|
|
92
99
|
return {
|
|
93
100
|
path: file.path,
|
|
94
|
-
summary
|
|
101
|
+
summary,
|
|
95
102
|
functions: allFunctionsMap[fileId]?.map(fn => fn.name) || [],
|
|
96
103
|
};
|
|
97
104
|
});
|
package/dist/db/fileIndex.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { generateEmbedding } from '../lib/generateEmbedding.js';
|
|
5
5
|
import { sanitizeQueryForFts } from '../utils/sanitizeQuery.js';
|
|
6
6
|
import * as sqlTemplates from './sqlTemplates.js';
|
|
7
|
+
import { stringSimilarity } from 'string-similarity-js';
|
|
7
8
|
/**
|
|
8
9
|
* 📄 Index a single file into the database.
|
|
9
10
|
*
|
|
@@ -67,7 +68,6 @@ export async function searchFiles(query, topK = 5) {
|
|
|
67
68
|
}
|
|
68
69
|
const safeQuery = sanitizeQueryForFts(query);
|
|
69
70
|
console.log(`Executing search query in FTS5: ${safeQuery}`);
|
|
70
|
-
// Step 1: Narrow candidate set using fast keyword match
|
|
71
71
|
const ftsResults = db.prepare(`
|
|
72
72
|
SELECT fts.rowid AS id, f.path, f.summary, f.type, bm25(files_fts) AS bm25Score, f.embedding
|
|
73
73
|
FROM files f
|
|
@@ -80,30 +80,39 @@ export async function searchFiles(query, topK = 5) {
|
|
|
80
80
|
if (ftsResults.length === 0) {
|
|
81
81
|
return [];
|
|
82
82
|
}
|
|
83
|
-
// Step 2: Compute score based on embedding similarity + BM25 score
|
|
84
83
|
const bm25Min = Math.min(...ftsResults.map(r => r.bm25Score));
|
|
85
84
|
const bm25Max = Math.max(...ftsResults.map(r => r.bm25Score));
|
|
86
85
|
const scored = ftsResults.map(result => {
|
|
87
|
-
let finalScore = 0;
|
|
88
86
|
let sim = 0;
|
|
87
|
+
let finalScore = 0;
|
|
88
|
+
const normalizedBm25 = 1 - ((result.bm25Score - bm25Min) / (bm25Max - bm25Min + 1e-5));
|
|
89
89
|
if (result.embedding) {
|
|
90
90
|
try {
|
|
91
91
|
const vector = JSON.parse(result.embedding);
|
|
92
92
|
sim = cosineSimilarity(embedding, vector);
|
|
93
|
-
const normalizedBm25 = 1 - ((result.bm25Score - bm25Min) / (bm25Max - bm25Min + 1e-5));
|
|
94
|
-
finalScore = 0.7 * sim + 0.3 * normalizedBm25;
|
|
95
93
|
}
|
|
96
94
|
catch (err) {
|
|
97
95
|
console.error(`❌ Failed to parse embedding for ${result.path}:`, err);
|
|
98
|
-
finalScore = 0;
|
|
99
96
|
}
|
|
100
97
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
const terms = query.toLowerCase().split(/\s+/);
|
|
99
|
+
const path = result.path.toLowerCase();
|
|
100
|
+
const summary = (result.summary || '').toLowerCase();
|
|
101
|
+
let termMatches = 0;
|
|
102
|
+
for (const term of terms) {
|
|
103
|
+
if (path.includes(term) || summary.includes(term)) {
|
|
104
|
+
termMatches += 1;
|
|
105
|
+
}
|
|
104
106
|
}
|
|
107
|
+
const matchRatio = termMatches / terms.length;
|
|
108
|
+
const termBoost = matchRatio >= 1 ? 1.0 : matchRatio >= 0.5 ? 0.5 : 0;
|
|
109
|
+
// 🧠 Final score with hybrid weighting (BM25 + Embedding + Term Boost)
|
|
110
|
+
finalScore = 0.4 * normalizedBm25 + 0.4 * sim + 0.2 * termBoost;
|
|
111
|
+
// ✅ Fuzzy score using string-similarity-js
|
|
112
|
+
const fuzzyScore = stringSimilarity(query.toLowerCase(), `${path} ${summary}`);
|
|
113
|
+
finalScore += fuzzyScore * 10;
|
|
105
114
|
return {
|
|
106
|
-
id: result.id,
|
|
115
|
+
id: result.id,
|
|
107
116
|
path: result.path,
|
|
108
117
|
summary: result.summary,
|
|
109
118
|
score: finalScore,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.46",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"scai": "./dist/index.js"
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"better-sqlite3": "^12.1.1",
|
|
31
31
|
"commander": "^11.0.0",
|
|
32
32
|
"fast-glob": "^3.3.3",
|
|
33
|
-
"proper-lockfile": "^4.1.2"
|
|
33
|
+
"proper-lockfile": "^4.1.2",
|
|
34
|
+
"string-similarity-js": "^2.1.4"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/better-sqlite3": "^7.6.13",
|