scai 0.1.19 → 0.1.21

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/README.md CHANGED
@@ -1,150 +1,175 @@
1
1
  # ⚙️ scai — Smart Commit AI ✨
2
2
 
3
- > AI-powered CLI tools for smart commit messages, code comments, summaries, and changelogs — all powered by local models.
3
+ > AI-powered CLI tool for smart commit messages, code comments, summaries, test generation, and changelogs — all powered by local models.
4
4
 
5
- **scai** (Smart Commit AI) is a lightweight, privacy-focused CLI tool that uses local AI models (via [Ollama](https://ollama.com)) to help developers work faster and cleaner:
5
+ **scai** (Smart Commit AI) is a lightweight, privacy-focused CLI tool that helps developers work faster and cleaner:
6
6
 
7
7
  - 💬 Suggest high-quality Git commit messages
8
8
  - ✨ Automatically comment your code
9
9
  - 🧠 Summarize code files instantly in the terminal
10
+ - 🧪 Generate test files for your code (JavaScript/TypeScript)
10
11
  - 📝 Generate changelog entries based on Git diffs
11
- - 🔒 100% local — no API keys, no cloud, no telemetry
12
+ - 🔒 100% local — no API keys, no cloud, no telemetry
12
13
 
13
14
  ---
14
15
 
15
16
  ## 🚀 Features
16
17
 
17
- - ⚡️ Powered by local Ollama models like `llama3` and `codellama`
18
- - 🛠️ CLI built with Node.js + TypeScript
19
- - 🔒 No external services, full privacy by design
20
- - ✅ Global options for model and language selection
18
+ - ⚡️ Powered by local AI models (e.g., `llama3`, `codellama`)
19
+ - 🛠️ Built with Node.js and TypeScript
20
+ - 🔒 No external services or APIs required
21
+ - ✅ Global options to select model and programming language
21
22
 
22
23
  ---
23
24
 
24
25
  ## ❤️ Why Local AI?
25
26
 
26
- We believe your code — and your workflow — should stay **yours**. scai runs entirely on your machine using open-source models and tools.
27
+ At scai, we believe your code — and your workflow — should stay **yours**. That's why we prioritize **local models** and **privacy-first** development:
27
28
 
28
- Works entirely offline
29
- No API keys or cloud dependencies
30
- Backed by open-source models
31
- ✅ Designed for CLI-first developers
29
+ - **Local models**: scai runs entirely on your machine, with no need for cloud-based processing. We use open-source models like `llama3` and `codellama`, ensuring that your code stays private and secure.
30
+ - **Privacy by design**: We do not send your code to the cloud, store your data, or require API keys. Your data remains 100% local, ensuring full control over your project.
31
+ - **EU & Danish friendly**: As a Danish company, we are deeply committed to respecting European privacy regulations (GDPR). This means no data collection, no tracking, and full compliance with the strictest data protection laws.
32
+
33
+ By running everything locally, you can be sure that your sensitive code and workflows stay safe, private, and within your control.
34
+
35
+ ---
32
36
 
33
37
  ## 📦 Installation
34
38
 
35
- 1. **Install [Ollama](https://ollama.com)**
39
+ 1. **Install Ollama:**
36
40
  - On **Windows**: [Download Ollama](https://ollama.com/download)
37
- - On **macOS**:
38
- - Via Homebrew:
39
- ```bash
40
- brew install ollama
41
- ```
42
- - Or download directly from the website
41
+ - On **macOS**: via Homebrew (`brew install ollama`) or [download from Ollama](https://ollama.com)
43
42
 
44
- 2. **Install scai globally:**
43
+ 2. **Install scai globally:**
44
+ ```bash
45
+ npm install -g scai
46
+ ```
45
47
 
46
- ```bash
47
- npm install -g scai
48
- ```
48
+ 3. **Initialize the tool and models:**
49
+ ```bash
50
+ scai init
51
+ ```
49
52
 
50
- 3. **Initialize the tool and models:**
53
+ This will download required models and set up scai.
51
54
 
52
- ```bash
53
- scai init
54
- ```
55
+ ---
55
56
 
56
- This will:
57
- - Start the Ollama background server
58
- - Download required models (`llama3`, etc.)
57
+ ## ⚒️ Usage Examples
59
58
 
60
- ## 🧪 Usage Examples
59
+ ### 🔧 Git Commands
61
60
 
62
- ### 💬 Suggest a commit message
61
+ - **Check Git status (better than `git status`):**
62
+ ```bash
63
+ scai git status
64
+ ```
63
65
 
64
- ```bash
65
- git add .
66
- scai sugg
67
- ```
66
+ This is an enhanced version of `git status`. It does more than just show the status of your working directory:
68
67
 
69
- > Example output:
70
- ```
71
- feat(api): add error handling to user service
72
- ```
68
+ - **Check if your working directory is clean**: It tells you if there are any uncommitted changes.
69
+ - **Check your current branch**: It displays your current branch name.
70
+ - **Check if you're up to date with `origin/main`**: Unlike `git status`, which only shows local status, scai ensures you know if your branch is behind or ahead of the `origin/main` branch. It compares your local commit hash with the remote `origin/main` hash, giving you more comprehensive information.
73
71
 
74
- To commit automatically with the suggestion:
72
+ Example output:
73
+ ```
74
+ ✅ Git working directory is clean
75
+ ✅ Up to date with origin/main
76
+ ```
75
77
 
76
- ```bash
77
- scai sugg --commit
78
- ```
78
+ If you're behind `origin/main`, it will show:
79
+ ```
80
+ 🔄 Branch <your-branch-name> is not up to date with origin/main
81
+ ```
79
82
 
80
- ---
83
+ - **Suggest a commit message:**
84
+ ```bash
85
+ git add .
86
+ scai git sugg
87
+ ```
81
88
 
82
- ### ✨ Comment a code file
89
+ *Example output:*
90
+ ```
91
+ feat(api): add error handling to user service
92
+ ```
83
93
 
84
- ```bash
85
- scai comm <file>
86
- ```
94
+ To commit automatically with the suggestion:
95
+ ```bash
96
+ scai git sugg --commit
97
+ ```
87
98
 
88
- Adds clear, helpful comments to the code. Optional flag:
89
- - `-a, --apply` — Overwrite the original file with the commented version
99
+ ### 🛠️ Generate Code-Related Output (`gen` commands)
90
100
 
91
- ---
101
+ - **Comment a code file:**
102
+ ```bash
103
+ scai gen comm <file>
104
+ ```
92
105
 
93
- ### 🔍 Summarize a code file
106
+ Adds clear, helpful comments to the code. Optional flag:
107
+ - `-a, --apply` — Overwrite the original file with the commented version.
94
108
 
95
- ```bash
96
- scai summ <file>
97
- ```
109
+ - **Summarize a code file:**
110
+ ```bash
111
+ scai gen summ <file>
112
+ ```
98
113
 
99
- Prints a summary of what the code does directly to your terminal.
114
+ Prints a summary of what the code does directly in the terminal. You can also pipe file content:
115
+ ```bash
116
+ cat <file> | scai gen summ
117
+ ```
100
118
 
101
- To pipe input for summarization:
119
+ - **Generate tests for a code file:**
120
+ ```bash
121
+ scai gen tests <file>
122
+ ```
102
123
 
103
- ```bash
104
- cat <file> | scai summ
105
- ```
124
+ Creates a Jest test file for the specified JavaScript/TypeScript module.
106
125
 
107
- ---
126
+ - **Update the changelog:**
127
+ ```bash
128
+ scai gen changelog
129
+ ```
108
130
 
109
- ### 📝 Update the changelog
131
+ Analyzes the current Git diff and updates (or creates) a `CHANGELOG.md` file with relevant changes.
110
132
 
111
- ```bash
112
- scai changelog
113
- ```
133
+ ## ⚙️ Configuration
114
134
 
115
- Analyzes the current Git diff and updates (or creates) a `CHANGELOG.md` file with relevant public-facing changes.
135
+ scai stores your configuration locally in the `~/.scai/config.json` file. You can configure the model and programming language settings as follows:
116
136
 
117
- ---
137
+ - **Set the model:**
138
+ ```bash
139
+ scai set model <model>
140
+ ```
141
+
142
+ e.g., `scai set model codellama:7b`
143
+
144
+ - **Set the programming language:**
145
+ ```bash
146
+ scai set lang <lang>
147
+ ```
118
148
 
119
- ### 📦 Module Pipeline (Advanced Usage)
149
+ e.g., `scai set lang rust`
120
150
 
121
- For more advanced functionality, scai allows you to run custom pipelines with multiple modules to process code. You can specify multiple modules to be run, such as comment, summary, cleanup, and more.
151
+ - **Show current configuration:**
152
+ ```bash
153
+ scai config
154
+ ```
122
155
 
123
- ```bash
124
- scai <file> --modules comment,summary,cleanup
125
- ```
126
- Like with regular Unix commands you can pipe this to a file
156
+ You can also use global options `--model` and `--lang` with any command to override the settings:
157
+ ```bash
158
+ scai --model codellama:34b --lang ts git sugg
159
+ ```
127
160
 
128
- ```bash
129
- scai <file> --modules comment,summary > test.txt
130
- ```
131
- Or to both stdout and a file with tee fx.
132
- ```bash
133
- scai <file> --modules comment,summary | tee test.txt
134
- ```
161
+ ---
135
162
 
136
163
  ## 🔐 License & Fair Use
137
164
 
138
165
  **scai is free to use** for individuals, teams, and companies — including in commercial work.
139
166
 
140
167
  You may:
141
-
142
168
  - ✅ Use it internally in your projects
143
169
  - ✅ Use it at work or in commercial development
144
170
  - ✅ Share and recommend it
145
171
 
146
172
  But you may not:
147
-
148
173
  - ❌ Repackage or resell **scai** as a product or SaaS
149
174
  - ❌ Claim ownership of the tool
150
175
 
@@ -0,0 +1,66 @@
1
+ import { searchFiles } from "../db/fileIndex.js";
2
+ import { generate } from "../lib/generate.js";
3
+ import { summaryModule } from "../pipeline/modules/summaryModule.js";
4
+ export async function runAskCommand(query) {
5
+ if (!query) {
6
+ console.error('❌ Please provide a search query.\n👉 Usage: scai ask "keyword"');
7
+ return;
8
+ }
9
+ console.log(`🔍 Searching for: "${query}"\n`);
10
+ // Use vector-based search
11
+ const results = await searchFiles(query, 5); // Or 3 if you want fewer
12
+ if (results.length === 0) {
13
+ console.log('⚠️ No similar embeddings found. Asking the model for context instead...');
14
+ }
15
+ else {
16
+ console.log('📊 Closest files based on semantic similarity:');
17
+ results.forEach(file => {
18
+ console.log(`📄 Path: ${file?.path}`);
19
+ });
20
+ }
21
+ let allSummaries = '';
22
+ for (const file of results) {
23
+ try {
24
+ if (!file?.summary) {
25
+ console.warn(`⚠️ No summary available for file: ${file?.path}`);
26
+ continue;
27
+ }
28
+ console.log(`📝 Using cached summary for file: ${file?.path}`);
29
+ const summaryResponse = await summaryModule.run({ content: file?.summary ? file.summary : '', filepath: file?.path });
30
+ if (summaryResponse.summary) {
31
+ allSummaries += `\n${summaryResponse.summary}`;
32
+ }
33
+ }
34
+ catch (err) {
35
+ console.error(`❌ Error processing file: ${file?.path}`, err instanceof Error ? err.message : err);
36
+ }
37
+ }
38
+ if (allSummaries.trim()) {
39
+ console.log('🧠 Summaries found, sending them to the model for synthesis...');
40
+ try {
41
+ const input = {
42
+ content: `${query}\n\n${allSummaries}`,
43
+ filepath: '',
44
+ };
45
+ const modelResponse = await generate(input, 'llama3');
46
+ console.log(`\n📝 Model response:\n${modelResponse.content}`);
47
+ }
48
+ catch (err) {
49
+ console.error('❌ Model request failed:', err);
50
+ }
51
+ }
52
+ else {
53
+ console.log('⚠️ No summaries found. Asking the model for context only...');
54
+ try {
55
+ const input = {
56
+ content: query,
57
+ filepath: '',
58
+ };
59
+ const modelResponse = await generate(input, 'llama3');
60
+ console.log(`\n📝 Model response:\n${modelResponse.content}`);
61
+ }
62
+ catch (err) {
63
+ console.error('❌ Model request failed:', err);
64
+ }
65
+ }
66
+ }
@@ -4,10 +4,17 @@ import fs from 'fs/promises';
4
4
  import fsSync from 'fs';
5
5
  import os from 'os';
6
6
  import path from 'path';
7
- const MAX_FILES = 500;
8
- const DAEMON_DURATION_MINUTES = 10;
7
+ import { generateEmbedding } from '../lib/generateEmbedding.js';
8
+ import { IGNORED_EXTENSIONS } from '../config/IgnoredExtensions.js';
9
+ const MAX_FILES = 1000;
10
+ const DAEMON_DURATION_MINUTES = 25;
9
11
  const DAEMON_INTERVAL_MINUTES = 30;
10
12
  const PID_PATH = path.join(os.homedir(), '.scai/daemon.pid');
13
+ // Helper function to check if a file should be ignored
14
+ const shouldIgnoreFile = (filePath) => {
15
+ const ext = path.extname(filePath).toLowerCase();
16
+ return IGNORED_EXTENSIONS.includes(ext);
17
+ };
11
18
  export async function runDaemonBatch() {
12
19
  console.log('📥 Daemon batch: scanning for files to summarize...');
13
20
  const rows = db.prepare(`
@@ -15,19 +22,34 @@ export async function runDaemonBatch() {
15
22
  WHERE summary IS NULL OR summary = ''
16
23
  ORDER BY last_modified DESC
17
24
  LIMIT ?
18
- `).all(MAX_FILES);
25
+ `).all(MAX_FILES);
19
26
  if (rows.length === 0) {
20
27
  console.log('✅ No files left to summarize.');
21
28
  return;
22
29
  }
23
30
  for (const row of rows) {
31
+ if (shouldIgnoreFile(row.path)) {
32
+ console.log(`⚠️ Ignored file (unwanted extension): ${row.path}`);
33
+ continue;
34
+ }
24
35
  try {
25
36
  const content = await fs.readFile(row.path, 'utf-8');
26
37
  const result = await summaryModule.run({ content, filepath: row.path });
27
38
  const summary = result?.summary?.trim() ? result.summary : null;
28
- db.prepare(`UPDATE files SET summary = ?, indexed_at = datetime('now') WHERE path = ?`)
29
- .run(summary, row.path);
39
+ let embedding = null;
40
+ if (summary) {
41
+ const vector = await generateEmbedding(summary);
42
+ if (vector)
43
+ embedding = JSON.stringify(vector);
44
+ }
45
+ // Using named parameters for better readability and flexibility
46
+ db.prepare(`
47
+ UPDATE files
48
+ SET summary = @summary, embedding = @embedding, indexed_at = datetime('now')
49
+ WHERE path = @path
50
+ `).run({ summary, embedding, path: row.path });
30
51
  console.log(`📝 Summarized: ${row.path}`);
52
+ console.log(`🔢 Embedded: ${row.path}`);
31
53
  }
32
54
  catch (err) {
33
55
  console.warn(`⚠️ Failed: ${row.path}`, err instanceof Error ? err.message : err);
@@ -6,15 +6,37 @@ import { shouldIgnoreFile } from '../utils/shouldIgnoreFiles.js';
6
6
  import { detectFileType } from '../utils/detectFileType.js';
7
7
  import { runDaemonScheduler } from './DaemonCmd.js';
8
8
  import { IGNORED_FOLDER_GLOBS } from '../config/IgnoredPaths.js';
9
+ import { db } from '../db/client.js';
9
10
  const IGNORE = [
10
11
  '**/node_modules/**', '**/dist/**', '**/build/**',
11
12
  '**/coverage/**', '**/.git/**', '**/*.test.*'
12
13
  ];
13
14
  export async function runIndexCommand(targetDir = process.cwd(), options = {}) {
14
- console.log(`📂 Indexing files in: ${targetDir}`);
15
+ const resolvedDir = path.resolve(targetDir);
16
+ console.log(`📂 Indexing files in: ${resolvedDir}`);
15
17
  initSchema();
18
+ // 🧠 Check if another directory has already been indexed
19
+ const indexedPaths = db.prepare(`
20
+ SELECT DISTINCT path FROM files LIMIT 100
21
+ `).all();
22
+ const knownRoot = indexedPaths.length > 0
23
+ ? path.dirname(indexedPaths[0].path)
24
+ : null;
25
+ if (knownRoot && !resolvedDir.startsWith(knownRoot) && !options.force) {
26
+ console.warn(`⚠️ You're indexing a different folder than before:
27
+ - Previously: ${knownRoot}
28
+ - Now: ${resolvedDir}
29
+
30
+ This will add more files into the existing index and may reduce accuracy or performance.
31
+
32
+ Use --force to continue, or consider clearing the index:
33
+ scai reset-db
34
+
35
+ Aborting.`);
36
+ process.exit(1);
37
+ }
16
38
  const files = await fg('**/*.*', {
17
- cwd: targetDir,
39
+ cwd: resolvedDir,
18
40
  ignore: IGNORED_FOLDER_GLOBS,
19
41
  absolute: true,
20
42
  });
@@ -28,7 +50,7 @@ export async function runIndexCommand(targetDir = process.cwd(), options = {}) {
28
50
  indexFile(file, null, type); // empty summary for now
29
51
  const ext = path.extname(file);
30
52
  countByExt[ext] = (countByExt[ext] || 0) + 1;
31
- console.log(`📄 Indexed: ${path.relative(targetDir, file)}`);
53
+ console.log(`📄 Indexed: ${path.relative(resolvedDir, file)}`);
32
54
  count++;
33
55
  }
34
56
  catch (err) {
@@ -39,6 +61,6 @@ export async function runIndexCommand(targetDir = process.cwd(), options = {}) {
39
61
  console.log(`✅ Done. Indexed ${count} files.`);
40
62
  if (options.detached) {
41
63
  console.log('🚀 Starting summarizer daemon in background mode...');
42
- runDaemonScheduler(); // Infinite loop every 30 min
64
+ runDaemonScheduler();
43
65
  }
44
66
  }
@@ -1,4 +1,4 @@
1
- import { searchFiles } from '../db/fileIndex.js';
1
+ import { queryFiles } from '../db/fileIndex.js';
2
2
  import path from 'path';
3
3
  export async function runQueryCommand(query) {
4
4
  if (!query) {
@@ -6,7 +6,7 @@ export async function runQueryCommand(query) {
6
6
  return;
7
7
  }
8
8
  console.log(`🔍 Searching for: "${query}"\n`);
9
- const results = searchFiles(query);
9
+ const results = queryFiles(query);
10
10
  if (results.length === 0) {
11
11
  console.log('⚠️ No matching files found.');
12
12
  return;
@@ -3,23 +3,44 @@ export const IGNORED_EXTENSIONS = [
3
3
  // 🖼 Media
4
4
  '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico',
5
5
  '.mp4', '.mp3', '.mov', '.avi', '.mkv', '.flv', '.wav', '.flac',
6
+ '.aac', '.m4a', '.wma', '.3gp', '.webm', '.ogg', '.aiff', '.au',
6
7
  // 📦 Archives & install packages
7
8
  '.zip', '.tar', '.gz', '.bz2', '.xz', '.rar', '.7z',
8
- '.jar', '.war', '.ear', // Java packaging
9
- '.deb', '.rpm', '.pkg', '.msi', '.dmg', '.cab', '.xz',
9
+ '.jar', '.war', '.ear', '.deb', '.rpm', '.pkg', '.msi', '.dmg', '.cab', '.apk',
10
+ '.tar.gz', '.tar.bz2', '.tar.xz', '.tar.lzma', '.tar.zst',
10
11
  // 🧱 Binaries & executables
11
12
  '.exe', '.dll', '.bin', '.so', '.dylib', '.a', '.lib',
12
- '.iso', '.img', '.elf', '.o', '.obj',
13
+ '.iso', '.img', '.elf', '.o', '.obj', '.msm', '.vbs', '.jscript',
14
+ '.cmd', '.bat', '.ps1', '.sh', '.bash', '.run',
13
15
  // 🧪 Runtime / build / cache
14
- '.log', '.lock', '.tmp', '.map',
16
+ '.log', '.tmp', '.map',
15
17
  '.db', '.sqlite', '.pkl', '.sav', '.rdb', '.ldb',
16
- '.pyc', '.class', '.tsbuildinfo', '.coverage',
18
+ '.pyc', '.class', '.tsbuildinfo', '.coverage', '.eslintcache',
19
+ '.yarn', '.webpack', '.babel', '.compilercache',
17
20
  // 🔤 Fonts & styles
18
21
  '.woff', '.woff2', '.ttf', '.eot', '.otf', '.css.map',
22
+ '.scss', '.sass', '.less', '.styl',
19
23
  // 🔐 Certs, keys, credentials
20
- '.crt', '.key', '.pem', '.pub', '.asc', '.gpg',
24
+ '.crt', '.key', '.pem', '.pub', '.asc', '.gpg', '.p12', '.csr', '.der', '.pfx',
21
25
  // ♻️ Backups / temp
22
- '.bak', '.old', '.swp', '.swo', '.tmp', '.orig',
23
- // 🌐 Misc
24
- '.torrent', '.DS_Store', '.env.local', '.env.production', '.env.development',
26
+ '.bak', '.old', '.swp', '.swo', '.orig',
27
+ '.sublime-workspace', '.sublime-project', '.db-shm', '.db-wal',
28
+ // 🌐 System/config folders (still ignored by path, not extension)
29
+ '.DS_Store', '.bundle', '.npmrc',
30
+ // 🗺️ GIS / Geospatial
31
+ '.shp', '.shx', '.dbf', '.prj', '.qix', '.sbn', '.sbx', '.shp.xml', '.cpg', '.gpkg', '.mif', '.mid',
32
+ // 📊 Enterprise BI / Reporting
33
+ '.pbix', '.rdl', '.rpt', '.bqy', '.iqy',
34
+ // 🧪 ETL / DWH / Modeling
35
+ '.abf', '.dtsx', '.bim', '.xmi',
36
+ // 🏗️ CAD / Engineering
37
+ '.dwg', '.dxf', '.step', '.stp', '.sldprt', '.sldasm',
38
+ '.iges', '.igs', '.3ds', '.fbx',
39
+ // 🧾 Forms / Print / Publishing
40
+ '.xps', '.afpub', '.pub', '.indd', '.qxd', '.frm', '.frx', '.frl',
41
+ // 💰 ERP / Finance / Legacy DB
42
+ '.mbd', '.fdb', '.nav', '.accdb', '.mdb', '.gdb',
43
+ '.sap', '.sappkg', '.qbw', '.qbb',
44
+ // 🔒 Lock files (but NOT lock *configs*)
45
+ '.lck', '.lockfile', '.db-lock', '.pid', '.socket',
25
46
  ];
@@ -18,4 +18,28 @@ export const IGNORED_FOLDER_GLOBS = [
18
18
  '**/.output/**',
19
19
  '**/tmp/**',
20
20
  '**/*.test.*',
21
+ '**/.m2/**',
22
+ '**/.gradle/**',
23
+ '**/.tox/**',
24
+ '**/.nox/**',
25
+ '**/.hypothesis/**',
26
+ '**/.npm/**',
27
+ '**/.yarn/**',
28
+ '**/*.o',
29
+ '**/*.out',
30
+ '**/*.exe',
31
+ '**/*.dll',
32
+ '**/.cache/**',
33
+ '**/.pylint.d/**',
34
+ '**/.eslintcache/**',
35
+ '**/.cache-loader/**',
36
+ '**/.serverless/**',
37
+ '**/.docker/**',
38
+ '**/.sublime-workspace',
39
+ '**/.sublime-project',
40
+ '**/*.log',
41
+ '**/npm-debug.log',
42
+ '**/yarn-error.log',
43
+ '**/debug.log',
44
+ '**/Dockerfile',
21
45
  ];
package/dist/config.js ADDED
@@ -0,0 +1,68 @@
1
+ import fs from 'fs';
2
+ import { CONFIG_PATH, SCAI_HOME, INDEX_DIR } from './constants.js'; // Correctly import INDEX_DIR from constants
3
+ // Default configuration values
4
+ const defaultConfig = {
5
+ model: 'llama3',
6
+ language: 'ts',
7
+ indexDir: INDEX_DIR, // Default index directory from constants
8
+ };
9
+ // Function to ensure the configuration directory exists
10
+ function ensureConfigDir() {
11
+ if (!fs.existsSync(SCAI_HOME)) {
12
+ fs.mkdirSync(SCAI_HOME, { recursive: true });
13
+ }
14
+ }
15
+ // Function to read the configuration file
16
+ function readConfig() {
17
+ try {
18
+ const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
19
+ return { ...defaultConfig, ...JSON.parse(content) };
20
+ }
21
+ catch {
22
+ return defaultConfig; // Return default config if read fails
23
+ }
24
+ }
25
+ // Function to write the configuration to the config file
26
+ function writeConfig(newConfig) {
27
+ ensureConfigDir();
28
+ const current = readConfig();
29
+ const merged = { ...current, ...newConfig };
30
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(merged, null, 2));
31
+ }
32
+ export const Config = {
33
+ // Get the current model from the config
34
+ getModel() {
35
+ return readConfig().model;
36
+ },
37
+ // Set a new model in the config
38
+ setModel(model) {
39
+ writeConfig({ model });
40
+ console.log(`📦 Model set to: ${model}`);
41
+ },
42
+ // Get the current language from the config
43
+ getLanguage() {
44
+ return readConfig().language;
45
+ },
46
+ // Set a new language in the config
47
+ setLanguage(language) {
48
+ writeConfig({ language });
49
+ console.log(`🗣️ Language set to: ${language}`);
50
+ },
51
+ // Get the index directory from the config
52
+ getIndexDir() {
53
+ return readConfig().indexDir;
54
+ },
55
+ // Set a new index directory in the config
56
+ setIndexDir(indexDir) {
57
+ writeConfig({ indexDir });
58
+ console.log(`📁 Index directory set to: ${indexDir}`);
59
+ },
60
+ // Show the current configuration
61
+ show() {
62
+ const cfg = readConfig();
63
+ console.log(`🔧 Current configuration:`);
64
+ console.log(` Model : ${cfg.model}`);
65
+ console.log(` Language : ${cfg.language}`);
66
+ console.log(` Index dir : ${cfg.indexDir}`);
67
+ }
68
+ };
@@ -0,0 +1,19 @@
1
+ import os from 'os';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ export const SCAI_HOME = path.join(os.homedir(), '.scai');
5
+ export const DB_PATH = path.join(SCAI_HOME, 'db.sqlite');
6
+ export const PID_PATH = path.join(SCAI_HOME, 'daemon.pid');
7
+ export const CONFIG_PATH = path.join(SCAI_HOME, 'config.json');
8
+ // Function to read config and get the indexDir on-demand
9
+ export function getIndexDir() {
10
+ try {
11
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
12
+ return config.indexDir || path.join(os.homedir(), 'defaultIndex'); // Default if not set
13
+ }
14
+ catch (e) {
15
+ return path.join(os.homedir(), 'defaultIndex'); // Fallback if no config file
16
+ }
17
+ }
18
+ // On-demand index directory retrieval
19
+ export const INDEX_DIR = getIndexDir();
package/dist/db/client.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import Database from 'better-sqlite3';
2
- import path from 'path';
3
2
  import fs from 'fs';
4
- const DB_PATH = path.resolve(process.cwd(), '.scai/db.sqlite');
5
- // Ensure directory exists
6
- fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
3
+ import { DB_PATH, SCAI_HOME } from '../constants.js';
4
+ fs.mkdirSync(SCAI_HOME, { recursive: true });
7
5
  export const db = new Database(DB_PATH);
@@ -1,51 +1,114 @@
1
+ // File: src/db/fileIndex.ts
1
2
  import { db } from './client.js';
2
3
  import fs from 'fs';
4
+ import { generateEmbedding } from '../lib/generateEmbedding.js';
5
+ import * as sqlTemplates from './sqlTemplates.js'; // Import the SQL templates
3
6
  export function indexFile(filePath, summary, type) {
4
7
  const stats = fs.statSync(filePath);
5
8
  const lastModified = stats.mtime.toISOString();
6
9
  // 1) INSERT new rows (only when path not present)
7
- const insertStmt = db.prepare(`
8
- INSERT OR IGNORE INTO files
9
- (path, summary, type, indexed_at, last_modified)
10
- VALUES (?, ?, ?, datetime('now'), ?)
11
- `);
12
- insertStmt.run(filePath, summary, type, lastModified);
10
+ const insertStmt = db.prepare(sqlTemplates.insertFileTemplate);
11
+ insertStmt.run({ path: filePath, summary, type, lastModified });
13
12
  // 2) UPDATE metadata if file already existed and changed
14
- const updateStmt = db.prepare(`
15
- UPDATE files
16
- SET type = ?,
17
- last_modified = ?,
18
- indexed_at = datetime('now')
19
- WHERE path = ?
20
- AND last_modified != ?
21
- `);
22
- updateStmt.run(type, lastModified, filePath, lastModified);
13
+ const updateStmt = db.prepare(sqlTemplates.updateFileTemplate);
14
+ updateStmt.run({ path: filePath, type, lastModified });
23
15
  // Step 1: Delete from FTS where the path matches
24
- db.prepare(`
25
- DELETE FROM files_fts
26
- WHERE rowid = (SELECT id FROM files WHERE path = ?)
27
- `).run(filePath);
16
+ db.prepare(sqlTemplates.deleteFromFtsTemplate).run({ path: filePath });
28
17
  // Step 2: Insert into FTS with the same id
29
- db.prepare(`
30
- INSERT INTO files_fts(rowid, path, summary)
31
- VALUES((SELECT id FROM files WHERE path = ?), ?, ?)
32
- `).run(filePath, filePath, summary);
18
+ db.prepare(sqlTemplates.insertIntoFtsTemplate).run({ path: filePath, summary });
33
19
  }
34
- export function searchFiles(query, limit = 10) {
35
- // Use FTS5 MATCH for relevance-ranked results
36
- const stmt = db.prepare(`
20
+ export function queryFiles(query, limit = 10) {
21
+ // Sanitize the query by removing or escaping special characters
22
+ const safeQuery = query
23
+ .trim()
24
+ .split(/\s+/)
25
+ .map(token => {
26
+ token = token
27
+ .replace(/[?*\\"]/g, '') // Remove question marks, asterisks, backslashes, and double quotes
28
+ .replace(/'/g, "''"); // Escape single quotes for SQL safety
29
+ // For multi-word queries, wrap the token in quotes for exact phrase matching
30
+ if (token.includes(' ')) {
31
+ return `"${token}"`; // Exact phrase match for multi-word tokens
32
+ }
33
+ return `${token}*`; // Prefix match for single tokens
34
+ })
35
+ .join(' OR ');
36
+ // Log the constructed query for debugging purposes
37
+ console.log(`Executing search query: ${safeQuery}`);
38
+ // Execute the query with safeQuery and limit as parameters
39
+ const sql = `
37
40
  SELECT f.path, f.summary, f.type, f.last_modified, f.indexed_at,
38
41
  bm25(files_fts) AS rank
39
42
  FROM files_fts
40
43
  JOIN files f ON files_fts.rowid = f.id
41
- WHERE files_fts MATCH ?
44
+ WHERE files_fts MATCH :query
42
45
  ORDER BY rank
43
- LIMIT ?
44
- `);
45
- const matchQuery = query
46
+ LIMIT :limit
47
+ `;
48
+ const results = db.prepare(sql).all({ query: safeQuery, limit });
49
+ return results;
50
+ }
51
+ export function cosineSimilarity(a, b) {
52
+ const dot = a.reduce((sum, ai, i) => sum + ai * b[i], 0);
53
+ const magA = Math.sqrt(a.reduce((sum, ai) => sum + ai * ai, 0));
54
+ const magB = Math.sqrt(b.reduce((sum, bi) => sum + bi * bi, 0));
55
+ return dot / (magA * magB);
56
+ }
57
+ export async function searchFiles(query, topK = 5) {
58
+ // Generate the query embedding
59
+ const embedding = await generateEmbedding(query);
60
+ if (!embedding)
61
+ return [];
62
+ // Sanitize the query by removing or escaping special characters
63
+ const safeQuery = query
46
64
  .trim()
47
65
  .split(/\s+/)
48
- .map(token => `${token}*`) // prefix search
49
- .join(' ');
50
- return stmt.all(matchQuery, limit);
66
+ .map(token => {
67
+ token = token
68
+ .replace(/[?*\\"]/g, '') // Remove question marks, asterisks, backslashes, and double quotes
69
+ .replace(/'/g, "''"); // Escape single quotes for SQL safety
70
+ // For multi-word queries, wrap the token in quotes for exact phrase matching
71
+ if (token.includes(' ')) {
72
+ return `"${token}"`; // Exact phrase match for multi-word tokens
73
+ }
74
+ return `${token}*`; // Prefix match for single tokens
75
+ })
76
+ .join(' OR ');
77
+ // Log the constructed query for debugging purposes
78
+ console.log(`Executing search query: ${safeQuery}`);
79
+ // Fetch BM25 scores from the FTS using the safeQuery string directly
80
+ const ftsResults = db.prepare(sqlTemplates.fetchBm25ScoresTemplate).all({ query: safeQuery });
81
+ const bm25Min = Math.min(...ftsResults.map(r => r.bm25Score));
82
+ const bm25Max = Math.max(...ftsResults.map(r => r.bm25Score));
83
+ // Calculate final score combining BM25 and cosine similarity
84
+ const scored = ftsResults.map(result => {
85
+ try {
86
+ // Fetch embedding for each file from the `files` table
87
+ const embResult = db.prepare(sqlTemplates.fetchEmbeddingTemplate).get({ path: result.path });
88
+ // Check if embedding exists and has the correct structure
89
+ if (!embResult || typeof embResult.embedding !== 'string')
90
+ return null;
91
+ // Parse the embedding
92
+ const vector = JSON.parse(embResult.embedding);
93
+ const sim = cosineSimilarity(embedding, vector);
94
+ // Normalize BM25 scores
95
+ const normalizedBm25 = 1 - ((result.bm25Score - bm25Min) / (bm25Max - bm25Min + 1e-5));
96
+ const normalizedSim = sim; // cosineSimilarity is already 0–1
97
+ const finalScore = 0.7 * normalizedSim + 0.3 * normalizedBm25;
98
+ return {
99
+ path: result.path,
100
+ summary: result.summary,
101
+ score: finalScore,
102
+ sim: normalizedSim,
103
+ bm25: normalizedBm25
104
+ };
105
+ }
106
+ catch (err) {
107
+ console.error(`Error processing embedding for file: ${result.path}`, err);
108
+ return null;
109
+ }
110
+ }).filter(Boolean)
111
+ .sort((a, b) => b.score - a.score)
112
+ .slice(0, topK);
113
+ return scored;
51
114
  }
package/dist/db/schema.js CHANGED
@@ -7,7 +7,8 @@ export function initSchema() {
7
7
  summary TEXT,
8
8
  type TEXT,
9
9
  indexed_at TEXT,
10
- last_modified TEXT
10
+ last_modified TEXT,
11
+ embedding TEXT
11
12
  );
12
13
 
13
14
  -- FTS5 table for fast full‑text search of summaries and paths
@@ -0,0 +1,37 @@
1
+ // Template for inserting or ignoring new file entries
2
+ export const insertFileTemplate = `
3
+ INSERT OR IGNORE INTO files
4
+ (path, summary, type, indexed_at, last_modified)
5
+ VALUES (:path, :summary, :type, datetime('now'), :lastModified)
6
+ `;
7
+ // Template for updating file metadata if it has changed
8
+ export const updateFileTemplate = `
9
+ UPDATE files
10
+ SET type = :type,
11
+ last_modified = :lastModified,
12
+ indexed_at = datetime('now')
13
+ WHERE path = :path
14
+ AND last_modified != :lastModified
15
+ `;
16
+ // Template for deleting a file from FTS
17
+ export const deleteFromFtsTemplate = `
18
+ DELETE FROM files_fts
19
+ WHERE rowid = (SELECT id FROM files WHERE path = :path)
20
+ `;
21
+ // Template for inserting a file into FTS with its ID
22
+ export const insertIntoFtsTemplate = `
23
+ INSERT INTO files_fts(rowid, path, summary)
24
+ VALUES((SELECT id FROM files WHERE path = :path), :path, :summary)
25
+ `;
26
+ // Template for fetching BM25 scores from FTS
27
+ export const fetchBm25ScoresTemplate = `
28
+ SELECT f.path, f.summary, f.type, bm25(files_fts) AS bm25Score
29
+ FROM files_fts
30
+ JOIN files f ON files_fts.rowid = f.id
31
+ WHERE files_fts MATCH :query
32
+ LIMIT 50
33
+ `;
34
+ // Template for fetching embedding for a specific file
35
+ export const fetchEmbeddingTemplate = `
36
+ SELECT embedding FROM files WHERE path = :path
37
+ `;
package/dist/index.js CHANGED
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
- // Importing package.json for version
3
+ import path from "path";
4
+ import { Config } from './config.js';
4
5
  import { createRequire } from 'module';
5
6
  const require = createRequire(import.meta.url);
6
7
  const { version } = require('../package.json');
7
- // Importing commands
8
+ // 🧠 Commands
8
9
  import { checkEnv } from "./commands/EnvCmd.js";
9
10
  import { checkGit } from "./commands/GitCmd.js";
10
11
  import { suggestCommitMessage } from "./commands/CommitSuggesterCmd.js";
11
12
  import { handleRefactor } from "./commands/RefactorCmd.js";
12
13
  import { generateTests } from "./commands/TestGenCmd.js";
13
14
  import { bootstrap } from './modelSetup.js';
14
- import { ModelConfig } from './config/ModelConfig.js';
15
15
  import { summarizeFile } from "./commands/SummaryCmd.js";
16
16
  import { handleChangelogUpdate } from './commands/ChangeLogUpdateCmd.js';
17
17
  import { runModulePipelineFromCLI } from './commands/ModulePipelineCmd.js';
@@ -20,12 +20,13 @@ import { resetDatabase } from './commands/ResetDbCmd.js';
20
20
  import { runQueryCommand } from './commands/QueryCmd.js';
21
21
  import { runDaemonBatch } from './commands/DaemonCmd.js';
22
22
  import { runStopDaemonCommand } from "./commands/StopDaemonCmd.js";
23
- // Create the CLI instance
23
+ import { runAskCommand } from './commands/AskCmd.js';
24
+ // 🎛️ CLI Setup
24
25
  const cmd = new Command('scai')
25
26
  .version(version)
26
27
  .option('--model <model>', 'Set the model to use (e.g., codellama:34b)')
27
28
  .option('--lang <lang>', 'Set the target language (ts, java, rust)');
28
- // Define CLI commands
29
+ // 🚀 Init command
29
30
  cmd
30
31
  .command('init')
31
32
  .description('Initialize the model and download required models')
@@ -33,44 +34,96 @@ cmd
33
34
  await bootstrap();
34
35
  console.log('✅ Model initialization completed!');
35
36
  });
36
- cmd
37
+ // 🔧 Group: Git-related commands
38
+ const git = cmd.command('git').description('Git utilities');
39
+ git
40
+ .command('status')
41
+ .description('Check Git status')
42
+ .action(checkGit);
43
+ git
37
44
  .command('sugg')
38
45
  .description('Suggest a commit message from staged changes')
39
46
  .option('-c, --commit', 'Automatically commit with suggested message')
40
47
  .action(suggestCommitMessage);
41
- cmd
48
+ // 🛠️ Group: `gen` commands for content generation
49
+ const gen = cmd.command('gen').description('Generate code-related output');
50
+ gen
42
51
  .command('comm <file>')
43
52
  .description('Write comments for the given file')
44
53
  .option('-a, --apply', 'Apply the refactored version to the original file')
45
54
  .action((file, options) => handleRefactor(file, options));
46
- cmd
55
+ gen
47
56
  .command('changelog')
48
57
  .description('Update or create the CHANGELOG.md based on current Git diff')
49
58
  .action(async () => {
50
59
  await handleChangelogUpdate();
51
60
  });
52
- cmd
61
+ gen
53
62
  .command('summ [file]')
54
63
  .description('Print a summary of the given file to the terminal')
55
64
  .action((file) => summarizeFile(file));
65
+ gen
66
+ .command('tests <file>')
67
+ .description('Generate a Jest test file for the specified JS/TS module')
68
+ .action((file) => generateTests(file));
69
+ // 🔍 Indexing
56
70
  cmd
57
- .command('git')
58
- .description('Check Git status')
59
- .action(checkGit);
71
+ .command('index [targetDir]')
72
+ .description('Index supported files in the given directory (or current folder if none)')
73
+ .option('-d, --detached', 'Run summarizer daemon after indexing')
74
+ .option('--force', 'Force indexing even if another folder has already been indexed')
75
+ .action((targetDir, options) => {
76
+ const resolvedDir = targetDir ? path.resolve(targetDir) : process.cwd();
77
+ runIndexCommand(resolvedDir, { detached: options.detached, force: options.force });
78
+ });
79
+ // ⚙️ Group: Configuration settings
80
+ const set = cmd.command('set').description('Set configuration values');
81
+ set
82
+ .command('model <model>')
83
+ .description('Set the model to use')
84
+ .action((model) => {
85
+ Config.setModel(model);
86
+ Config.show();
87
+ });
88
+ set
89
+ .command('lang <lang>')
90
+ .description('Set the programming language')
91
+ .action((lang) => {
92
+ Config.setLanguage(lang);
93
+ Config.show();
94
+ });
95
+ set
96
+ .command('index-dir <dir>')
97
+ .description('Set the path to the indexed directory')
98
+ .action((dir) => {
99
+ Config.setIndexDir(path.resolve(dir));
100
+ Config.show();
101
+ });
102
+ // 🧪 Diagnostics and info
60
103
  cmd
61
104
  .command('env')
62
105
  .description('Check environment variables')
63
106
  .action(checkEnv);
64
- cmd
65
- .command('gen-tests <file>')
66
- .description('Generate a Jest test file for the specified JS/TS module')
67
- .action((file) => generateTests(file));
68
107
  cmd
69
108
  .command('config')
70
109
  .description('Show the currently active model and language settings')
71
110
  .action(() => {
72
- ModelConfig.logCurrentConfig();
111
+ Config.show();
73
112
  });
113
+ // 🧠 Query and assistant
114
+ cmd
115
+ .command('query <query>')
116
+ .description('Search indexed files by keyword')
117
+ .action(runQueryCommand);
118
+ cmd
119
+ .command('ask')
120
+ .description('Ask a question using file summaries and a local model')
121
+ .argument('<question...>', 'The question to ask')
122
+ .action((question) => {
123
+ const q = question.join(' ');
124
+ runAskCommand(q);
125
+ });
126
+ // 🛠️ Background tasks and maintenance
74
127
  cmd
75
128
  .command('daemon')
76
129
  .description('Run background summarization of indexed files')
@@ -79,22 +132,11 @@ cmd
79
132
  .command('stop-daemon')
80
133
  .description('Stop the background summarizer daemon')
81
134
  .action(runStopDaemonCommand);
82
- cmd
83
- .command('index [targetDir]')
84
- .description('Index supported files in the given directory (or current folder if none)')
85
- .option('-d, --detached', 'Run summarizer daemon after indexing')
86
- .action((targetDir, options) => {
87
- runIndexCommand(targetDir, { detached: options.detached });
88
- });
89
- cmd
90
- .command('query <query>')
91
- .description('Search indexed files by keyword')
92
- .action(runQueryCommand);
93
135
  cmd
94
136
  .command('reset-db')
95
137
  .description('Delete and reset the SQLite database')
96
138
  .action(() => resetDatabase());
97
- // Default
139
+ // 🧬 Fallback: Pipeline mode
98
140
  cmd
99
141
  .arguments('<file>')
100
142
  .option('-m, --modules <modules>', 'Comma-separated list of modules to run (e.g., comments,cleanup,summary)')
@@ -105,11 +147,11 @@ cmd
105
147
  }
106
148
  runModulePipelineFromCLI(file, options);
107
149
  });
108
- // ✅ Now that commands are defined, parse args
150
+ // ✅ Parse CLI args
109
151
  cmd.parse(process.argv);
110
- // Apply global options after parsing
152
+ // 🔁 Apply global options post-parse
111
153
  const opts = cmd.opts();
112
154
  if (opts.model)
113
- ModelConfig.setModel(opts.model);
155
+ Config.setModel(opts.model);
114
156
  if (opts.lang)
115
- ModelConfig.setLanguage(opts.lang);
157
+ Config.setLanguage(opts.lang);
@@ -0,0 +1,22 @@
1
+ export async function generateEmbedding(text) {
2
+ try {
3
+ const res = await fetch('http://localhost:11434/api/embeddings', {
4
+ method: 'POST',
5
+ headers: { 'Content-Type': 'application/json' },
6
+ body: JSON.stringify({
7
+ model: 'mistral', // or 'llama3' — whatever works best
8
+ prompt: text,
9
+ }),
10
+ });
11
+ if (!res.ok) {
12
+ console.error('❌ Failed to generate embedding:', await res.text());
13
+ return null;
14
+ }
15
+ const data = await res.json();
16
+ return data.embedding;
17
+ }
18
+ catch (err) {
19
+ console.error('❌ Embedding error:', err instanceof Error ? err.message : err);
20
+ return null;
21
+ }
22
+ }
@@ -1,10 +1,10 @@
1
- import { ModelConfig } from '../../config/ModelConfig.js';
1
+ import { Config } from '../../config.js';
2
2
  import { generate } from '../../lib/generate.js';
3
3
  export const changelogModule = {
4
4
  name: 'changelogModule',
5
5
  description: 'Generates changelog entry based on Git diff',
6
6
  async run(input) {
7
- const model = ModelConfig.getModel();
7
+ const model = Config.getModel();
8
8
  const prompt = `
9
9
  You're an experienced changelog writer. Based on this Git diff, write a markdown bullet-point entry suitable for CHANGELOG.md:
10
10
 
@@ -1,11 +1,11 @@
1
- import { ModelConfig } from '../../config/ModelConfig.js';
1
+ import { Config } from '../../config.js';
2
2
  import { generate } from '../../lib/generate.js';
3
3
  export const addCommentsModule = {
4
4
  name: 'addComments',
5
5
  description: 'Adds meaningful // comments to each block of code',
6
6
  async run(input) {
7
- const model = ModelConfig.getModel();
8
- const lang = ModelConfig.getLanguage();
7
+ const model = Config.getModel();
8
+ const lang = Config.getLanguage();
9
9
  const prompt = `
10
10
  You are a senior ${lang.toUpperCase()} engineer reviewing source code.
11
11
 
@@ -1,10 +1,10 @@
1
1
  import { generate } from '../../lib/generate.js';
2
- import { ModelConfig } from '../../config/ModelConfig.js';
2
+ import { Config } from '../../config.js';
3
3
  export const commitSuggesterModule = {
4
4
  name: 'commitSuggester',
5
5
  description: 'Suggests conventional commit messages from Git diff',
6
6
  async run({ content }) {
7
- const model = ModelConfig.getModel();
7
+ const model = Config.getModel();
8
8
  const prompt = `
9
9
  Suggest ALWAYS 3 concise, conventional Git commit messages based on the input code diff.
10
10
  Use this format ONLY:
@@ -1,14 +1,14 @@
1
1
  // src/pipeline/modules/generateTestsModule.ts
2
2
  import fs from 'fs/promises';
3
3
  import path from 'path';
4
- import { ModelConfig } from '../../config/ModelConfig.js';
4
+ import { Config } from '../../config.js';
5
5
  import { generate } from '../../lib/generate.js';
6
6
  export const generateTestsModule = {
7
7
  name: 'generateTests',
8
8
  description: 'Generate a Jest test file for the class/module',
9
9
  async run({ content, filepath }) {
10
- const model = ModelConfig.getModel();
11
- const lang = ModelConfig.getLanguage();
10
+ const model = Config.getModel();
11
+ const lang = Config.getLanguage();
12
12
  if (!filepath)
13
13
  throw new Error('Missing filepath in pipeline context');
14
14
  const prompt = `
@@ -1,11 +1,11 @@
1
- import { ModelConfig } from '../../config/ModelConfig.js';
1
+ import { Config } from '../../config.js';
2
2
  import { generate } from '../../lib/generate.js';
3
3
  export const refactorModule = {
4
4
  name: 'refactor',
5
5
  description: 'Break code into small, clean functions',
6
6
  async run(input) {
7
- const model = ModelConfig.getModel();
8
- const lang = ModelConfig.getLanguage();
7
+ const model = Config.getModel();
8
+ const lang = Config.getLanguage();
9
9
  const prompt = `
10
10
  You are a senior ${lang.toUpperCase()} engineer.
11
11
 
@@ -1,11 +1,11 @@
1
- import { ModelConfig } from '../../config/ModelConfig.js';
1
+ import { Config } from '../../config.js';
2
2
  import { generate } from '../../lib/generate.js';
3
3
  import path from 'path';
4
4
  export const summaryModule = {
5
5
  name: 'summary',
6
6
  description: 'Generates a general summary of any file content.',
7
7
  run: async ({ content, filepath }) => {
8
- const model = ModelConfig.getModel();
8
+ const model = Config.getModel();
9
9
  const ext = filepath ? path.extname(filepath).toLowerCase() : '';
10
10
  const filename = filepath ? path.basename(filepath) : '';
11
11
  // More neutral prompt for general-purpose content
@@ -36,7 +36,6 @@ ${content}
36
36
  }
37
37
  else {
38
38
  console.warn('⚠️ No summary generated.');
39
- response.summary = '⚠️ No summary generated.';
40
39
  }
41
40
  return response;
42
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"
@@ -1,23 +0,0 @@
1
- export class ModelConfig {
2
- static setModel(model) {
3
- this.model = model;
4
- console.log(`📦 Model set to: ${model}`);
5
- }
6
- static getModel() {
7
- return this.model;
8
- }
9
- static setLanguage(lang) {
10
- this.language = lang;
11
- console.log(`🗣️ Language set to: ${lang}`);
12
- }
13
- static getLanguage() {
14
- return this.language;
15
- }
16
- static logCurrentConfig() {
17
- console.log(`🔧 Current configuration:`);
18
- console.log(` Model : ${this.model}`);
19
- console.log(` Language: ${this.language}`);
20
- }
21
- }
22
- ModelConfig.model = 'codellama:7b';
23
- ModelConfig.language = 'ts';
File without changes