scai 0.1.7 β 0.1.9
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 +9 -2
- package/dist/commands/CommitSuggesterCmd.js +26 -6
- package/dist/commands/CommitSuggesterCmd.refactored.js +57 -41
- package/dist/commands/GitCmd.refactored.js +12 -0
- package/dist/commands/RefactorCmd.js +1 -2
- package/dist/pipeline/modules/cleanupModule.js +37 -29
- package/dist/pipeline/modules/commentModule.js +3 -3
- package/dist/pipeline/modules/refactorModule.js +3 -6
- package/dist/pipeline/runPipeline.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,13 @@
|
|
|
25
25
|
## π¦ Installation
|
|
26
26
|
|
|
27
27
|
1. **Install [Ollama](https://ollama.com)**
|
|
28
|
-
- On Windows
|
|
28
|
+
- On **Windows**: [Download Ollama](https://ollama.com/download)
|
|
29
|
+
- On **macOS**:
|
|
30
|
+
- Install via Homebrew:
|
|
31
|
+
```bash
|
|
32
|
+
brew install ollama
|
|
33
|
+
```
|
|
34
|
+
- Or download directly from the [Ollama website](https://ollama.com/download)
|
|
29
35
|
- Ensure itβs added to your system `PATH`
|
|
30
36
|
|
|
31
37
|
2. **Install scai globally via npm:**
|
|
@@ -74,7 +80,7 @@ scai commit --commit
|
|
|
74
80
|
### π Refactor a JavaScript file
|
|
75
81
|
|
|
76
82
|
```bash
|
|
77
|
-
scai
|
|
83
|
+
scai refac path/to/file.js
|
|
78
84
|
```
|
|
79
85
|
|
|
80
86
|
A cleaned-up version will be written to:
|
|
@@ -118,3 +124,4 @@ For full terms, see the [LICENSE](./LICENSE) file.
|
|
|
118
124
|
We believe your code β and your workflow β should stay **yours**. scai runs entirely on your machine using open-source models and tools.
|
|
119
125
|
|
|
120
126
|
No internet connection. No vendor lock-in. Just local, private, AI-enhanced developer experience.
|
|
127
|
+
|
|
@@ -7,17 +7,21 @@ function askUserToChoose(suggestions) {
|
|
|
7
7
|
console.log(`${i + 1}) ${msg}`);
|
|
8
8
|
});
|
|
9
9
|
console.log(`${suggestions.length + 1}) π Regenerate suggestions`);
|
|
10
|
+
console.log(`${suggestions.length + 2}) βοΈ Write your own commit message`);
|
|
10
11
|
const rl = readline.createInterface({
|
|
11
12
|
input: process.stdin,
|
|
12
13
|
output: process.stdout,
|
|
13
14
|
});
|
|
14
|
-
rl.question(`\nπ Choose a commit message [1-${suggestions.length +
|
|
15
|
+
rl.question(`\nπ Choose a commit message [1-${suggestions.length + 2}]: `, (answer) => {
|
|
15
16
|
rl.close();
|
|
16
17
|
const choice = parseInt(answer, 10);
|
|
17
|
-
if (isNaN(choice) || choice < 1 || choice > suggestions.length +
|
|
18
|
+
if (isNaN(choice) || choice < 1 || choice > suggestions.length + 2) {
|
|
18
19
|
console.log('β οΈ Invalid selection. Using the first suggestion by default.');
|
|
19
20
|
resolve(0);
|
|
20
21
|
}
|
|
22
|
+
else if (choice === suggestions.length + 2) {
|
|
23
|
+
resolve('custom');
|
|
24
|
+
}
|
|
21
25
|
else {
|
|
22
26
|
resolve(choice - 1); // Return 0-based index (0 to 3)
|
|
23
27
|
}
|
|
@@ -45,6 +49,18 @@ async function generateSuggestions(prompt) {
|
|
|
45
49
|
}
|
|
46
50
|
return messages;
|
|
47
51
|
}
|
|
52
|
+
function promptCustomMessage() {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
const rl = readline.createInterface({
|
|
55
|
+
input: process.stdin,
|
|
56
|
+
output: process.stdout,
|
|
57
|
+
});
|
|
58
|
+
rl.question('\nπ Enter your custom commit message:\n> ', (input) => {
|
|
59
|
+
rl.close();
|
|
60
|
+
resolve(input.trim());
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
48
64
|
export async function suggestCommitMessage(options) {
|
|
49
65
|
try {
|
|
50
66
|
let diff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
@@ -65,13 +81,17 @@ ${diff}`;
|
|
|
65
81
|
let message = null;
|
|
66
82
|
while (message === null) {
|
|
67
83
|
const suggestions = await generateSuggestions(prompt);
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
84
|
+
const choice = await askUserToChoose(suggestions);
|
|
85
|
+
if (choice === suggestions.length) {
|
|
70
86
|
// User chose "Regenerate"
|
|
71
87
|
console.log('\nπ Regenerating suggestions...\n');
|
|
72
88
|
continue;
|
|
73
89
|
}
|
|
74
|
-
|
|
90
|
+
if (choice === 'custom') {
|
|
91
|
+
message = await promptCustomMessage();
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
message = suggestions[choice];
|
|
75
95
|
}
|
|
76
96
|
console.log(`\nβ
Selected commit message:\n${message}\n`);
|
|
77
97
|
if (options.commit) {
|
|
@@ -80,7 +100,7 @@ ${diff}`;
|
|
|
80
100
|
execSync("git add .", { encoding: "utf-8" });
|
|
81
101
|
}
|
|
82
102
|
execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
|
|
83
|
-
console.log('β
Committed with
|
|
103
|
+
console.log('β
Committed with selected message.');
|
|
84
104
|
}
|
|
85
105
|
}
|
|
86
106
|
catch (err) {
|
|
@@ -1,44 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
a;
|
|
11
|
-
summary;
|
|
12
|
-
of;
|
|
13
|
-
refactor;
|
|
14
|
-
at;
|
|
15
|
-
the;
|
|
16
|
-
end: `` `typescript
|
|
17
|
-
// Import necessary modules for executing child processes, fetch requests, and readline interface
|
|
18
|
-
import { execSync } from 'child_process';
|
|
19
|
-
import readline from 'readline';
|
|
20
|
-
import { fetch } from 'node-fetch';
|
|
21
|
-
|
|
22
|
-
// Define an asynchronous function to generate messages based on the provided prompt
|
|
23
|
-
async function generateMessages(prompt: string): Promise<string[]> {
|
|
24
|
-
// ... (Existing code)
|
|
1
|
+
// Prompt function asks user to choose a commit message from given suggestions
|
|
2
|
+
function askUserToChoose(suggestions) {
|
|
3
|
+
// Create and close readline interface after getting user input
|
|
4
|
+
const rl = readline.createInterface({
|
|
5
|
+
input: process.stdin,
|
|
6
|
+
output: process.stdout,
|
|
7
|
+
});
|
|
8
|
+
rl.question(...);
|
|
9
|
+
rl.close();
|
|
25
10
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
11
|
+
// Fetches suggestions from the API and processes the response
|
|
12
|
+
async function generateSuggestions(prompt) {
|
|
13
|
+
// Fetch JSON data from the given API endpoint
|
|
14
|
+
const res = await fetch("http://localhost:11434/api/generate", { ... });
|
|
15
|
+
// Filter out any invalid lines from the response and map them to a list of commit messages
|
|
16
|
+
const messages = lines.map(...);
|
|
30
17
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
18
|
+
// Export function for suggesting and committing a commit message if needed
|
|
19
|
+
export async function suggestCommitMessage(options) {
|
|
20
|
+
try {
|
|
21
|
+
// Get the diff between local changes and staged changes
|
|
22
|
+
let diff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
23
|
+
if (!diff) {
|
|
24
|
+
diff = execSync("git diff --cached", { encoding: "utf-8" }).trim();
|
|
25
|
+
}
|
|
26
|
+
// Check if there are any changes to suggest a message for
|
|
27
|
+
if (!diff) {
|
|
28
|
+
console.log('β οΈ No staged changes to suggest a message for.');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// Construct the prompt based on the diff and generate suggestions using the 'generateSuggestions' function
|
|
32
|
+
const prompt = `...`;
|
|
33
|
+
let message = null;
|
|
34
|
+
// Continuously ask user for their choice until a valid commit message is selected
|
|
35
|
+
while (message === null) {
|
|
36
|
+
const suggestions = await generateSuggestions(prompt);
|
|
37
|
+
// Ask user to choose from the given suggestions and handle 'Regenerate' option
|
|
38
|
+
const choiceIndex = await askUserToChoose(suggestions);
|
|
39
|
+
if (choiceIndex === suggestions.length) {
|
|
40
|
+
console.log('π Regenerating suggestions...\n');
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
message = suggestions[choiceIndex];
|
|
44
|
+
}
|
|
45
|
+
// Print the selected commit message and commit it if needed
|
|
46
|
+
console.log(...);
|
|
47
|
+
if (options.commit) {
|
|
48
|
+
const commitDiff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
49
|
+
if (commitDiff) {
|
|
50
|
+
execSync("git add .", { encoding: "utf-8" });
|
|
51
|
+
}
|
|
52
|
+
execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
|
|
53
|
+
console.log('β
Committed with AI-suggested message.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
// Print error message if an error occurs during the commit message suggestion process
|
|
58
|
+
console.error(...);
|
|
59
|
+
}
|
|
39
60
|
}
|
|
40
|
-
|
|
41
|
-
// Summary of refactor:
|
|
42
|
-
// - Added comments to each block for better understanding.
|
|
43
|
-
// - Extracted the functions generateMessages and askForChoice into separate blocks for code organization.
|
|
44
|
-
` ``;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Import the 'child_process' module from 'node:child_process'.
|
|
3
|
+
// Export a function called 'checkGit', which checks the Git status.
|
|
4
|
+
// Try to execute the command "git rev-parse --is-inside-work-tree" with ignored stdio, ensuring we're in a Git repository.
|
|
5
|
+
// If the Git working directory is clean (no uncommitted changes), log a message indicating this.
|
|
6
|
+
// Otherwise, if there are uncommitted changes, log a warning message.
|
|
7
|
+
// Execute "git status --porcelain" to get a compact representation of the current Git state and store its output as a string.
|
|
8
|
+
// Trim any leading/trailing whitespace from the string.
|
|
9
|
+
// Compare the trimmed string's length to zero. If it is equal to zero, the working directory is clean (length > 0 indicates uncommitted changes).
|
|
10
|
+
// If the Git branch matches the head commit hash of 'origin/main', log a message indicating that the current branch is up-to-date with 'origin/main'.
|
|
11
|
+
// Otherwise, log a message indicating that the current branch is not up-to-date with 'origin/main'.
|
|
12
|
+
// Catch any errors that may occur during Git execution and log an error message.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { runPromptPipeline } from '../pipeline/runPipeline.js';
|
|
4
|
-
import { refactorModule } from '../pipeline/modules/refactorModule.js';
|
|
5
4
|
import { addCommentsModule } from '../pipeline/modules/commentModule.js';
|
|
6
5
|
import { cleanupModule } from '../pipeline/modules/cleanupModule.js';
|
|
7
6
|
export async function handleRefactor(filepath, options = {}) {
|
|
@@ -28,7 +27,7 @@ export async function handleRefactor(filepath, options = {}) {
|
|
|
28
27
|
// Read source code
|
|
29
28
|
const originalCode = await fs.readFile(filepath, 'utf-8');
|
|
30
29
|
// Run through pipeline modules
|
|
31
|
-
const refactored = await runPromptPipeline([
|
|
30
|
+
const refactored = await runPromptPipeline([addCommentsModule, cleanupModule], { code: originalCode });
|
|
32
31
|
if (!refactored.trim())
|
|
33
32
|
throw new Error('β οΈ Model returned empty result');
|
|
34
33
|
// Save refactored output
|
|
@@ -1,34 +1,42 @@
|
|
|
1
|
+
function isNaturalLanguageNoise(line) {
|
|
2
|
+
const trimmed = line.trim().toLowerCase();
|
|
3
|
+
return (trimmed.startsWith('i ') ||
|
|
4
|
+
trimmed.startsWith('here') ||
|
|
5
|
+
trimmed.startsWith('this') ||
|
|
6
|
+
trimmed.startsWith('the following') ||
|
|
7
|
+
trimmed.startsWith('below') ||
|
|
8
|
+
trimmed.startsWith('in this') ||
|
|
9
|
+
trimmed.startsWith('we have') ||
|
|
10
|
+
trimmed.includes('the code above') ||
|
|
11
|
+
trimmed.includes('ensures that') ||
|
|
12
|
+
trimmed.includes('it handles') ||
|
|
13
|
+
trimmed.includes('used to'));
|
|
14
|
+
}
|
|
1
15
|
export const cleanupModule = {
|
|
2
16
|
name: 'cleanup',
|
|
3
|
-
description: '
|
|
17
|
+
description: 'Remove markdown fences and natural language noise from top/bottom of code',
|
|
4
18
|
async run({ code }) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
prompt,
|
|
28
|
-
stream: false
|
|
29
|
-
}),
|
|
30
|
-
});
|
|
31
|
-
const data = await res.json();
|
|
32
|
-
return { code: data.response?.trim() ?? '' };
|
|
19
|
+
let lines = code.trim().split('\n');
|
|
20
|
+
// βββββ Clean top βββββ
|
|
21
|
+
while (lines.length) {
|
|
22
|
+
const line = lines[0].trim();
|
|
23
|
+
if (line === '' || line.startsWith('```') || isNaturalLanguageNoise(line)) {
|
|
24
|
+
lines.shift();
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// βββββ Clean bottom βββββ
|
|
31
|
+
while (lines.length) {
|
|
32
|
+
const line = lines[lines.length - 1].trim();
|
|
33
|
+
if (line === '' || line.startsWith('```') || isNaturalLanguageNoise(line)) {
|
|
34
|
+
lines.pop();
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { code: lines.join('\n').trim() };
|
|
33
41
|
}
|
|
34
42
|
};
|
|
@@ -5,9 +5,9 @@ export const addCommentsModule = {
|
|
|
5
5
|
const prompt = `
|
|
6
6
|
You are a senior JavaScript engineer.
|
|
7
7
|
|
|
8
|
-
Add clear and helpful single-line // comments
|
|
9
|
-
-
|
|
10
|
-
-
|
|
8
|
+
Add clear and helpful single-line // comments to complex parts of the code.
|
|
9
|
+
- Do NOT remove or change in ANY way any parts of the code!
|
|
10
|
+
- Output ALL of the original code with your comments included
|
|
11
11
|
|
|
12
12
|
--- CODE START ---
|
|
13
13
|
${input.code}
|
|
@@ -5,12 +5,9 @@ export const refactorModule = {
|
|
|
5
5
|
const prompt = `
|
|
6
6
|
You are a senior JavaScript/TypeScript engineer.
|
|
7
7
|
|
|
8
|
-
Refactor the code
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
- do not include any libraries that were not in the original file
|
|
12
|
-
|
|
13
|
-
ONLY return the plain refactored code.
|
|
8
|
+
Refactor the following code:
|
|
9
|
+
- Refactor all functions longer than 20 lines by extracting logical parts into smaller helper functions.
|
|
10
|
+
- Each resulting function should aim for clarity and ideally be under 20 lines, unless splitting would harm readability or functionality.
|
|
14
11
|
|
|
15
12
|
--- CODE START ---
|
|
16
13
|
${code}
|
|
@@ -2,8 +2,8 @@ export async function runPromptPipeline(modules, input) {
|
|
|
2
2
|
let current = input;
|
|
3
3
|
console.log('Input: ', input);
|
|
4
4
|
for (const mod of modules) {
|
|
5
|
-
console.log(`βοΈ Running: ${mod.name}`);
|
|
6
5
|
current = await mod.run(current);
|
|
6
|
+
console.log(`βοΈ Running: ${mod.name}`);
|
|
7
7
|
console.log("Current: ", current);
|
|
8
8
|
}
|
|
9
9
|
return current.code;
|