scai 0.1.26 → 0.1.27
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/{QueryCmd.js → FindCmd.js} +8 -7
- package/dist/commands/SummaryCmd.js +47 -16
- package/dist/db/fileIndex.js +1 -13
- package/dist/index.js +4 -3
- package/dist/utils/sanitizeQuery.js +8 -5
- package/dist/utils/textWrapper.js +9 -1
- package/package.json +1 -1
- package/dist/commands/EnvCmd.js +0 -10
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { queryFiles } from '../db/fileIndex.js';
|
|
2
|
+
import { sanitizeQueryForFts } from '../utils/sanitizeQuery.js';
|
|
2
3
|
import path from 'path';
|
|
3
|
-
export async function
|
|
4
|
+
export async function runFindCommand(query) {
|
|
4
5
|
if (!query) {
|
|
5
|
-
console.error('❌ Please provide a search query.\n👉 Usage: scai
|
|
6
|
+
console.error('❌ Please provide a search query.\n👉 Usage: scai find "keyword"');
|
|
6
7
|
return;
|
|
7
8
|
}
|
|
8
|
-
console.log(
|
|
9
|
-
const
|
|
9
|
+
console.log(`\n🔍 Searching for: "${query}"\n`);
|
|
10
|
+
const sanitizedQuery = sanitizeQueryForFts(query);
|
|
11
|
+
const results = queryFiles(sanitizedQuery);
|
|
10
12
|
if (results.length === 0) {
|
|
11
13
|
console.log('⚠️ No matching files found.');
|
|
12
14
|
return;
|
|
13
15
|
}
|
|
16
|
+
console.log(`✅ Found ${results.length} result(s).`);
|
|
17
|
+
console.log();
|
|
14
18
|
results.forEach((result, index) => {
|
|
15
19
|
console.log(`📄 [${index + 1}] ${path.relative(process.cwd(), result.path)}`);
|
|
16
|
-
console.log(` 📝 ${result.summary}`);
|
|
17
|
-
console.log();
|
|
18
20
|
});
|
|
19
|
-
console.log(`✅ Found ${results.length} result(s).`);
|
|
20
21
|
}
|
|
@@ -1,44 +1,75 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
2
3
|
import readline from 'readline';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
4
|
+
import { queryFiles } from '../db/fileIndex.js';
|
|
5
|
+
import { summaryModule } from '../pipeline/modules/summaryModule.js';
|
|
6
|
+
import { summarizeCode } from '../utils/summarizer.js';
|
|
5
7
|
export async function summarizeFile(filepath) {
|
|
6
8
|
let content = '';
|
|
7
|
-
|
|
9
|
+
let summary;
|
|
10
|
+
let filePathResolved = filepath ? path.resolve(process.cwd(), filepath) : undefined;
|
|
11
|
+
// Handle case where user provides only a filename (without extension)
|
|
12
|
+
if (filepath && !path.extname(filepath)) {
|
|
13
|
+
// Search for matching files using the provided base name
|
|
14
|
+
const matches = queryFiles(`"${filepath}"`);
|
|
15
|
+
if (matches.length > 0) {
|
|
16
|
+
const match = matches[0]; // Get the first match (adjust based on your preference)
|
|
17
|
+
filePathResolved = path.resolve(process.cwd(), match.path);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
else if (filepath && path.extname(filepath)) {
|
|
21
|
+
// Handle case where full filename with extension is provided
|
|
22
|
+
filePathResolved = path.resolve(process.cwd(), filepath);
|
|
23
|
+
}
|
|
24
|
+
// Now, let's search the database for a summary
|
|
25
|
+
if (filePathResolved) {
|
|
8
26
|
try {
|
|
9
|
-
|
|
27
|
+
// Try to find an existing summary from the database using the resolved path
|
|
28
|
+
const matches = queryFiles(`"${filePathResolved}"`);
|
|
29
|
+
const match = matches.find(row => path.resolve(row.path) === filePathResolved);
|
|
30
|
+
if (match?.summary) {
|
|
31
|
+
// If a summary exists in the database, use it
|
|
32
|
+
console.log(`🧠 Cached summary for ${filepath}:\n`);
|
|
33
|
+
console.log(summarizeCode(match.summary));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// If no cached summary, read the file content
|
|
37
|
+
content = await fs.readFile(filePathResolved, 'utf-8');
|
|
10
38
|
}
|
|
11
39
|
catch (err) {
|
|
12
|
-
console.error(`❌ Could not
|
|
40
|
+
console.error(`❌ Could not process ${filepath}:`, err.message);
|
|
13
41
|
return;
|
|
14
42
|
}
|
|
15
43
|
}
|
|
16
|
-
else if (process.stdin.isTTY) {
|
|
17
|
-
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
44
|
+
else if (!process.stdin.isTTY) {
|
|
45
|
+
// If no file path and input comes from stdin (piped content)
|
|
21
46
|
const rl = readline.createInterface({
|
|
22
47
|
input: process.stdin,
|
|
23
48
|
output: process.stdout,
|
|
24
49
|
terminal: false,
|
|
25
50
|
});
|
|
51
|
+
// Collect all piped input into the `content` string
|
|
26
52
|
for await (const line of rl) {
|
|
27
53
|
content += line + '\n';
|
|
28
54
|
}
|
|
29
55
|
}
|
|
56
|
+
else {
|
|
57
|
+
console.error('❌ No file provided and no piped input.\n👉 Usage: scai summ <file> or cat file | scai summ');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// If content is available (from file or stdin)
|
|
30
61
|
if (content.trim()) {
|
|
31
|
-
|
|
62
|
+
console.log('🧪 Generating summary...\n');
|
|
63
|
+
// Generate a summary using your summarization pipeline
|
|
32
64
|
const response = await summaryModule.run({ content, filepath });
|
|
33
|
-
// Pass the summary text to the utility function for formatting
|
|
34
65
|
if (!response.summary) {
|
|
35
|
-
console.warn(
|
|
66
|
+
console.warn('⚠️ No summary generated.');
|
|
36
67
|
return;
|
|
37
68
|
}
|
|
38
|
-
|
|
39
|
-
console.log(
|
|
69
|
+
// Print the formatted summary
|
|
70
|
+
console.log(summarizeCode(response.summary));
|
|
40
71
|
}
|
|
41
72
|
else {
|
|
42
|
-
console.error('❌ No
|
|
73
|
+
console.error('❌ No content provided to summarize.');
|
|
43
74
|
}
|
|
44
75
|
}
|
package/dist/db/fileIndex.js
CHANGED
|
@@ -29,15 +29,7 @@ export function indexFile(filePath, summary, type) {
|
|
|
29
29
|
/**
|
|
30
30
|
* Perform a raw keyword-based full-text search using the FTS5 index.
|
|
31
31
|
*/
|
|
32
|
-
export function queryFiles(
|
|
33
|
-
const safeQuery = query
|
|
34
|
-
.trim()
|
|
35
|
-
.split(/\s+/)
|
|
36
|
-
.map(token => {
|
|
37
|
-
token = token.replace(/[?*\\"]/g, '').replace(/'/g, "''");
|
|
38
|
-
return token.includes(' ') ? `"${token}"` : `${token}*`;
|
|
39
|
-
})
|
|
40
|
-
.join(' OR ');
|
|
32
|
+
export function queryFiles(safeQuery, limit = 10) {
|
|
41
33
|
console.log(`Executing search query: ${safeQuery}`);
|
|
42
34
|
const results = db.prepare(`
|
|
43
35
|
SELECT f.id, f.path, f.summary, f.type, f.last_modified, f.indexed_at
|
|
@@ -46,10 +38,6 @@ export function queryFiles(query, limit = 10) {
|
|
|
46
38
|
WHERE fts.files_fts MATCH ?
|
|
47
39
|
LIMIT ?
|
|
48
40
|
`).all(safeQuery, limit);
|
|
49
|
-
console.log(`Search returned ${results.length} results.`);
|
|
50
|
-
results.forEach(result => {
|
|
51
|
-
console.log(`📄 Found in FTS search: ${result.path}`);
|
|
52
|
-
});
|
|
53
41
|
return results;
|
|
54
42
|
}
|
|
55
43
|
/**
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Config } from './config.js';
|
|
|
5
5
|
import { createRequire } from 'module';
|
|
6
6
|
const require = createRequire(import.meta.url);
|
|
7
7
|
const { version } = require('../package.json');
|
|
8
|
+
// 🧠 Commands
|
|
8
9
|
import { suggestCommitMessage } from "./commands/CommitSuggesterCmd.js";
|
|
9
10
|
import { handleRefactor } from "./commands/RefactorCmd.js";
|
|
10
11
|
import { generateTests } from "./commands/TestGenCmd.js";
|
|
@@ -14,7 +15,7 @@ import { handleChangelogUpdate } from './commands/ChangeLogUpdateCmd.js';
|
|
|
14
15
|
import { runModulePipelineFromCLI } from './commands/ModulePipelineCmd.js';
|
|
15
16
|
import { runIndexCommand } from './commands/IndexCmd.js';
|
|
16
17
|
import { resetDatabase } from './commands/ResetDbCmd.js';
|
|
17
|
-
import {
|
|
18
|
+
import { runFindCommand } from './commands/FindCmd.js';
|
|
18
19
|
import { startDaemon } from './commands/DaemonCmd.js';
|
|
19
20
|
import { runStopDaemonCommand } from "./commands/StopDaemonCmd.js";
|
|
20
21
|
import { runAskCommand } from './commands/AskCmd.js';
|
|
@@ -113,9 +114,9 @@ cmd
|
|
|
113
114
|
});
|
|
114
115
|
// 🧠 Query and assistant
|
|
115
116
|
cmd
|
|
116
|
-
.command('
|
|
117
|
+
.command('find <query>')
|
|
117
118
|
.description('Search indexed files by keyword')
|
|
118
|
-
.action(
|
|
119
|
+
.action(runFindCommand);
|
|
119
120
|
cmd
|
|
120
121
|
.command('ask [question...]') // <- the ... makes it variadic
|
|
121
122
|
.description('Ask a question based on indexed files')
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
// src/utils/sanitizeQuery.ts
|
|
2
2
|
import { STOP_WORDS } from '../config/StopWords.js';
|
|
3
3
|
export function sanitizeQueryForFts(input) {
|
|
4
|
+
input = input.trim().toLowerCase();
|
|
5
|
+
// If it's a single filename-like string (includes dots or slashes), quote it
|
|
6
|
+
if (/^[\w\-./]+$/.test(input) && !/\s/.test(input)) {
|
|
7
|
+
// Escape quotes and wrap with double-quotes for FTS safety
|
|
8
|
+
return `"${input.replace(/"/g, '""')}"*`;
|
|
9
|
+
}
|
|
10
|
+
// Otherwise, treat it as a natural language prompt
|
|
4
11
|
const tokens = input
|
|
5
|
-
.trim()
|
|
6
12
|
.split(/\s+/)
|
|
7
13
|
.map(token => token.toLowerCase())
|
|
8
14
|
.filter(token => token.length > 2 &&
|
|
9
15
|
!STOP_WORDS.has(token) &&
|
|
10
16
|
/^[a-z0-9]+$/.test(token))
|
|
11
17
|
.map(token => token.replace(/[?*\\"]/g, '').replace(/'/g, "''") + '*');
|
|
12
|
-
|
|
13
|
-
if (tokens.length === 0)
|
|
14
|
-
return '*';
|
|
15
|
-
return tokens.join(' OR ');
|
|
18
|
+
return tokens.length > 0 ? tokens.join(' OR ') : '*';
|
|
16
19
|
}
|
|
@@ -3,6 +3,14 @@ export function wrapText(text, maxWidth) {
|
|
|
3
3
|
let wrappedText = '';
|
|
4
4
|
let currentLine = '';
|
|
5
5
|
words.forEach(word => {
|
|
6
|
+
// If the word is longer than the maxWidth, break it up into multiple lines
|
|
7
|
+
if (word.length > maxWidth) {
|
|
8
|
+
// Break the word into smaller chunks
|
|
9
|
+
while (word.length > maxWidth) {
|
|
10
|
+
wrappedText += word.slice(0, maxWidth) + '\n';
|
|
11
|
+
word = word.slice(maxWidth);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
6
14
|
// Check if adding the word would exceed the max width
|
|
7
15
|
if ((currentLine + word).length > maxWidth) {
|
|
8
16
|
wrappedText += currentLine + '\n'; // Add the current line and start a new one
|
|
@@ -13,6 +21,6 @@ export function wrapText(text, maxWidth) {
|
|
|
13
21
|
}
|
|
14
22
|
});
|
|
15
23
|
// Append the last line if any
|
|
16
|
-
wrappedText += currentLine;
|
|
24
|
+
wrappedText += currentLine.trim(); // trim() to remove the extra space at the end
|
|
17
25
|
return wrappedText;
|
|
18
26
|
}
|
package/package.json
CHANGED
package/dist/commands/EnvCmd.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export function checkEnv() {
|
|
2
|
-
const requiredVars = ["DB_HOST", "API_KEY"];
|
|
3
|
-
const missing = requiredVars.filter((v) => !process.env[v]);
|
|
4
|
-
if (missing.length) {
|
|
5
|
-
console.warn("❌ Missing env vars:", missing.join(", "));
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
8
|
-
console.log("✅ All env vars are set");
|
|
9
|
-
}
|
|
10
|
-
}
|