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.
package/dist/commands/AskCmd.js
CHANGED
|
@@ -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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
@@ -1,33 +1,75 @@
|
|
|
1
|
-
|
|
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');
|
|
15
|
+
.join('\n\n');
|
|
11
16
|
parts.push(`š§ Functions:\n${formattedFunctions}`);
|
|
12
17
|
}
|
|
13
18
|
else {
|
|
14
|
-
console.log(
|
|
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');
|
|
27
|
+
.join('\n\n');
|
|
23
28
|
return `⢠${f.path}: ${f.summary}\n${relatedFunctions}`;
|
|
24
29
|
})
|
|
25
|
-
.join('\n\n');
|
|
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
|
-
|
|
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
|
}
|
package/dist/utils/fileTree.js
CHANGED
|
@@ -1,17 +1,38 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { getIndexDir } from '../constants.js';
|
|
4
|
-
|
|
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
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
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 '';
|
|
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
|
}
|