scai 0.1.41 → 0.1.42

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,9 +1,16 @@
1
- import readline from 'readline';
1
+ import fs from 'fs';
2
2
  import path from 'path';
3
- import { searchFiles, queryFiles } from "../db/fileIndex.js";
4
- import { sanitizeQueryForFts } from "../utils/sanitizeQuery.js";
5
- import { generate } from "../lib/generate.js";
3
+ import readline from 'readline';
4
+ import { searchFiles, queryFiles } from '../db/fileIndex.js';
5
+ import { sanitizeQueryForFts } from '../utils/sanitizeQuery.js';
6
+ import { generate } from '../lib/generate.js';
7
+ import { buildContextualPrompt } from '../utils/buildContextualPrompt.js';
8
+ import { generateFileTree } from '../utils/fileTree.js';
9
+ import { log } from '../utils/log.js';
10
+ import { PROMPT_LOG_PATH, SCAI_HOME, INDEX_DIR } from '../constants.js';
11
+ const MAX_RELATED_FILES = 5;
6
12
  export async function runAskCommand(query) {
13
+ // 🧠 Prompt the user if no query is passed
7
14
  if (!query) {
8
15
  query = await promptOnce('🧠 Ask your question:\n> ');
9
16
  }
@@ -12,21 +19,32 @@ export async function runAskCommand(query) {
12
19
  console.error('āŒ No question provided.\nšŸ‘‰ Usage: scai ask "your question"');
13
20
  return;
14
21
  }
22
+ console.log(`šŸ“ Using index root: ${INDEX_DIR}`);
15
23
  console.log(`šŸ” Searching for: "${query}"\n`);
24
+ // 🧠 Step 1: Semantic + fallback search
16
25
  const start = Date.now();
17
- const semanticResults = await searchFiles(query, 5);
26
+ const semanticResults = await searchFiles(query, MAX_RELATED_FILES);
18
27
  const duration = Date.now() - start;
19
28
  console.log(`ā±ļø searchFiles took ${duration}ms and returned ${semanticResults.length} result(s)`);
20
- // Also run fallback keyword search
29
+ // šŸ” Log raw semantic results
30
+ console.log('šŸ” Raw semantic search results:');
31
+ semanticResults.forEach((file, i) => {
32
+ console.log(` ${i + 1}. šŸ“„ Path: ${file.path} | Score: ${file.score?.toFixed(3) ?? 'n/a'}`);
33
+ });
21
34
  const safeQuery = sanitizeQueryForFts(query);
22
35
  const fallbackResults = queryFiles(safeQuery, 10);
23
- // Merge semantic and fallback results
36
+ // šŸ” Log raw keyword fallback results
37
+ console.log('\nšŸ” Raw fallback keyword (FTS) search results:');
38
+ fallbackResults.forEach((file, i) => {
39
+ console.log(` ${i + 1}. šŸ“„ Path: ${file.path}`);
40
+ });
41
+ // 🧠 Step 2: Merge results
24
42
  const seen = new Set();
25
43
  const combinedResults = [];
26
44
  for (const file of semanticResults) {
27
45
  const resolved = path.resolve(file.path);
28
46
  seen.add(resolved);
29
- combinedResults.push(file); // Already scored
47
+ combinedResults.push(file);
30
48
  }
31
49
  for (const file of fallbackResults) {
32
50
  const resolved = path.resolve(file.path);
@@ -35,7 +53,7 @@ export async function runAskCommand(query) {
35
53
  combinedResults.push({
36
54
  path: file.path,
37
55
  summary: file.summary,
38
- score: 0.0, // fallback score
56
+ score: 0.0,
39
57
  });
40
58
  }
41
59
  }
@@ -46,26 +64,43 @@ export async function runAskCommand(query) {
46
64
  });
47
65
  }
48
66
  else {
49
- console.log('āš ļø No similar files found. Asking the model for context only...');
67
+ console.log('āš ļø No similar files found. Asking the model using question only...');
50
68
  }
51
- // Aggregate summaries
52
- let allSummaries = '';
53
- for (const file of combinedResults) {
54
- if (!file?.summary) {
55
- console.warn(`āš ļø No summary available for file: ${file?.path}`);
56
- continue;
57
- }
58
- console.log(`šŸ“ Using stored summary for: ${file.path}`);
59
- allSummaries += `\n${file.summary}`;
69
+ // 🧠 Step 3: Build metadata for prompt
70
+ const relatedFiles = combinedResults.slice(0, MAX_RELATED_FILES).map(file => ({
71
+ path: file.path,
72
+ summary: file.summary || '(No summary available)',
73
+ }));
74
+ let fileTree = '';
75
+ try {
76
+ fileTree = generateFileTree(INDEX_DIR, 2); // Limit depth
77
+ }
78
+ catch (e) {
79
+ console.warn('āš ļø Failed to generate file tree:', e);
80
+ }
81
+ const prompt = buildContextualPrompt({
82
+ baseInstruction: query,
83
+ code: '', // No specific code selected
84
+ relatedFiles,
85
+ projectFileTree: fileTree || undefined,
86
+ });
87
+ // 🧠 Step 4: Log prompt to file
88
+ try {
89
+ if (!fs.existsSync(SCAI_HOME))
90
+ fs.mkdirSync(SCAI_HOME, { recursive: true });
91
+ fs.writeFileSync(PROMPT_LOG_PATH, prompt, 'utf-8');
92
+ log(`šŸ“ Prompt saved to ${PROMPT_LOG_PATH}`);
93
+ }
94
+ catch (err) {
95
+ log('āŒ Failed to write prompt log:', err);
60
96
  }
61
- const input = {
62
- content: allSummaries ? `${query}\n\n${allSummaries}` : query,
63
- filepath: '',
64
- };
97
+ // 🧠 Step 5: Call the model
65
98
  try {
66
- console.log(allSummaries.trim()
67
- ? '🧠 Summaries found, sending them to the model for synthesis...'
68
- : 'āš ļø No summaries found. Asking the model for context only...');
99
+ console.log('šŸ¤– Asking the model...');
100
+ const input = {
101
+ content: prompt,
102
+ filepath: '',
103
+ };
69
104
  const modelResponse = await generate(input, 'llama3');
70
105
  console.log(`\nšŸ“ Model response:\n${modelResponse.content}`);
71
106
  }
@@ -77,7 +112,7 @@ function promptOnce(promptText) {
77
112
  return new Promise(resolve => {
78
113
  const rl = readline.createInterface({
79
114
  input: process.stdin,
80
- output: process.stdout
115
+ output: process.stdout,
81
116
  });
82
117
  rl.question(promptText, answer => {
83
118
  rl.close();
package/dist/constants.js CHANGED
@@ -26,6 +26,11 @@ export const CONFIG_PATH = path.join(SCAI_HOME, 'config.json');
26
26
  * ~/.scai/daemon.log
27
27
  */
28
28
  export const LOG_PATH = path.join(SCAI_HOME, 'daemon.log');
29
+ /**
30
+ * Path to the last prompt sent to the model:
31
+ * ~/.scai/prompt.log
32
+ */
33
+ export const PROMPT_LOG_PATH = path.join(SCAI_HOME, 'prompt.log');
29
34
  /**
30
35
  * Get the active index directory.
31
36
  *
@@ -0,0 +1,20 @@
1
+ export function buildContextualPrompt({ baseInstruction, code, summary, functions, relatedFiles, projectFileTree }) {
2
+ const parts = [baseInstruction];
3
+ if (summary) {
4
+ parts.push(`šŸ“„ File Summary:\n${summary}`);
5
+ }
6
+ if (functions?.length) {
7
+ parts.push(`šŸ”§ Functions:\n${functions.join(', ')}`);
8
+ }
9
+ if (relatedFiles?.length) {
10
+ const formatted = relatedFiles
11
+ .map(f => `• ${f.path}: ${f.summary}`)
12
+ .join('\n');
13
+ parts.push(`šŸ“š Related Files:\n${formatted}`);
14
+ }
15
+ if (projectFileTree) {
16
+ parts.push(`šŸ“ Project File Structure:\n\`\`\`\n${projectFileTree.trim()}\n\`\`\``);
17
+ }
18
+ parts.push(`\n--- CODE START ---\n${code}\n--- CODE END ---`);
19
+ return parts.join('\n\n');
20
+ }
@@ -0,0 +1,30 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ const IGNORED_DIRS = new Set([
4
+ 'node_modules', 'dist', '.git', '.vscode', 'coverage', 'build', 'out', 'logs', 'tmp'
5
+ ]);
6
+ export function generateFileTree(dir, maxDepth = 3, currentDepth = 0, prefix = '') {
7
+ if (currentDepth > maxDepth)
8
+ return '';
9
+ let output = '';
10
+ const items = fs.readdirSync(dir, { withFileTypes: true })
11
+ .filter(item => !IGNORED_DIRS.has(item.name))
12
+ .sort((a, b) => {
13
+ // Directories first
14
+ if (a.isDirectory() && !b.isDirectory())
15
+ return -1;
16
+ if (!a.isDirectory() && b.isDirectory())
17
+ return 1;
18
+ return a.name.localeCompare(b.name);
19
+ });
20
+ for (const [i, item] of items.entries()) {
21
+ const isLast = i === items.length - 1;
22
+ const connector = isLast ? '└── ' : 'ā”œā”€ā”€ ';
23
+ const childPrefix = prefix + (isLast ? ' ' : '│ ');
24
+ output += `${prefix}${connector}${item.name}\n`;
25
+ if (item.isDirectory()) {
26
+ output += generateFileTree(path.join(dir, item.name), maxDepth, currentDepth + 1, childPrefix);
27
+ }
28
+ }
29
+ return output;
30
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"