scai 0.1.98 → 0.1.100
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 +23 -15
- package/dist/CHANGELOG.md +7 -1
- package/dist/config.js +2 -2
- package/dist/lib/generate.js +1 -3
- package/dist/pipeline/modules/preserveCodeModule.js +84 -79
- package/dist/utils/splitCodeIntoChunk.js +25 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
- 💬 **Suggests intelligent Git commit messages** based on your staged diff
|
|
8
8
|
- 🤖 **Reviews open pull requests** and provides AI‑driven feedback (BETA)
|
|
9
|
-
- 📝
|
|
9
|
+
- 📝 **Generates comments for multiple files** while you keep coding
|
|
10
10
|
- 📜 Auto‑updates your changelog
|
|
11
11
|
- 🔍 (ALPHA) Search & ask questions across your codebase
|
|
12
12
|
- 🔐 100% local — no API keys, no cloud, no telemetry
|
|
@@ -225,22 +225,12 @@ This will:
|
|
|
225
225
|
### 🛠️ Code Generation Commands (`gen` group)
|
|
226
226
|
|
|
227
227
|
```bash
|
|
228
|
-
scai gen summ <file>
|
|
229
228
|
scai gen comm <file|folder...>
|
|
229
|
+
scai gen summ <file>
|
|
230
230
|
scai gen changelog
|
|
231
231
|
scai gen tests <file>
|
|
232
232
|
```
|
|
233
233
|
|
|
234
|
-
* `summ`: Summarize a file
|
|
235
|
-
|
|
236
|
-
You can also pipe file content directly:
|
|
237
|
-
|
|
238
|
-
```bash
|
|
239
|
-
cat src/utils/math.ts | scai gen summ
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
</br>
|
|
243
|
-
|
|
244
234
|
* `comm`: Add comments to one or more files, or to all matching files in a folder (recursive).
|
|
245
235
|
|
|
246
236
|
</br>
|
|
@@ -255,6 +245,17 @@ scai gen tests <file>
|
|
|
255
245
|
|
|
256
246
|
</br>
|
|
257
247
|
|
|
248
|
+
* `summ`: Summarize a file
|
|
249
|
+
|
|
250
|
+
You can also pipe file content directly:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
cat src/utils/math.ts | scai gen summ
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
</br>
|
|
257
|
+
|
|
258
|
+
|
|
258
259
|
* `changelog`: Update or create `CHANGELOG.md` from Git diff
|
|
259
260
|
|
|
260
261
|
* `tests`: Create Jest test stubs (ALPHA)
|
|
@@ -268,17 +269,24 @@ scai stores settings in `~/.scai/config.json`. You can override or view them:
|
|
|
268
269
|
* **Set model:**
|
|
269
270
|
|
|
270
271
|
```bash
|
|
271
|
-
scai set
|
|
272
|
+
scai config set-model codellama:7b
|
|
272
273
|
```
|
|
273
274
|
* **Set language:**
|
|
274
275
|
|
|
275
276
|
```bash
|
|
276
|
-
scai set
|
|
277
|
+
scai config set-lang ts
|
|
277
278
|
```
|
|
278
279
|
* **Show config:**
|
|
279
280
|
|
|
281
|
+
To see the config for the active repo
|
|
280
282
|
```bash
|
|
281
|
-
scai config
|
|
283
|
+
scai config show
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
To see the config for all repos
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
scai config show --raw
|
|
282
290
|
```
|
|
283
291
|
|
|
284
292
|
---
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -140,4 +140,10 @@ Type handling with the module pipeline
|
|
|
140
140
|
|
|
141
141
|
## 2025-08-24
|
|
142
142
|
|
|
143
|
-
• Improved CLI review command with AI-generated suggestions and enhanced user interface.
|
|
143
|
+
• Improved CLI review command with AI-generated suggestions and enhanced user interface.
|
|
144
|
+
|
|
145
|
+
## 2025-08-26
|
|
146
|
+
|
|
147
|
+
• Fixed bug where entire block was returned as a single line for multi-line comments
|
|
148
|
+
• Add multi-line comment handling with ~90% accuracy
|
|
149
|
+
• Update CLI config file to use codellama:13b model and 4096 context length
|
package/dist/config.js
CHANGED
|
@@ -6,8 +6,8 @@ import { normalizePath } from './utils/normalizePath.js';
|
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import { getHashedRepoKey } from './utils/repoKey.js';
|
|
8
8
|
const defaultConfig = {
|
|
9
|
-
model: '
|
|
10
|
-
contextLength:
|
|
9
|
+
model: 'codellama:13b',
|
|
10
|
+
contextLength: 4096,
|
|
11
11
|
language: 'ts',
|
|
12
12
|
indexDir: '',
|
|
13
13
|
githubToken: '',
|
package/dist/lib/generate.js
CHANGED
|
@@ -5,9 +5,7 @@ export async function generate(input, model) {
|
|
|
5
5
|
const contextLength = readConfig().contextLength ?? 8192;
|
|
6
6
|
let prompt = input.content;
|
|
7
7
|
if (prompt.length > contextLength) {
|
|
8
|
-
console.warn(`⚠️ Warning: Input prompt length (${prompt.length}) exceeds model context length (${contextLength}). `
|
|
9
|
-
`The model may truncate or not handle the entire prompt. Truncating input.`);
|
|
10
|
-
prompt = prompt.slice(0, contextLength);
|
|
8
|
+
console.warn(`⚠️ Warning: Input prompt length (${prompt.length}) exceeds model context length (${contextLength}). `);
|
|
11
9
|
}
|
|
12
10
|
const spinner = new Spinner(`🧠 Thinking with ${model}...`);
|
|
13
11
|
spinner.start();
|
|
@@ -8,126 +8,131 @@ export const preserveCodeModule = {
|
|
|
8
8
|
throw new Error("Requires `originalContent`.");
|
|
9
9
|
const syntax = {
|
|
10
10
|
singleLine: ["//"],
|
|
11
|
-
multiLine: [{ start: "/*", end: "*/" }]
|
|
11
|
+
multiLine: [{ start: "/*", end: "*/" }, { start: "/**", end: "*/" }]
|
|
12
12
|
};
|
|
13
13
|
// --- Normalize line endings ---
|
|
14
14
|
const normalize = (txt) => txt.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
15
15
|
const origLines = normalize(originalContent).split("\n");
|
|
16
16
|
const newLines = normalize(content).split("\n");
|
|
17
|
-
//
|
|
17
|
+
// --- Classify line ---
|
|
18
18
|
let inBlockComment = false;
|
|
19
|
+
let blockLines = [];
|
|
19
20
|
const classifyLine = (line) => {
|
|
20
21
|
const trimmed = line.trimStart();
|
|
21
|
-
// Single-line
|
|
22
|
+
// --- Single-line comment ---
|
|
22
23
|
for (const s of syntax.singleLine) {
|
|
23
24
|
if (trimmed.startsWith(s))
|
|
24
|
-
return
|
|
25
|
+
return line; // return actual line
|
|
25
26
|
}
|
|
26
|
-
// Multi-line
|
|
27
|
+
// --- Multi-line comment ---
|
|
27
28
|
for (const { start, end } of syntax.multiLine) {
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
if (!inBlockComment) {
|
|
30
|
+
if (trimmed.startsWith(start) && trimmed.includes(end)) {
|
|
31
|
+
return line; // entire block on a single line
|
|
32
|
+
}
|
|
33
|
+
if (trimmed.startsWith(start)) {
|
|
34
|
+
inBlockComment = true;
|
|
35
|
+
blockLines = [line];
|
|
36
|
+
return line; // start of multi-line block
|
|
37
|
+
}
|
|
34
38
|
}
|
|
35
|
-
|
|
39
|
+
else {
|
|
40
|
+
blockLines.push(line);
|
|
36
41
|
if (trimmed.includes(end)) {
|
|
37
42
|
inBlockComment = false;
|
|
38
|
-
|
|
43
|
+
const fullBlock = blockLines.join("\n");
|
|
44
|
+
blockLines = [];
|
|
45
|
+
return fullBlock; // return entire multi-line block
|
|
39
46
|
}
|
|
40
|
-
return "
|
|
47
|
+
return ""; // middle lines, wait until block ends
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
50
|
return "code";
|
|
44
51
|
};
|
|
45
|
-
//
|
|
46
|
-
function
|
|
47
|
-
const
|
|
48
|
-
let
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
const trimBlock = (block) => block.map(line => line.trim());
|
|
56
|
-
const blocksEqual = (a, b) => JSON.stringify(trimBlock(a)) === JSON.stringify(trimBlock(b));
|
|
57
|
-
const fixedLines = [];
|
|
58
|
-
let origIndex = 0;
|
|
59
|
-
let newIndex = 0;
|
|
60
|
-
// Track all inserted comment blocks globally
|
|
61
|
-
const insertedBlocks = new Set();
|
|
62
|
-
while (origIndex < origLines.length) {
|
|
63
|
-
const origLine = origLines[origIndex];
|
|
64
|
-
// If this is a comment line in original or model
|
|
65
|
-
if (classifyLine(origLine) !== "code" || classifyLine(newLines[newIndex] ?? "") !== "code") {
|
|
66
|
-
const origBlock = collectBlock(origLines, origIndex);
|
|
67
|
-
const modelBlock = collectBlock(newLines, newIndex);
|
|
68
|
-
// Merge: model block first, then any orig lines not in model
|
|
69
|
-
const seen = new Set(trimBlock(modelBlock));
|
|
70
|
-
const mergedBlock = [...modelBlock];
|
|
71
|
-
for (const line of origBlock) {
|
|
72
|
-
if (!seen.has(line.trim())) {
|
|
73
|
-
mergedBlock.push(line);
|
|
74
|
-
}
|
|
52
|
+
// --- Helper: collect comment blocks into map ---
|
|
53
|
+
function collectCommentsMap(lines) {
|
|
54
|
+
const map = new Map();
|
|
55
|
+
let commentBuffer = [];
|
|
56
|
+
for (const line of lines) {
|
|
57
|
+
const type = classifyLine(line);
|
|
58
|
+
if (type && type !== "code") {
|
|
59
|
+
// Collect comment lines
|
|
60
|
+
commentBuffer.push(type.trim());
|
|
75
61
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
62
|
+
else if (type === "code") {
|
|
63
|
+
// Flush buffer when hitting code
|
|
64
|
+
if (commentBuffer.length > 0) {
|
|
65
|
+
const key = line.trim().toLowerCase();
|
|
66
|
+
if (!map.has(key))
|
|
67
|
+
map.set(key, new Set());
|
|
68
|
+
// Join consecutive comments into one block
|
|
69
|
+
const commentBlock = commentBuffer.join("\n").toLowerCase();
|
|
70
|
+
map.get(key).add(commentBlock);
|
|
71
|
+
commentBuffer = [];
|
|
72
|
+
}
|
|
85
73
|
}
|
|
86
|
-
// Advance indices past the entire blocks
|
|
87
|
-
origIndex += origBlock.length;
|
|
88
|
-
newIndex += modelBlock.length;
|
|
89
|
-
continue;
|
|
90
74
|
}
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
75
|
+
// Flush remaining comments at EOF
|
|
76
|
+
if (commentBuffer.length > 0) {
|
|
77
|
+
const key = "";
|
|
78
|
+
if (!map.has(key))
|
|
79
|
+
map.set(key, new Set());
|
|
80
|
+
const commentBlock = commentBuffer.join("\n").toLowerCase();
|
|
81
|
+
map.get(key).add(commentBlock);
|
|
82
|
+
}
|
|
83
|
+
return map;
|
|
96
84
|
}
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
85
|
+
// --- Step 1: Collect comments ---
|
|
86
|
+
const modelComments = collectCommentsMap(newLines); // model first
|
|
87
|
+
const origComments = collectCommentsMap(origLines); // original
|
|
88
|
+
// --- Step 2: Remove duplicates ---
|
|
89
|
+
for (const [key, commentSet] of modelComments.entries()) {
|
|
90
|
+
if (origComments.has(key)) {
|
|
91
|
+
commentSet.forEach(c => {
|
|
92
|
+
origComments.get(key).delete(c.trim().toLowerCase());
|
|
93
|
+
});
|
|
94
|
+
if (origComments.get(key).size === 0)
|
|
95
|
+
origComments.delete(key);
|
|
96
|
+
}
|
|
100
97
|
}
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
// --- Step 3: Build fixed lines with model comments inserted above original ---
|
|
99
|
+
const fixedLines = [];
|
|
100
|
+
for (const origLine of origLines) {
|
|
101
|
+
const key = origLine.trim().toLowerCase();
|
|
102
|
+
// Insert model comment blocks if any
|
|
103
|
+
if (modelComments.has(key)) {
|
|
104
|
+
modelComments.get(key).forEach(block => {
|
|
105
|
+
const lines = block.split("\n");
|
|
106
|
+
for (const line of lines) {
|
|
107
|
+
if (!fixedLines.includes(line)) {
|
|
108
|
+
fixedLines.push(line);
|
|
109
|
+
console.log(chalk.blue("Inserted comment:"), line.trim());
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
console.log(chalk.gray("Skipped duplicate comment:"), line.trim());
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
105
116
|
}
|
|
106
|
-
|
|
117
|
+
fixedLines.push(origLine);
|
|
107
118
|
}
|
|
108
119
|
// --- Logging for debugging ---
|
|
109
120
|
console.log(chalk.bold.blue("\n=== LINE CLASSIFICATION (original) ==="));
|
|
110
121
|
origLines.forEach((line, i) => {
|
|
111
122
|
const type = classifyLine(line);
|
|
112
|
-
const colored = type === "code"
|
|
113
|
-
? chalk.green(line)
|
|
114
|
-
: chalk.yellow(line);
|
|
123
|
+
const colored = type === "code" ? chalk.green(line) : chalk.yellow(line);
|
|
115
124
|
console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`);
|
|
116
125
|
});
|
|
117
126
|
console.log(chalk.bold.blue("\n=== LINE CLASSIFICATION (model) ==="));
|
|
118
127
|
newLines.forEach((line, i) => {
|
|
119
128
|
const type = classifyLine(line);
|
|
120
|
-
const colored = type === "code"
|
|
121
|
-
? chalk.green(line)
|
|
122
|
-
: chalk.yellow(line);
|
|
129
|
+
const colored = type === "code" ? chalk.green(line) : chalk.yellow(line);
|
|
123
130
|
console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`);
|
|
124
131
|
});
|
|
125
132
|
console.log(chalk.bold.blue("\n=== FIXED CONTENT ==="));
|
|
126
133
|
fixedLines.forEach((line, i) => {
|
|
127
134
|
const type = classifyLine(line);
|
|
128
|
-
const colored = type === "code"
|
|
129
|
-
? chalk.green(line)
|
|
130
|
-
: chalk.yellow(line);
|
|
135
|
+
const colored = type === "code" ? chalk.green(line) : chalk.yellow(line);
|
|
131
136
|
console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`);
|
|
132
137
|
});
|
|
133
138
|
return { content: fixedLines.join("\n"), filepath };
|
|
@@ -4,24 +4,41 @@ export function splitCodeIntoChunks(text, maxTokens) {
|
|
|
4
4
|
const chunks = [];
|
|
5
5
|
let currentChunkLines = [];
|
|
6
6
|
let currentTokens = 0;
|
|
7
|
+
let inMultiComment = false;
|
|
8
|
+
const start = '/*';
|
|
9
|
+
const end = '*/';
|
|
7
10
|
for (const line of lines) {
|
|
11
|
+
const trimmed = line.trim();
|
|
12
|
+
// --- Track multi-line comments ---
|
|
13
|
+
if (trimmed.includes(start) && !trimmed.includes(end)) {
|
|
14
|
+
// Starts a block comment but does not end on the same line
|
|
15
|
+
inMultiComment = true;
|
|
16
|
+
}
|
|
17
|
+
else if (trimmed.includes(start) && trimmed.includes(end)) {
|
|
18
|
+
// Inline comment: "/* ... */" on same line → ignore, don't toggle state
|
|
19
|
+
// do nothing with inMultiComment
|
|
20
|
+
}
|
|
21
|
+
else if (trimmed.includes(end)) {
|
|
22
|
+
// End of a block comment
|
|
23
|
+
inMultiComment = false;
|
|
24
|
+
}
|
|
8
25
|
const lineTokens = encode(line + '\n').length;
|
|
9
26
|
if (currentTokens + lineTokens > maxTokens) {
|
|
10
|
-
//
|
|
27
|
+
// Split at natural points but never inside a multi-line comment
|
|
11
28
|
let splitIndex = currentChunkLines.length;
|
|
12
29
|
for (let i = currentChunkLines.length - 1; i >= 0; i--) {
|
|
13
|
-
const
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
const t = currentChunkLines[i].trim();
|
|
31
|
+
if (!inMultiComment &&
|
|
32
|
+
(t === '' ||
|
|
33
|
+
t.startsWith('function ') ||
|
|
34
|
+
t.startsWith('class ') ||
|
|
35
|
+
t.endsWith('}') ||
|
|
36
|
+
t.endsWith(';'))) {
|
|
19
37
|
splitIndex = i + 1;
|
|
20
38
|
break;
|
|
21
39
|
}
|
|
22
40
|
}
|
|
23
41
|
chunks.push(currentChunkLines.slice(0, splitIndex).join('\n'));
|
|
24
|
-
// Move leftover lines into the next chunk
|
|
25
42
|
currentChunkLines = currentChunkLines.slice(splitIndex);
|
|
26
43
|
currentTokens = encode(currentChunkLines.join('\n')).length;
|
|
27
44
|
}
|