scai 0.1.66 → 0.1.68

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.
@@ -7,7 +7,8 @@ import { generate } from '../lib/generate.js';
7
7
  import { buildContextualPrompt } from '../utils/buildContextualPrompt.js';
8
8
  import { generateFocusedFileTree } from '../utils/fileTree.js';
9
9
  import { log } from '../utils/log.js';
10
- import { PROMPT_LOG_PATH, SCAI_HOME, RELATED_FILES_LIMIT, MAX_SUMMARY_LINES, getIndexDir } from '../constants.js';
10
+ import { PROMPT_LOG_PATH, SCAI_HOME, RELATED_FILES_LIMIT, MAX_SUMMARY_LINES, getIndexDir, MAX_FUNCTION_LINES } from '../constants.js';
11
+ import chalk from 'chalk';
11
12
  export async function runAskCommand(query) {
12
13
  if (!query) {
13
14
  query = await promptOnce('šŸ’¬ Ask your question:\n');
@@ -81,10 +82,15 @@ export async function runAskCommand(query) {
81
82
  try {
82
83
  code = fs.readFileSync(filepath, 'utf-8');
83
84
  const topFileId = topFile.id;
84
- topFunctions = allFunctionsMap[topFileId]?.map(fn => ({
85
- name: fn.name,
86
- content: fn.content
87
- })) || [];
85
+ topFunctions = allFunctionsMap[topFileId]?.map(fn => {
86
+ const content = fn.content
87
+ ? fn.content.split('\n').slice(0, MAX_FUNCTION_LINES).join('\n')
88
+ : '(No content available)';
89
+ return {
90
+ name: fn.name,
91
+ content,
92
+ };
93
+ }) || [];
88
94
  }
89
95
  catch (err) {
90
96
  console.warn(`āš ļø Failed to read or analyze top file (${filepath}):`, err);
@@ -96,10 +102,15 @@ export async function runAskCommand(query) {
96
102
  if (summary) {
97
103
  summary = summary.split('\n').slice(0, MAX_SUMMARY_LINES).join('\n');
98
104
  }
99
- const functions = allFunctionsMap[fileId]?.map(fn => ({
100
- name: fn.name,
101
- content: fn.content || '(No content available)', // Assuming content is available
102
- })) || [];
105
+ const functions = allFunctionsMap[fileId]?.map(fn => {
106
+ const content = fn.content
107
+ ? fn.content.split('\n').slice(0, MAX_FUNCTION_LINES).join('\n')
108
+ : '(No content available)';
109
+ return {
110
+ name: fn.name,
111
+ content,
112
+ };
113
+ }) || [];
103
114
  return {
104
115
  path: file.path,
105
116
  summary,
@@ -115,6 +126,8 @@ export async function runAskCommand(query) {
115
126
  console.warn('āš ļø Could not generate file tree:', e);
116
127
  }
117
128
  // 🟩 STEP 7: Build prompt
129
+ console.log(chalk.blueBright('\nšŸ“¦ Building contextual prompt...'));
130
+ console.log(chalk.gray(`[runAskCommand] Calling buildContextualPrompt()`));
118
131
  const promptContent = buildContextualPrompt({
119
132
  baseInstruction: query,
120
133
  code,
@@ -124,6 +137,8 @@ export async function runAskCommand(query) {
124
137
  projectFileTree: fileTree || undefined,
125
138
  fileFunctions,
126
139
  });
140
+ console.log(chalk.greenBright('āœ… Prompt built successfully.'));
141
+ console.log(chalk.cyan(`[runAskCommand] Prompt token estimate: ~${Math.round(promptContent.length / 4)} tokens`));
127
142
  // 🟩 STEP 8: Save prompt
128
143
  try {
129
144
  if (!fs.existsSync(SCAI_HOME))
package/dist/constants.js CHANGED
@@ -61,3 +61,7 @@ export const CANDIDATE_LIMIT = 100;
61
61
  * Limit number of summary lines
62
62
  */
63
63
  export const MAX_SUMMARY_LINES = 12;
64
+ /**
65
+ * Limit number of function content
66
+ */
67
+ export const MAX_FUNCTION_LINES = 12;
@@ -1,33 +1,75 @@
1
- export function buildContextualPrompt({ baseInstruction, code, summary, functions, relatedFiles, projectFileTree }) {
1
+ // File: src/utils/buildContextualPrompt.ts
2
+ import chalk from 'chalk';
3
+ import path from 'path';
4
+ function estimateTokenCount(text) {
5
+ return Math.round(text.length / 4); // simple heuristic approximation
6
+ }
7
+ export function buildContextualPrompt({ baseInstruction, code, summary, functions, relatedFiles, projectFileTree, }) {
2
8
  const parts = [baseInstruction];
3
9
  if (summary) {
4
10
  parts.push(`šŸ“„ File Summary:\n${summary}`);
5
11
  }
6
12
  if (functions?.length) {
7
- // Display each function's name and content
8
13
  const formattedFunctions = functions
9
14
  .map(fn => `• ${fn.name}:\n${fn.content}`)
10
- .join('\n\n'); // Adds a line break between each function
15
+ .join('\n\n');
11
16
  parts.push(`šŸ”§ Functions:\n${formattedFunctions}`);
12
17
  }
13
18
  else {
14
- console.log(`šŸ”§ No functions found `);
19
+ console.log(chalk.gray(`[buildContextualPrompt]`) +
20
+ chalk.yellow(` āš ļø No functions found in top file.`));
15
21
  }
16
22
  if (relatedFiles?.length) {
17
- // Include functions from related files
18
23
  const formattedRelatedFiles = relatedFiles
19
24
  .map(f => {
20
25
  const relatedFunctions = f.functions
21
26
  .map(fn => ` • ${fn.name}:\n ${fn.content}`)
22
- .join('\n\n'); // Adds a line break between related file functions
27
+ .join('\n\n');
23
28
  return `• ${f.path}: ${f.summary}\n${relatedFunctions}`;
24
29
  })
25
- .join('\n\n'); // Adds a line break between related files
30
+ .join('\n\n');
26
31
  parts.push(`šŸ“š Related Files:\n${formattedRelatedFiles}`);
27
32
  }
28
33
  if (projectFileTree) {
29
34
  parts.push(`šŸ“ Project File Structure:\n\`\`\`\n${projectFileTree.trim()}\n\`\`\``);
30
35
  }
31
36
  parts.push(`\n--- CODE START ---\n${code}\n--- CODE END ---`);
32
- return parts.join('\n\n');
37
+ const prompt = parts.join('\n\n');
38
+ const tokenEstimate = estimateTokenCount(prompt);
39
+ // šŸ”µ Colorized diagnostic output
40
+ // šŸ”µ Colorized diagnostic output
41
+ const header = chalk.bgBlue.white.bold(' [SCAI] Prompt Overview ');
42
+ const labelColor = chalk.cyan;
43
+ const contentColor = chalk.gray;
44
+ console.log('\n' + header);
45
+ console.log(labelColor('šŸ”¢ Token Estimate:'), contentColor(`${tokenEstimate.toLocaleString()} tokens`));
46
+ // šŸ“„ Summary
47
+ if (summary) {
48
+ console.log(labelColor('šŸ“„ Summary:'), contentColor(`${estimateTokenCount(summary).toLocaleString()} tokens`));
49
+ }
50
+ // šŸ”§ Functions
51
+ if (functions?.length) {
52
+ const fnCount = functions.length;
53
+ const fnTokens = functions.reduce((sum, f) => sum + estimateTokenCount(f.content), 0);
54
+ console.log(labelColor(`šŸ”§ Functions (${fnCount}):`), contentColor(`${fnTokens.toLocaleString()} tokens`));
55
+ }
56
+ // šŸ“š Related Files
57
+ if (relatedFiles?.length) {
58
+ const relCount = relatedFiles.length;
59
+ const relTokens = relatedFiles.reduce((sum, f) => sum + estimateTokenCount(f.summary), 0);
60
+ console.log(labelColor(`šŸ“š Related Files (${relCount}):`), contentColor(`${relTokens.toLocaleString()} tokens`));
61
+ // Optional: Show top 3 file names
62
+ const fileList = relatedFiles.slice(0, 3).map(f => `- ${path.basename(f.path)}`).join('\n');
63
+ if (fileList)
64
+ console.log(contentColor(fileList + (relCount > 3 ? `\n ...+${relCount - 3} more` : '')));
65
+ }
66
+ // šŸ“ File Tree
67
+ if (projectFileTree) {
68
+ console.log(labelColor('šŸ“ File Tree:'), contentColor(`${estimateTokenCount(projectFileTree).toLocaleString()} tokens`));
69
+ }
70
+ // šŸ“¦ Code Section
71
+ console.log(labelColor('šŸ“¦ Code:'), contentColor(`${estimateTokenCount(prompt).toLocaleString()} tokens`));
72
+ // šŸ“Œ Key
73
+ console.log(labelColor('šŸ” Key:'), contentColor('[buildContextualPrompt]\n'));
74
+ return prompt;
33
75
  }
@@ -1,17 +1,38 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { getIndexDir } from '../constants.js';
4
- export function generateFocusedFileTree(focusPath, maxDepth = 2) {
4
+ /**
5
+ * Generate a reduced file tree centered around the focus path, including nearby sibling folders.
6
+ */
7
+ export function generateFocusedFileTree(focusPath, maxDepth = 2, siblingWindow = 2) {
5
8
  const absoluteFocus = path.resolve(focusPath);
6
- const parentDir = path.dirname(absoluteFocus);
9
+ const fileOrDir = fs.statSync(absoluteFocus);
10
+ const targetDir = fileOrDir.isDirectory()
11
+ ? absoluteFocus
12
+ : path.dirname(absoluteFocus);
13
+ const parentDir = path.dirname(targetDir);
7
14
  const indexDir = getIndexDir();
8
- const relativeTitle = path.relative(indexDir, parentDir).replace(/\\/g, '/');
9
- const tree = generateFileTree(parentDir, maxDepth, absoluteFocus);
10
- return `šŸ“‚ ${relativeTitle || '.'}\n${tree}`;
15
+ const siblings = fs
16
+ .readdirSync(parentDir, { withFileTypes: true })
17
+ .filter(entry => entry.isDirectory())
18
+ .sort((a, b) => a.name.localeCompare(b.name));
19
+ const focusIndex = siblings.findIndex(entry => path.resolve(path.join(parentDir, entry.name)) === path.resolve(targetDir));
20
+ const start = Math.max(0, focusIndex - siblingWindow);
21
+ const end = Math.min(siblings.length, focusIndex + siblingWindow + 1);
22
+ const nearbySiblings = siblings.slice(start, end);
23
+ const relativeTitle = path.relative(indexDir, parentDir).replace(/\\/g, '/') || '.';
24
+ let output = `šŸ“‚ ${relativeTitle}\n`;
25
+ nearbySiblings.forEach(entry => {
26
+ const siblingPath = path.join(parentDir, entry.name);
27
+ const isFocusDir = path.resolve(siblingPath) === path.resolve(targetDir);
28
+ const tree = generateFileTree(siblingPath, maxDepth - 1, isFocusDir ? absoluteFocus : undefined, '│ ');
29
+ output += `${isFocusDir ? 'āž”ļø ' : ''}${entry.name}/\n${tree}`;
30
+ });
31
+ return output;
11
32
  }
12
33
  function generateFileTree(dir, depth, highlightPath, prefix = '') {
13
34
  if (depth < 0)
14
- return ''; // Stop at maxDepth
35
+ return '';
15
36
  let output = '';
16
37
  const entries = fs.readdirSync(dir, { withFileTypes: true });
17
38
  const sorted = entries.sort((a, b) => Number(b.isDirectory()) - Number(a.isDirectory()));
@@ -20,13 +41,11 @@ function generateFileTree(dir, depth, highlightPath, prefix = '') {
20
41
  const connector = isLast ? '└── ' : 'ā”œā”€ā”€ ';
21
42
  const fullPath = path.join(dir, entry.name);
22
43
  const isHighlighted = highlightPath && path.resolve(fullPath) === path.resolve(highlightPath);
23
- // If it is a directory, recurse and reduce depth
24
44
  if (entry.isDirectory()) {
25
45
  output += `${prefix}${connector}${entry.name}/\n`;
26
46
  output += generateFileTree(fullPath, depth - 1, highlightPath, prefix + (isLast ? ' ' : '│ '));
27
47
  }
28
48
  else {
29
- // Highlight the file if it matches the focusPath
30
49
  const name = isHighlighted ? `āž”ļø ${entry.name}` : entry.name;
31
50
  output += `${prefix}${connector}${name}\n`;
32
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.66",
3
+ "version": "0.1.68",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"