scai 0.1.25 → 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.
@@ -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 runQueryCommand(query) {
4
+ export async function runFindCommand(query) {
4
5
  if (!query) {
5
- console.error('❌ Please provide a search query.\n👉 Usage: scai query "keyword"');
6
+ console.error('❌ Please provide a search query.\n👉 Usage: scai find "keyword"');
6
7
  return;
7
8
  }
8
- console.log(`🔍 Searching for: "${query}"\n`);
9
- const results = queryFiles(query);
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 { summaryModule } from '../pipeline/modules/summaryModule.js'; // Import summaryModule
4
- import { summarizeCode } from '../utils/summarizer.js'; // Import summarizeCode
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
- if (filepath) {
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
- content = await fs.readFile(filepath, 'utf-8');
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 read or summarize ${filepath}:`, err.message);
40
+ console.error(`❌ Could not process ${filepath}:`, err.message);
13
41
  return;
14
42
  }
15
43
  }
16
- else if (process.stdin.isTTY) {
17
- console.error('❌ No file provided and no piped input.\n👉 Usage: scai summ <file> or cat file | scai summ');
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
- // Call the summary module to get the raw summary
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("No summary available.");
66
+ console.warn('⚠️ No summary generated.');
36
67
  return;
37
68
  }
38
- const formattedSummary = summarizeCode(response.summary);
39
- console.log(formattedSummary);
69
+ // Print the formatted summary
70
+ console.log(summarizeCode(response.summary));
40
71
  }
41
72
  else {
42
- console.error('❌ No code provided to summarize.');
73
+ console.error('❌ No content provided to summarize.');
43
74
  }
44
75
  }
@@ -1,52 +1,68 @@
1
1
  export const IGNORED_EXTENSIONS = [
2
- // 🖼 Media
2
+ // Media
3
3
  '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico',
4
4
  '.mp4', '.mp3', '.mov', '.avi', '.mkv', '.flv', '.wav', '.flac',
5
5
  '.aac', '.m4a', '.wma', '.3gp', '.webm', '.ogg', '.aiff', '.au',
6
- // 📦 Archives & install packages
6
+ // Archives & install packages
7
7
  '.zip', '.tar', '.gz', '.bz2', '.xz', '.rar', '.7z',
8
8
  '.jar', '.war', '.ear', '.deb', '.rpm', '.pkg', '.msi', '.dmg', '.cab', '.apk',
9
9
  '.tar.gz', '.tar.bz2', '.tar.xz', '.tar.lzma', '.tar.zst',
10
- // 🧱 Binaries & executables
10
+ // Binaries & executables
11
11
  '.exe', '.dll', '.bin', '.so', '.dylib', '.a', '.lib',
12
12
  '.iso', '.img', '.elf', '.o', '.obj', '.msm', '.vbs', '.jscript',
13
13
  '.cmd', '.bat', '.ps1', '.sh', '.bash', '.run',
14
- // 🧪 Runtime / build / cache
14
+ // Runtime / build / cache
15
15
  '.log', '.tmp', '.map',
16
16
  '.db', '.sqlite', '.pkl', '.sav', '.rdb', '.ldb',
17
17
  '.pyc', '.class', '.tsbuildinfo', '.coverage', '.eslintcache',
18
18
  '.yarn', '.webpack', '.babel', '.compilercache',
19
- // 🔤 Fonts & styles
19
+ // Fonts & styles
20
20
  '.woff', '.woff2', '.ttf', '.eot', '.otf', '.css.map',
21
21
  '.scss', '.sass', '.less', '.styl',
22
- // 🔐 Certs, keys, credentials
22
+ // Certs, keys, credentials
23
23
  '.crt', '.key', '.pem', '.pub', '.asc', '.gpg', '.p12', '.csr', '.der', '.pfx',
24
- // ♻️ Backups / temp
24
+ // Backups / temp
25
25
  '.bak', '.old', '.swp', '.swo', '.orig',
26
26
  '.sublime-workspace', '.sublime-project', '.db-shm', '.db-wal',
27
- // 🌐 System/config folders (still ignored by path, not extension)
27
+ // System/config folders (still ignored by path, not extension)
28
28
  '.DS_Store', '.bundle', '.npmrc',
29
- // 🗺️ GIS / Geospatial
29
+ // GIS / Geospatial
30
30
  '.shp', '.shx', '.dbf', '.prj', '.qix', '.sbn', '.sbx', '.shp.xml', '.cpg', '.gpkg', '.mif', '.mid',
31
- // 📊 Enterprise BI / Reporting
31
+ // Enterprise BI / Reporting
32
32
  '.pbix', '.rdl', '.rpt', '.bqy', '.iqy',
33
- // 🧪 ETL / DWH / Modeling
33
+ // ETL / DWH / Modeling
34
34
  '.abf', '.dtsx', '.bim', '.xmi',
35
- // 🏗️ CAD / Engineering
35
+ // CAD / Engineering
36
36
  '.dwg', '.dxf', '.step', '.stp', '.sldprt', '.sldasm',
37
37
  '.iges', '.igs', '.3ds', '.fbx',
38
- // 🧾 Forms / Print / Publishing
38
+ // Forms / Print / Publishing
39
39
  '.xps', '.afpub', '.pub', '.indd', '.qxd', '.frm', '.frx', '.frl',
40
- // 💰 ERP / Finance / Legacy DB
40
+ // ERP / Finance / Legacy DB
41
41
  '.mbd', '.fdb', '.nav', '.accdb', '.mdb', '.gdb',
42
42
  '.sap', '.sappkg', '.qbw', '.qbb',
43
- // 🔒 Lock files (but NOT lock *configs*)
43
+ // Lock files (but NOT lock *configs*)
44
44
  '.lck', '.lockfile', '.db-lock', '.pid', '.socket',
45
- // ⚠️ Added file types that are only kept by exception
46
- '.xml', // Only specific XML files like pom.xml should be kept
47
- '.json', // Kept only if exact filename is in exceptions
48
- '.yaml', // Kept only if filename is explicitly whitelisted
49
- '.yml',
50
- '.md',
51
- '.txt',
45
+ // Documentation and Readme Files
46
+ '.md', // Markdown files for documentation
47
+ '.rst', // reStructuredText files for documentation
48
+ '.txt', // Plain text files (can be used for documentation or notes)
49
+ // Configuration files (JSON/YAML/XML)
50
+ '.json', // General JSON config files
51
+ '.yaml', // YAML configuration files
52
+ '.yml', // YAML configuration files
53
+ '.xml', // XML configuration files
54
+ // Miscellaneous Important Files
55
+ '.properties', // Java properties files (configuration)
56
+ '.xml.bck', // Backup XML files
57
+ '.bak', // General backup files
58
+ '.swp', // Swap files (e.g., vim/sublime temp files)
59
+ '.swo', // Swap files
60
+ '.orig', // Original backup files
61
+ '.old', // Old version files
62
+ '.~', // Temp files generated by some editors (e.g., vim)
63
+ '.md5', // MD5 checksums
64
+ '.sha1', // SHA1 checksums
65
+ '.sha256', // SHA256 checksums
66
+ '.tmp', // Temporary files
67
+ '.bak', // Backup file (for various applications)
52
68
  ];
@@ -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(query, limit = 10) {
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 { runQueryCommand } from './commands/QueryCmd.js';
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('query <query>')
117
+ .command('find <query>')
117
118
  .description('Search indexed files by keyword')
118
- .action(runQueryCommand);
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
- // 👇 Prevent FTS syntax errors by returning a catch-all query
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"
@@ -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
- }