scai 0.1.42 → 0.1.44

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.
@@ -67,10 +67,12 @@ export async function runAskCommand(query) {
67
67
  console.log('⚠️ No similar files found. Asking the model using question only...');
68
68
  }
69
69
  // 🧠 Step 3: Build metadata for prompt
70
- const relatedFiles = combinedResults.slice(0, MAX_RELATED_FILES).map(file => ({
70
+ const relatedFiles = combinedResults.slice(0, MAX_RELATED_FILES).map((file, index) => ({
71
71
  path: file.path,
72
- summary: file.summary || '(No summary available)',
72
+ summary: file.summary || '(No summary available)', // Ensure summary is included
73
73
  }));
74
+ // Get the top-ranked file (the first one in the sorted results)
75
+ const topRankedFile = combinedResults[0]; // The most relevant file
74
76
  let fileTree = '';
75
77
  try {
76
78
  fileTree = generateFileTree(INDEX_DIR, 2); // Limit depth
@@ -78,17 +80,18 @@ export async function runAskCommand(query) {
78
80
  catch (e) {
79
81
  console.warn('⚠️ Failed to generate file tree:', e);
80
82
  }
81
- const prompt = buildContextualPrompt({
83
+ // Now we can build the prompt with summaries included for each file
84
+ const promptContent = buildContextualPrompt({
82
85
  baseInstruction: query,
83
86
  code: '', // No specific code selected
84
- relatedFiles,
87
+ relatedFiles, // This now includes both path and summary for each file
85
88
  projectFileTree: fileTree || undefined,
86
89
  });
87
90
  // 🧠 Step 4: Log prompt to file
88
91
  try {
89
92
  if (!fs.existsSync(SCAI_HOME))
90
93
  fs.mkdirSync(SCAI_HOME, { recursive: true });
91
- fs.writeFileSync(PROMPT_LOG_PATH, prompt, 'utf-8');
94
+ fs.writeFileSync(PROMPT_LOG_PATH, promptContent, 'utf-8');
92
95
  log(`📝 Prompt saved to ${PROMPT_LOG_PATH}`);
93
96
  }
94
97
  catch (err) {
@@ -97,9 +100,15 @@ export async function runAskCommand(query) {
97
100
  // 🧠 Step 5: Call the model
98
101
  try {
99
102
  console.log('🤖 Asking the model...');
103
+ // Create a more structured PromptInput object
100
104
  const input = {
101
- content: prompt,
102
- filepath: '',
105
+ content: query, // Main instruction (the query)
106
+ filepath: topRankedFile?.path || '', // Include the path of the top-ranked file
107
+ metadata: {
108
+ summary: topRankedFile?.summary || '', // Add summary of the top-ranked file
109
+ relatedFiles: relatedFiles, // Pass related files as part of metadata
110
+ },
111
+ projectFileTree: fileTree || '' // Include file structure in metadata
103
112
  };
104
113
  const modelResponse = await generate(input, 'llama3');
105
114
  console.log(`\n📝 Model response:\n${modelResponse.content}`);
@@ -1,27 +1,45 @@
1
- import fsSync from 'fs';
2
- import { LOG_PATH, PID_PATH, SCAI_HOME } from '../constants.js';
1
+ import { db } from '../db/client.js';
2
+ import { runDaemonBatch } from './daemonBatch.js'; // assuming this function is already defined
3
3
  import { log } from '../utils/log.js';
4
- import { runDaemonBatch } from '../daemon/daemonBatch.js'; // ✅ now from utils
5
- const SLEEP_MS = 30 * 1000;
6
- const IDLE_SLEEP_MS = 4 * SLEEP_MS;
7
- function sleep(ms) {
8
- return new Promise(resolve => setTimeout(resolve, ms));
4
+ // Time between each batch in milliseconds
5
+ const SLEEP_MS = 2000; // Adjust as needed
6
+ const IDLE_SLEEP_MS = 5000; // Adjust as needed
7
+ // Check if there are any files left to process
8
+ async function isQueueEmpty() {
9
+ // Query the database for the count of files with certain processing statuses
10
+ const row = db.prepare(`
11
+ SELECT COUNT(*) AS count
12
+ FROM files
13
+ WHERE processing_status IN ('unprocessed')
14
+ `).get();
15
+ // Cast the row to an object that has a `count` property of type number
16
+ const castRow = row;
17
+ // Check if the casted `row` has a valid `count` property (number)
18
+ if (typeof castRow.count !== 'number') {
19
+ console.error('Error: Invalid count value in the database query result.');
20
+ return true; // Assume queue is empty if the count is invalid
21
+ }
22
+ // Return true if count is 0, otherwise false
23
+ return castRow.count === 0;
9
24
  }
10
- async function runDaemonScheduler() {
11
- fsSync.mkdirSync(SCAI_HOME, { recursive: true });
12
- fsSync.writeFileSync(PID_PATH, process.pid.toString(), 'utf-8');
13
- fsSync.appendFileSync(LOG_PATH, `\n\n🧠 Daemon started at ${new Date().toISOString()} — PID ${process.pid}\n`);
14
- let cycles = 0;
25
+ export async function daemonWorker() {
15
26
  while (true) {
27
+ // Execute a batch job
16
28
  const didWork = await runDaemonBatch();
17
- cycles++;
18
- if (cycles % 20 === 0) {
19
- log(`🌀 Still running. Cycles: ${cycles}`);
29
+ if (!didWork) {
30
+ // Check if the queue is empty after a batch job
31
+ const queueEmpty = await isQueueEmpty();
32
+ if (queueEmpty) {
33
+ // If no files are left to process, stop the daemon
34
+ log("✅ No more work left. Stopping daemon.");
35
+ break;
36
+ }
20
37
  }
38
+ // Sleep for a set amount of time before checking again
21
39
  await sleep(didWork ? SLEEP_MS : IDLE_SLEEP_MS);
22
40
  }
23
41
  }
24
- runDaemonScheduler().catch(err => {
25
- log(`❌ Daemon crashed: ${err instanceof Error ? err.message : String(err)}`);
26
- process.exit(1);
27
- });
42
+ // Sleep function to control how often the worker checks
43
+ function sleep(ms) {
44
+ return new Promise(resolve => setTimeout(resolve, ms));
45
+ }
@@ -1,14 +1,22 @@
1
1
  import { parse } from 'acorn';
2
- import { simple as walkSimple } from 'acorn-walk';
2
+ import { ancestor as walkAncestor } from 'acorn-walk';
3
3
  import { generateEmbedding } from '../../lib/generateEmbedding.js';
4
4
  import { db } from '../client.js';
5
5
  import path from 'path';
6
6
  import { log } from '../../utils/log.js';
7
- /**
8
- * Parses a JavaScript/TypeScript file, extracts all top-level functions,
9
- * generates embeddings, and indexes both the functions and any calls made
10
- * within each function into the database.
11
- */
7
+ function getFunctionName(node, parent, fileName) {
8
+ if (node.id?.name)
9
+ return node.id.name;
10
+ if (parent?.type === 'VariableDeclarator' && parent.id?.name)
11
+ return parent.id.name;
12
+ if (parent?.type === 'Property' && parent.key?.name)
13
+ return parent.key.name;
14
+ if (parent?.type === 'AssignmentExpression' && parent.left?.name)
15
+ return parent.left.name;
16
+ if (parent?.type === 'MethodDefinition' && parent.key?.name)
17
+ return parent.key.name;
18
+ return `${fileName}:<anon>`;
19
+ }
12
20
  export async function extractFromJS(filePath, content, fileId) {
13
21
  const ast = parse(content, {
14
22
  ecmaVersion: 'latest',
@@ -16,27 +24,36 @@ export async function extractFromJS(filePath, content, fileId) {
16
24
  locations: true,
17
25
  });
18
26
  const functions = [];
19
- walkSimple(ast, {
20
- FunctionDeclaration(node) {
21
- const name = node.id?.name || `${path.basename(filePath)}:<anon>`;
22
- const start_line = node?.loc?.start.line ?? -1;
23
- const end_line = node?.loc?.end.line ?? -1;
24
- const body = content.slice(node.start, node.end);
25
- functions.push({ name, start_line, end_line, body });
27
+ walkAncestor(ast, {
28
+ FunctionDeclaration(node, ancestors) {
29
+ const parent = ancestors[ancestors.length - 2];
30
+ const name = getFunctionName(node, parent, path.basename(filePath));
31
+ functions.push({
32
+ name,
33
+ start_line: node.loc?.start.line ?? -1,
34
+ end_line: node.loc?.end.line ?? -1,
35
+ body: content.slice(node.start, node.end),
36
+ });
26
37
  },
27
- FunctionExpression(node) {
28
- const name = `${path.basename(filePath)}:<anon>`;
29
- const start_line = node?.loc?.start.line ?? -1;
30
- const end_line = node?.loc?.end.line ?? -1;
31
- const body = content.slice(node.start, node.end);
32
- functions.push({ name, start_line, end_line, body });
38
+ FunctionExpression(node, ancestors) {
39
+ const parent = ancestors[ancestors.length - 2];
40
+ const name = getFunctionName(node, parent, path.basename(filePath));
41
+ functions.push({
42
+ name,
43
+ start_line: node.loc?.start.line ?? -1,
44
+ end_line: node.loc?.end.line ?? -1,
45
+ body: content.slice(node.start, node.end),
46
+ });
33
47
  },
34
- ArrowFunctionExpression(node) {
35
- const name = `${path.basename(filePath)}:<anon>`;
36
- const start_line = node?.loc?.start.line ?? -1;
37
- const end_line = node?.loc?.end.line ?? -1;
38
- const body = content.slice(node.start, node.end);
39
- functions.push({ name, start_line, end_line, body });
48
+ ArrowFunctionExpression(node, ancestors) {
49
+ const parent = ancestors[ancestors.length - 2];
50
+ const name = getFunctionName(node, parent, path.basename(filePath));
51
+ functions.push({
52
+ name,
53
+ start_line: node.loc?.start.line ?? -1,
54
+ end_line: node.loc?.end.line ?? -1,
55
+ body: content.slice(node.start, node.end),
56
+ });
40
57
  },
41
58
  });
42
59
  if (functions.length === 0) {
@@ -62,15 +79,15 @@ export async function extractFromJS(filePath, content, fileId) {
62
79
  lang: 'js'
63
80
  });
64
81
  const callerId = result.lastInsertRowid;
65
- const fnAst = parse(content, {
82
+ const fnAst = parse(fn.body, {
66
83
  ecmaVersion: 'latest',
67
84
  sourceType: 'module',
68
85
  locations: true,
69
86
  });
70
87
  const calls = [];
71
- walkSimple(fnAst, {
88
+ walkAncestor(fnAst, {
72
89
  CallExpression(node) {
73
- if (node.callee.type === 'Identifier' && node.callee.name) {
90
+ if (node.callee?.type === 'Identifier' && node.callee.name) {
74
91
  calls.push({ calleeName: node.callee.name });
75
92
  }
76
93
  }
@@ -86,7 +103,6 @@ export async function extractFromJS(filePath, content, fileId) {
86
103
  }
87
104
  log(`📌 Indexed function: ${fn.name} with ${calls.length} calls`);
88
105
  }
89
- // ✅ Mark as extracted using new processing_status column
90
106
  db.prepare(`
91
107
  UPDATE files
92
108
  SET processing_status = 'extracted'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"