scai 0.1.122 → 0.1.123

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,10 +1,34 @@
1
1
  # ⚙️ SCAI — Source Code AI 🌿
2
2
 
3
- > **AI-powered CLI for local code analysis, commit message suggestions, and natural-language queries.** 100% local, private, GDPR-friendly, made in Denmark/EU with ❤️.
3
+ > **AI-powered CLI for local code analysis, commit message suggestions, and natural-language queries.**
4
+ > **100% local • No token cost • Private by design • GDPR-friendly** — made in Denmark/EU with ❤️.
4
5
 
5
- SCAI is your AI coding companion in the terminal. Focus on coding while Scai helps you understand, comment, and analyze your code. **Local Model Note:** Using local LLMs ensures privacy and offline usage, and **NO token cost, but capabilities are more limited compared to cloud-hosted AI.**
6
+ 🔗 **Website:** [https://scai.dk](https://scai.dk)
6
7
 
7
- > ⚠️ **Alpha Version Notice:** If you have previously installed Scai, please run `scai db reset && scai index start` before using this version.
8
+ SCAI is your AI coding companion in the terminal. Stay focused on coding while SCAI helps you understand, analyze, and reason about your codebase using local language models.
9
+
10
+ **Local Model Note:** SCAI runs entirely on local LLMs. This means **no API keys, no token cost, and full privacy**, but also **more limited capabilities** compared to cloud-hosted AI.
11
+
12
+ > ⚠️ **Alpha Version Notice**
13
+ > If you have previously installed SCAI, please run:
14
+ >
15
+ > ```bash
16
+ > scai db reset && scai index start
17
+ > ```
18
+ >
19
+ > before using this version.
20
+
21
+ ---
22
+
23
+ ## 🗣️ Language Support (Important)
24
+
25
+ SCAI is currently **tested and validated only on the following languages**:
26
+
27
+ * **JavaScript (JS)**
28
+ * **TypeScript (TS)**
29
+ * **Java**
30
+
31
+ Other languages may work partially, but analysis quality, indexing accuracy, and agent behavior are **not guaranteed** outside these languages. Broader language support is planned, but for now SCAI should be considered **JS/TS/Java-first**.
8
32
 
9
33
  ---
10
34
 
@@ -18,7 +42,9 @@ scai init
18
42
  scai index start
19
43
  ```
20
44
 
21
- This will initialize local models (recommended: `qwen3-coder:30b`) and start indexing your code repository.
45
+ This initializes local models (recommended: `qwen3-coder:30b`) and starts indexing your code repository.
46
+
47
+ > ⏳ **Note:** Initial indexing and analysis can take **minutes to hours** depending on repository size and enabled analysis tools.
22
48
 
23
49
  ### 2️⃣ Check Available Commands
24
50
 
@@ -38,7 +64,7 @@ scai shell
38
64
 
39
65
  Once in the REPL, you can:
40
66
 
41
- * Ask questions freely about your codebase:
67
+ ### Ask questions about your codebase
42
68
 
43
69
  ```text
44
70
  scai> How many functions in config.js are missing tests?
@@ -47,7 +73,7 @@ scai> Where are all the database queries defined?
47
73
  scai> List files involved in authentication
48
74
  ```
49
75
 
50
- * Run slash commands inside the REPL for standard CLI operations:
76
+ ### Run CLI commands from inside the REPL
51
77
 
52
78
  ```text
53
79
  scai> /git commit
@@ -57,64 +83,72 @@ scai> /index switch
57
83
  scai> /index delete
58
84
  ```
59
85
 
60
- * Execute shell commands directly:
86
+ ### Execute shell commands
61
87
 
62
88
  ```text
63
89
  scai> !ls -la
64
90
  scai> !git status
65
91
  ```
66
92
 
67
- > ✅ Asking questions in the REPL is free, GDPR-friendly, and entirely local.
93
+ > ✅ REPL queries are free, offline, GDPR-friendly, and **no token cost**.
68
94
 
69
95
  ---
70
96
 
71
- ## 📦 Indexing Commands
97
+ ## 📦 Repository Indexing
72
98
 
73
- Before asking questions, you should index your code repository:
99
+ Before SCAI can answer questions, your repository must be indexed.
74
100
 
75
- * **Set index directory**
101
+ ### Common Index Commands
76
102
 
77
103
  ```bash
78
104
  scai index set /path/to/repo
105
+ scai index start
106
+ scai index list
107
+ scai index switch
108
+ scai index delete
79
109
  ```
80
110
 
81
- * **Start indexing**
111
+ Only indexed repositories can be queried.
82
112
 
83
- ```bash
84
- scai index start
85
- ```
113
+ ---
86
114
 
87
- * **List all indexed repositories**
115
+ ## 🧠 Background Indexing & Analysis (Daemon)
88
116
 
89
- ```bash
90
- scai index list
91
- ```
117
+ SCAI performs **deep repository indexing and static analysis** using background workers. This includes:
92
118
 
93
- * **Switch active repository**
119
+ * File structure discovery
120
+ * Language-aware parsing (JS / TS / Java)
121
+ * Symbol and dependency mapping
122
+ * Heuristic analysis for tests, architecture, and patterns
94
123
 
95
- ```bash
96
- scai index switch
97
- ```
124
+ ⚠️ **Important:** On first install or on large repositories, this process can take **several hours**.
125
+
126
+ All background work is handled by the **SCAI daemon**, which can be fully controlled from the CLI.
98
127
 
99
- * **Delete a repository from index**
128
+ ### Daemon Commands
100
129
 
101
130
  ```bash
102
- scai index delete
131
+ scai daemon start
132
+ scai daemon stop
133
+ scai daemon restart
134
+ scai daemon status
135
+ scai daemon unlock
136
+ scai daemon logs
103
137
  ```
104
138
 
105
- > Only indexed repositories can be queried by scai.
139
+ You can safely stop the daemon at any time. Indexing and analysis will resume when restarted.
106
140
 
107
141
  ---
108
142
 
109
143
  ## ⚙️ Configuration
110
144
 
111
- Set the AI model for local usage (recommended: `qwen3-coder:30b`):
145
+ Set the local AI model (recommended):
112
146
 
113
147
  ```bash
114
148
  scai config set-model qwen3-coder:30b
115
149
  ```
116
150
 
117
- Check current configuration:
151
+ View current configuration:
118
152
 
119
153
  ```bash
120
154
  scai config show --raw
@@ -124,42 +158,30 @@ scai config show --raw
124
158
 
125
159
  ## 🔧 Git Commit Assistant
126
160
 
127
- Use AI to suggest meaningful commit messages based on your staged changes:
161
+ Generate meaningful commit messages based on staged changes:
128
162
 
129
163
  ```bash
130
164
  git add .
131
165
  scai git commit
132
166
  ```
133
167
 
134
- This generates a commit message reflecting your changes and context, entirely locally.
168
+ All analysis is performed locally **no token usage, no cloud calls**.
135
169
 
136
170
  ---
137
171
 
138
172
  ## 🔑 GitHub Authentication
139
173
 
140
- To work with GitHub for repositories, scai needs a Personal Access Token:
141
-
142
- * **Set your token:**
174
+ For GitHub-related features, SCAI requires a Personal Access Token.
143
175
 
144
176
  ```bash
145
177
  scai auth set
146
- ```
147
-
148
- * **Check your token:**
149
-
150
- ```bash
151
178
  scai auth check
152
- ```
153
-
154
- * **Reset your token:**
155
-
156
- ```bash
157
179
  scai auth reset
158
180
  ```
159
181
 
160
182
  ---
161
183
 
162
- ## 🧠 Example Queries in REPL
184
+ ## 🧠 Example Queries
163
185
 
164
186
  * `Summarize codeTransform.js`
165
187
  * `Explain utils/helpers.ts architecture`
@@ -167,26 +189,26 @@ scai auth reset
167
189
  * `Show where database queries are defined`
168
190
  * `Highlight potential memory leaks`
169
191
  * `Describe how authentication works`
170
- * `Which files handle error handling in contextReview.ts`
171
192
  * `Summarize repo architecture`
172
193
 
173
194
  ---
174
195
 
175
196
  ## 🔐 Privacy & GDPR
176
197
 
177
- * Fully local — no API keys, no cloud
178
- * GDPR-friendly, made in Denmark/EU with love ❤️
179
- * Free to ask questions about your code
198
+ * Fully local — no cloud calls
199
+ * No API keys
200
+ * **No token cost**
201
+ * GDPR-friendly, built in Denmark/EU 🇩🇰
180
202
 
181
203
  ---
182
204
 
183
205
  ## 🙌 Feedback & Support
184
206
 
185
- Reach out with feedback or questions:
207
+ Feedback, bugs, and ideas are very welcome:
186
208
 
187
- * Threads: [@scai.dk](https://threads.net/@scai.dk)
188
- * Bugs, feature requests, or ideas welcome!
209
+ * 🌍 Website: [https://scai.dk](https://scai.dk)
210
+ * 🧵 Threads: [@scai.dk](https://threads.net/@scai.dk)
189
211
 
190
212
  ---
191
213
 
192
- Enjoy your AI-powered coding companion, fully local, private, and developer-friendly!
214
+ Enjoy your **fully local, private, and developer-focused AI coding companion** ✨
@@ -3,58 +3,113 @@ import { generate } from "../../lib/generate.js";
3
3
  import chalk from "chalk";
4
4
  export const finalAnswerModule = {
5
5
  name: "finalAnswer",
6
- description: "Generates a final answer using structured context and relevant working files.",
6
+ description: "Produces the final, post-execution explanation for the user.",
7
7
  groups: ["finalize"],
8
8
  run: async (input) => {
9
9
  const query = input.query;
10
10
  const context = input.context;
11
- if (!context)
11
+ if (!context) {
12
12
  throw new Error("[finalAnswerModule] No context provided");
13
+ }
14
+ // ---------------------------------------------------------------------
15
+ // Detect execution state
16
+ // ---------------------------------------------------------------------
13
17
  const transformedFiles = context.execution?.codeTransformArtifacts?.files ?? [];
14
- const workingFiles = context.workingFiles ?? [];
15
- const filesToConsider = transformedFiles.length
18
+ const hasTransforms = transformedFiles.length > 0;
19
+ // ---------------------------------------------------------------------
20
+ // Select authoritative files
21
+ // ---------------------------------------------------------------------
22
+ const filesToConsider = hasTransforms
16
23
  ? transformedFiles.map(f => ({
17
24
  path: f.filePath,
18
- codeSnippet: f.content.slice(0, 500),
19
25
  notes: f.notes,
26
+ codeSnippet: f.content.slice(0, 500),
20
27
  }))
21
- : workingFiles.map(f => ({
28
+ : (context.workingFiles ?? []).map(f => ({
22
29
  path: f.path,
23
- codeSnippet: f.code ? f.code.slice(0, 500) : undefined,
24
30
  summary: f.summary ?? "",
31
+ codeSnippet: f.code ? f.code.slice(0, 500) : undefined,
25
32
  }));
26
- const analysisSummary = {
33
+ // ---------------------------------------------------------------------
34
+ // Analysis framing (before / after)
35
+ // ---------------------------------------------------------------------
36
+ const analysisPayload = {
27
37
  intent: context.analysis?.intent,
28
- fileAnalysis: context.analysis?.fileAnalysis,
38
+ // BEFORE: what problem was being solved
39
+ understanding: context.analysis?.understanding,
40
+ // AFTER: what is now true across the system
29
41
  combinedAnalysis: context.analysis?.combinedAnalysis,
30
- plan: context.plan,
31
- executionArtifacts: context.execution,
42
+ // Supporting detail (optional but useful)
43
+ fileAnalysis: context.analysis?.fileAnalysis,
32
44
  };
45
+ // ---------------------------------------------------------------------
46
+ // Phase-aware prompt construction
47
+ // ---------------------------------------------------------------------
48
+ const phasePreamble = hasTransforms
49
+ ? `
50
+ SYSTEM CONTEXT:
51
+ This is the FINAL STEP in a long, multi-stage execution pipeline.
52
+
53
+ All planned code transformations have already been executed.
54
+ The files shown represent the FINAL and AUTHORITATIVE state of the codebase.
55
+
56
+ Your role is to explain what was done and why — not to suggest changes.
57
+ `
58
+ : `
59
+ SYSTEM CONTEXT:
60
+ This request does not include executed code changes.
61
+ Provide a direct explanation based on the available context.
62
+ `;
63
+ const instructions = hasTransforms
64
+ ? `
65
+ INSTRUCTIONS (POST-EXECUTION EXPLANATION MODE):
66
+
67
+ - Explain the completed changes using past tense.
68
+ - Anchor the explanation in the original problem (understanding).
69
+ - Describe system-wide effects using combinedAnalysis.
70
+ - Refer to transformed files as evidence, not proposals.
71
+ - Mention relevant risks or trade-offs, if any.
72
+ - DO NOT explain how to implement the change.
73
+ - DO NOT suggest additional work.
74
+ - DO NOT ask questions.
75
+ `
76
+ : `
77
+ INSTRUCTIONS:
78
+
79
+ - Answer the developer’s question clearly and concisely.
80
+ - Base your response strictly on the provided context.
81
+ - Do not invent changes or assume missing information.
82
+ `;
33
83
  const promptText = `
34
- You are an AI assistant answering a developer query.
84
+ ${phasePreamble}
35
85
 
36
- Query:
86
+ You are producing the FINAL ANSWER for this run.
87
+
88
+ User query:
37
89
  ${query}
38
90
 
39
- Analysis summary:
40
- ${JSON.stringify(analysisSummary, null, 2)}
91
+ Analysis:
92
+ ${JSON.stringify(analysisPayload, null, 2)}
41
93
 
42
- Target files and content:
94
+ Relevant files (final state):
43
95
  ${JSON.stringify(filesToConsider, null, 2)}
44
96
 
45
- Instructions:
46
- - Provide a clear, concise answer based only on the available files and analysis.
47
- - Highlight risks or important notes from fileAnalysis or combinedAnalysis.
48
- - Do not ask for additional files; assume the provided code is authoritative.
49
- `;
50
- const aiResponse = await generate({ query, content: promptText });
97
+ ${instructions}
98
+ `.trim();
99
+ // ---------------------------------------------------------------------
100
+ // Generate final answer
101
+ // ---------------------------------------------------------------------
102
+ const aiResponse = await generate({
103
+ query,
104
+ content: promptText,
105
+ });
51
106
  const output = {
52
107
  query,
53
- content: '',
108
+ content: "",
54
109
  data: aiResponse.data,
55
110
  };
56
111
  logInputOutput("finalAnswer", "output", output.data);
57
- console.log('\n\n\n\n--> Answer: ', chalk.blue(output.data));
112
+ console.log("\n\n\n\n--> Answer:\n", chalk.blue(output.data));
58
113
  return output;
59
- }
114
+ },
60
115
  };
@@ -1,7 +1,5 @@
1
1
  import { encode } from 'gpt-3-encoder';
2
- export function splitCodeIntoChunks(text, softLimit = 1500, // first threshold
3
- hardLimitMultiplier = 2 // hard limit will be softLimit * multiplier
4
- ) {
2
+ export function splitCodeIntoChunks(text, softLimit = 1500, hardLimitMultiplier = 2) {
5
3
  const hardLimit = softLimit * hardLimitMultiplier;
6
4
  const lines = text.split('\n');
7
5
  const chunks = [];
@@ -10,12 +8,19 @@ hardLimitMultiplier = 2 // hard limit will be softLimit * multiplier
10
8
  let inMultiComment = false;
11
9
  let inFunction = false;
12
10
  let inTryBlock = false;
11
+ let inTryChain = false;
12
+ let tryChainHasHandler = false;
13
13
  let globalBraceDepth = 0;
14
14
  let functionBraceDepth = 0;
15
+ let tryBraceDepth = 0;
15
16
  let parenDepth = 0;
16
17
  let bracketDepth = 0;
18
+ let justClosedFunction = false;
19
+ let justClosedTryBlock = false;
17
20
  for (const line of lines) {
18
21
  const trimmed = line.trim();
22
+ justClosedFunction = false;
23
+ justClosedTryBlock = false;
19
24
  // ---------- comments ----------
20
25
  if (trimmed.includes('/*') && !trimmed.includes('*/'))
21
26
  inMultiComment = true;
@@ -30,22 +35,40 @@ hardLimitMultiplier = 2 // hard limit will be softLimit * multiplier
30
35
  inFunction = true;
31
36
  functionBraceDepth = 0;
32
37
  }
33
- // ---------- try/catch ----------
34
- if (trimmed.startsWith('try {'))
38
+ // ---------- try / catch / finally start ----------
39
+ if (!inTryChain && trimmed.startsWith('try')) {
40
+ inTryChain = true;
35
41
  inTryBlock = true;
36
- if (trimmed.startsWith('catch') || trimmed.startsWith('finally'))
37
- inTryBlock = false;
42
+ tryBraceDepth = 0;
43
+ }
44
+ if (inTryChain &&
45
+ (trimmed.startsWith('catch') || trimmed.startsWith('finally'))) {
46
+ inTryBlock = true;
47
+ tryChainHasHandler = true;
48
+ }
38
49
  // ---------- depth tracking ----------
39
50
  for (const char of line) {
40
51
  if (char === '{') {
41
52
  globalBraceDepth++;
42
53
  if (inFunction)
43
54
  functionBraceDepth++;
55
+ if (inTryChain)
56
+ tryBraceDepth++;
44
57
  }
45
58
  else if (char === '}') {
46
59
  globalBraceDepth = Math.max(0, globalBraceDepth - 1);
47
- if (inFunction)
60
+ if (inFunction) {
48
61
  functionBraceDepth = Math.max(0, functionBraceDepth - 1);
62
+ if (functionBraceDepth === 0) {
63
+ justClosedFunction = true;
64
+ }
65
+ }
66
+ if (inTryChain) {
67
+ tryBraceDepth = Math.max(0, tryBraceDepth - 1);
68
+ if (tryBraceDepth === 0) {
69
+ justClosedTryBlock = true;
70
+ }
71
+ }
49
72
  }
50
73
  else if (char === '(') {
51
74
  parenDepth++;
@@ -60,6 +83,16 @@ hardLimitMultiplier = 2 // hard limit will be softLimit * multiplier
60
83
  bracketDepth = Math.max(0, bracketDepth - 1);
61
84
  }
62
85
  }
86
+ if (justClosedFunction)
87
+ inFunction = false;
88
+ if (justClosedTryBlock) {
89
+ inTryBlock = false;
90
+ // Only close the chain if we've already seen a handler
91
+ if (tryChainHasHandler) {
92
+ inTryChain = false;
93
+ tryChainHasHandler = false;
94
+ }
95
+ }
63
96
  // ---------- add line ----------
64
97
  currentChunkLines.push(line);
65
98
  currentTokens += encode(line + '\n').length;
@@ -67,20 +100,18 @@ hardLimitMultiplier = 2 // hard limit will be softLimit * multiplier
67
100
  const softLimitReached = currentTokens >= softLimit;
68
101
  const hardLimitReached = currentTokens >= hardLimit;
69
102
  const safeToSplit = !inMultiComment &&
70
- !inTryBlock &&
103
+ !inFunction &&
104
+ !inTryChain &&
105
+ !justClosedFunction &&
106
+ !justClosedTryBlock &&
71
107
  functionBraceDepth === 0 &&
72
108
  parenDepth === 0 &&
73
109
  bracketDepth === 0;
74
- // try to split after soft limit if safe, or force at hard limit
75
110
  if ((softLimitReached && safeToSplit) || hardLimitReached) {
76
111
  chunks.push(currentChunkLines.join('\n'));
77
112
  currentChunkLines = [];
78
113
  currentTokens = 0;
79
114
  }
80
- // ---------- function end ----------
81
- if (inFunction && functionBraceDepth === 0) {
82
- inFunction = false;
83
- }
84
115
  }
85
116
  if (currentChunkLines.length > 0) {
86
117
  chunks.push(currentChunkLines.join('\n'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.122",
3
+ "version": "0.1.123",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"