scai 0.1.13 β 0.1.14
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 +7 -0
- package/dist/commands/CommitSuggesterCmd.js +8 -34
- package/dist/lib/generate.js +22 -0
- package/dist/pipeline/modules/changeLogModule.js +2 -11
- package/dist/pipeline/modules/commentModule.js +3 -11
- package/dist/pipeline/modules/commitSuggesterModule.js +30 -0
- package/dist/pipeline/modules/generateTestsModule.js +5 -14
- package/dist/pipeline/modules/refactorModule.js +9 -26
- package/dist/pipeline/modules/summaryModule.js +4 -9
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
// File: src/commands/CommitSuggesterCmd.ts
|
|
1
2
|
import { execSync } from 'child_process';
|
|
2
3
|
import readline from 'readline';
|
|
4
|
+
import { commitSuggesterModule } from '../pipeline/modules/commitSuggesterModule.js';
|
|
3
5
|
function askUserToChoose(suggestions) {
|
|
4
6
|
return new Promise((resolve) => {
|
|
5
7
|
console.log('\nπ‘ AI-suggested commit messages:\n');
|
|
6
|
-
suggestions.
|
|
8
|
+
suggestions.forEach((msg, i) => {
|
|
7
9
|
console.log(`${i + 1}) ${msg}`);
|
|
8
10
|
});
|
|
9
11
|
console.log('\n---');
|
|
10
|
-
console.log(
|
|
11
|
-
console.log(
|
|
12
|
+
console.log(`${suggestions.length + 1}) π Regenerate suggestions`);
|
|
13
|
+
console.log(`${suggestions.length + 2}) βοΈ Write your own commit message`);
|
|
12
14
|
const rl = readline.createInterface({
|
|
13
15
|
input: process.stdin,
|
|
14
16
|
output: process.stdout,
|
|
@@ -24,32 +26,11 @@ function askUserToChoose(suggestions) {
|
|
|
24
26
|
resolve('custom');
|
|
25
27
|
}
|
|
26
28
|
else {
|
|
27
|
-
resolve(choice - 1);
|
|
29
|
+
resolve(choice - 1);
|
|
28
30
|
}
|
|
29
31
|
});
|
|
30
32
|
});
|
|
31
33
|
}
|
|
32
|
-
async function generateSuggestions(prompt) {
|
|
33
|
-
const res = await fetch("http://localhost:11434/api/generate", {
|
|
34
|
-
method: "POST",
|
|
35
|
-
headers: { "Content-Type": "application/json" },
|
|
36
|
-
body: JSON.stringify({
|
|
37
|
-
model: "llama3",
|
|
38
|
-
prompt,
|
|
39
|
-
stream: false,
|
|
40
|
-
}),
|
|
41
|
-
});
|
|
42
|
-
const { response } = await res.json();
|
|
43
|
-
if (!response || typeof response !== 'string') {
|
|
44
|
-
throw new Error('Invalid LLM response');
|
|
45
|
-
}
|
|
46
|
-
const lines = response.trim().split('\n').filter(line => /^\d+\.\s+/.test(line));
|
|
47
|
-
const messages = lines.map(line => line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
|
|
48
|
-
if (messages.length === 0) {
|
|
49
|
-
throw new Error('No valid commit messages found in LLM response.');
|
|
50
|
-
}
|
|
51
|
-
return messages;
|
|
52
|
-
}
|
|
53
34
|
function promptCustomMessage() {
|
|
54
35
|
return new Promise((resolve) => {
|
|
55
36
|
const rl = readline.createInterface({
|
|
@@ -72,19 +53,12 @@ export async function suggestCommitMessage(options) {
|
|
|
72
53
|
console.log('β οΈ No staged changes to suggest a message for.');
|
|
73
54
|
return;
|
|
74
55
|
}
|
|
75
|
-
|
|
76
|
-
1. ...
|
|
77
|
-
2. ...
|
|
78
|
-
3. ...
|
|
79
|
-
|
|
80
|
-
Here is the diff:
|
|
81
|
-
${diff}`;
|
|
56
|
+
let suggestions = [];
|
|
82
57
|
let message = null;
|
|
83
58
|
while (message === null) {
|
|
84
|
-
|
|
59
|
+
suggestions = await commitSuggesterModule.run({ code: diff }).then(out => out.suggestions || []);
|
|
85
60
|
const choice = await askUserToChoose(suggestions);
|
|
86
61
|
if (choice === suggestions.length) {
|
|
87
|
-
// User chose "Regenerate"
|
|
88
62
|
console.log('\nπ Regenerating suggestions...\n');
|
|
89
63
|
continue;
|
|
90
64
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
export async function generate(prompt, model) {
|
|
3
|
+
const spinner = ora(`π§ Thinking with ${model}...`).start();
|
|
4
|
+
try {
|
|
5
|
+
const res = await fetch('http://localhost:11434/api/generate', {
|
|
6
|
+
method: 'POST',
|
|
7
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8
|
+
body: JSON.stringify({
|
|
9
|
+
model,
|
|
10
|
+
prompt,
|
|
11
|
+
stream: false,
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
const data = await res.json();
|
|
15
|
+
spinner.succeed('β
Model response received.');
|
|
16
|
+
return data.response?.trim() ?? '';
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
spinner.fail('β Model request failed.');
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
2
|
+
import { generate } from '../../lib/generate.js';
|
|
2
3
|
export const changelogModule = {
|
|
3
4
|
name: 'changelogModule',
|
|
4
5
|
description: 'Generates changelog entry based on Git diff',
|
|
@@ -14,17 +15,7 @@ ${input.code}
|
|
|
14
15
|
β
If the changes are significant, return a changelog entry.
|
|
15
16
|
β If not, return ONLY: "NO UPDATE".
|
|
16
17
|
`.trim();
|
|
17
|
-
const
|
|
18
|
-
method: 'POST',
|
|
19
|
-
headers: { 'Content-Type': 'application/json' },
|
|
20
|
-
body: JSON.stringify({
|
|
21
|
-
model,
|
|
22
|
-
prompt,
|
|
23
|
-
stream: false,
|
|
24
|
-
}),
|
|
25
|
-
});
|
|
26
|
-
const data = await res.json();
|
|
27
|
-
const output = data.response?.trim() ?? '';
|
|
18
|
+
const output = await generate(prompt, model);
|
|
28
19
|
return { code: output === 'NO UPDATE' ? '' : output };
|
|
29
20
|
},
|
|
30
21
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
2
|
+
import { generate } from '../../lib/generate.js';
|
|
2
3
|
export const addCommentsModule = {
|
|
3
4
|
name: 'addComments',
|
|
4
5
|
description: 'Adds meaningful // comments to each block of code',
|
|
@@ -21,16 +22,7 @@ Your task is to add clear and helpful single-line // comments to complex or non-
|
|
|
21
22
|
${input.code}
|
|
22
23
|
--- CODE END ---
|
|
23
24
|
`.trim();
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
headers: { 'Content-Type': 'application/json' },
|
|
27
|
-
body: JSON.stringify({
|
|
28
|
-
model,
|
|
29
|
-
prompt,
|
|
30
|
-
stream: false,
|
|
31
|
-
}),
|
|
32
|
-
});
|
|
33
|
-
const data = await res.json();
|
|
34
|
-
return { code: data.response?.trim() ?? '' };
|
|
25
|
+
const output = await generate(prompt, model);
|
|
26
|
+
return { code: output === 'NO UPDATE' ? '' : output };
|
|
35
27
|
},
|
|
36
28
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { generate } from '../../lib/generate.js';
|
|
2
|
+
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
3
|
+
export const commitSuggesterModule = {
|
|
4
|
+
name: 'commitSuggester',
|
|
5
|
+
description: 'Suggests conventional commit messages from Git diff',
|
|
6
|
+
async run({ code }) {
|
|
7
|
+
const model = ModelConfig.getModel();
|
|
8
|
+
const prompt = `
|
|
9
|
+
Suggest 3 concise, conventional Git commit messages based on this diff.
|
|
10
|
+
Use this format ONLY:
|
|
11
|
+
|
|
12
|
+
1. feat: ...
|
|
13
|
+
2. fix: ...
|
|
14
|
+
3. refactor: ...
|
|
15
|
+
|
|
16
|
+
Here is the diff:
|
|
17
|
+
${code}
|
|
18
|
+
`.trim();
|
|
19
|
+
const raw = await generate(prompt, model);
|
|
20
|
+
const lines = raw
|
|
21
|
+
.split('\n')
|
|
22
|
+
.map(line => line.trim())
|
|
23
|
+
.filter(line => /^\d+\.\s+/.test(line));
|
|
24
|
+
const suggestions = lines.map(line => line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
|
|
25
|
+
return {
|
|
26
|
+
code,
|
|
27
|
+
suggestions
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
};
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
// src/pipeline/modules/generateTestsModule.ts
|
|
1
2
|
import fs from 'fs/promises';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
5
|
+
import { generate } from '../../lib/generate.js';
|
|
4
6
|
export const generateTestsModule = {
|
|
5
7
|
name: 'generateTests',
|
|
6
8
|
description: 'Generate a Jest test file for the class/module',
|
|
7
9
|
async run({ code, filepath }) {
|
|
8
10
|
const model = ModelConfig.getModel();
|
|
9
11
|
const lang = ModelConfig.getLanguage();
|
|
12
|
+
if (!filepath)
|
|
13
|
+
throw new Error('Missing filepath in pipeline context');
|
|
10
14
|
const prompt = `
|
|
11
15
|
You are a senior ${lang.toUpperCase()} engineer. Given the following class or module, generate a Jest test file.
|
|
12
16
|
|
|
@@ -20,22 +24,9 @@ Guidelines:
|
|
|
20
24
|
${code}
|
|
21
25
|
--- CODE END ---
|
|
22
26
|
`.trim();
|
|
23
|
-
const
|
|
24
|
-
method: 'POST',
|
|
25
|
-
headers: { 'Content-Type': 'application/json' },
|
|
26
|
-
body: JSON.stringify({
|
|
27
|
-
model,
|
|
28
|
-
prompt,
|
|
29
|
-
stream: false
|
|
30
|
-
})
|
|
31
|
-
});
|
|
32
|
-
const data = await res.json();
|
|
33
|
-
const testCode = data.response?.trim();
|
|
27
|
+
const testCode = await generate(prompt, model);
|
|
34
28
|
if (!testCode)
|
|
35
29
|
throw new Error('β οΈ No test code returned from model');
|
|
36
|
-
// Determine test file path next to refactored file
|
|
37
|
-
if (!filepath)
|
|
38
|
-
throw new Error('Missing filepath in pipeline context');
|
|
39
30
|
const { dir, name } = path.parse(filepath);
|
|
40
31
|
const testPath = path.join(dir, `${name}.test.ts`);
|
|
41
32
|
await fs.writeFile(testPath, testCode, 'utf-8');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
2
|
+
import { generate } from '../../lib/generate.js';
|
|
2
3
|
export const refactorModule = {
|
|
3
4
|
name: 'refactor',
|
|
4
5
|
description: 'Break code into small, clean functions',
|
|
@@ -9,37 +10,19 @@ export const refactorModule = {
|
|
|
9
10
|
You are a senior ${lang.toUpperCase()} engineer.
|
|
10
11
|
|
|
11
12
|
Refactor the following code:
|
|
12
|
-
-
|
|
13
|
-
-
|
|
13
|
+
- Only split up long and complex functions
|
|
14
|
+
- Preserve original names and semantics
|
|
14
15
|
- Do NOT insert comments
|
|
16
|
+
- Output the full, valid ${lang.toUpperCase()} code
|
|
15
17
|
|
|
16
18
|
--- CODE START ---
|
|
17
19
|
${input.code}
|
|
18
20
|
--- CODE END ---
|
|
19
|
-
`.trim();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
headers: { 'Content-Type': 'application/json' },
|
|
24
|
-
body: JSON.stringify({
|
|
25
|
-
model,
|
|
26
|
-
prompt,
|
|
27
|
-
stream: false
|
|
28
|
-
})
|
|
29
|
-
});
|
|
30
|
-
if (!res.ok) {
|
|
31
|
-
const errBody = await res.text();
|
|
32
|
-
throw new Error(`β Refactor failed: ${res.status} ${res.statusText} - ${errBody}`);
|
|
33
|
-
}
|
|
34
|
-
const data = await res.json();
|
|
35
|
-
if (!data.response) {
|
|
36
|
-
throw new Error('β No response field returned from model.');
|
|
37
|
-
}
|
|
38
|
-
return { code: data.response.trim() };
|
|
39
|
-
}
|
|
40
|
-
catch (err) {
|
|
41
|
-
console.error('π₯ Error in refactorModule:', err);
|
|
42
|
-
throw new Error('β Error in refactor module: ' + err.message);
|
|
21
|
+
`.trim();
|
|
22
|
+
const response = await generate(prompt, model);
|
|
23
|
+
if (!response) {
|
|
24
|
+
throw new Error('β Model returned empty response for refactoring.');
|
|
43
25
|
}
|
|
26
|
+
return { code: response };
|
|
44
27
|
}
|
|
45
28
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
2
|
+
import { generate } from '../../lib/generate.js';
|
|
2
3
|
export const summaryModule = {
|
|
3
4
|
name: 'summary',
|
|
4
5
|
description: 'Prints a summary of changes to the terminal',
|
|
@@ -26,14 +27,8 @@ Take the following source code and do NOT modify it in any way. Your task is:
|
|
|
26
27
|
--- CODE START ---
|
|
27
28
|
${code}
|
|
28
29
|
--- CODE END ---
|
|
29
|
-
`.trim();
|
|
30
|
-
const
|
|
31
|
-
method: 'POST',
|
|
32
|
-
headers: { 'Content-Type': 'application/json' },
|
|
33
|
-
body: JSON.stringify({ model, prompt, stream: false })
|
|
34
|
-
});
|
|
35
|
-
const data = await res.json();
|
|
36
|
-
const summary = data.response?.trim();
|
|
30
|
+
`.trim();
|
|
31
|
+
const summary = await generate(prompt, model);
|
|
37
32
|
if (summary) {
|
|
38
33
|
console.log('\nπ Code Summary:\n');
|
|
39
34
|
console.log(summary);
|
|
@@ -41,7 +36,7 @@ ${code}
|
|
|
41
36
|
else {
|
|
42
37
|
console.warn('β οΈ No summary generated.');
|
|
43
38
|
}
|
|
44
|
-
// Return unchanged input
|
|
39
|
+
// Return unchanged input for composability
|
|
45
40
|
return { code };
|
|
46
41
|
}
|
|
47
42
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"scai": "./dist/index.js"
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"start": "node dist/index.js"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"commander": "^11.0.0"
|
|
27
|
+
"commander": "^11.0.0",
|
|
28
|
+
"ora": "^8.2.0"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@types/jest": "^30.0.0",
|