scai 0.1.11 → 0.1.13
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 +38 -48
- package/dist/commands/ChangeLogUpdateCmd.js +39 -0
- package/dist/commands/CommitSuggesterCmd.js +4 -3
- package/dist/commands/SummaryCmd.js +36 -0
- package/dist/config/ModelConfig.js +23 -0
- package/dist/index.js +43 -19
- package/dist/pipeline/modules/changeLogModule.js +30 -0
- package/dist/pipeline/modules/commentModule.js +14 -6
- package/dist/pipeline/modules/generateTestsModule.js +5 -2
- package/dist/pipeline/modules/refactorModule.js +31 -11
- package/dist/pipeline/modules/summaryModule.js +28 -12
- package/package.json +1 -1
- package/dist/commands/CommitSuggesterCmd.refactored.js +0 -116
- package/dist/commands/ReadmeCmd.js +0 -61
package/README.md
CHANGED
|
@@ -1,38 +1,34 @@
|
|
|
1
1
|
# ⚙️ scai — Smart Commit AI ✨
|
|
2
2
|
|
|
3
|
-
> AI-powered CLI tools for smart commit messages,
|
|
3
|
+
> AI-powered CLI tools for smart commit messages, code comments, summaries, and changelogs — all powered by local models.
|
|
4
4
|
|
|
5
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:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- ✨
|
|
9
|
-
- 🧠
|
|
7
|
+
- 💬 Suggest high-quality Git commit messages
|
|
8
|
+
- ✨ Automatically comment your code
|
|
9
|
+
- 🧠 Summarize code files instantly in the terminal
|
|
10
|
+
- 📝 Generate changelog entries based on Git diffs
|
|
10
11
|
- 🔒 100% local — no API keys, no cloud, no telemetry
|
|
11
12
|
|
|
12
13
|
---
|
|
13
14
|
|
|
14
|
-
**scai** follows the Unix philosophy — small, composable tools with powerful output.
|
|
15
|
-
|
|
16
15
|
## 🚀 Features
|
|
17
16
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
- ⚡️ Powered by open local Ollama models like `mistral`
|
|
21
|
-
- 🛠️ CLI built with Node.js + TypeScript
|
|
17
|
+
- ⚡️ Powered by local Ollama models like `llama3` and `codellama`
|
|
18
|
+
- 🛠️ CLI built with Node.js + TypeScript
|
|
22
19
|
- 🔒 No external services, full privacy by design
|
|
20
|
+
- ✅ Global options for model and language selection
|
|
23
21
|
|
|
24
22
|
---
|
|
25
23
|
|
|
26
|
-
## ❤️
|
|
24
|
+
## ❤️ Why Local AI?
|
|
27
25
|
|
|
28
26
|
We believe your code — and your workflow — should stay **yours**. scai runs entirely on your machine using open-source models and tools.
|
|
29
27
|
|
|
30
|
-
No internet connection. No vendor lock-in. Just local, private, AI-enhanced developer experience.
|
|
31
|
-
|
|
32
28
|
✅ Works entirely offline
|
|
33
|
-
✅ No API keys
|
|
29
|
+
✅ No API keys or cloud dependencies
|
|
34
30
|
✅ Backed by open-source models
|
|
35
|
-
✅ Designed for CLI-first
|
|
31
|
+
✅ Designed for CLI-first developers
|
|
36
32
|
|
|
37
33
|
---
|
|
38
34
|
|
|
@@ -41,28 +37,27 @@ No internet connection. No vendor lock-in. Just local, private, AI-enhanced deve
|
|
|
41
37
|
1. **Install [Ollama](https://ollama.com)**
|
|
42
38
|
- On **Windows**: [Download Ollama](https://ollama.com/download)
|
|
43
39
|
- On **macOS**:
|
|
44
|
-
-
|
|
40
|
+
- Via Homebrew:
|
|
45
41
|
```bash
|
|
46
42
|
brew install ollama
|
|
47
43
|
```
|
|
48
|
-
- Or download directly from the
|
|
49
|
-
- Ensure it’s added to your system `PATH`
|
|
44
|
+
- Or download directly from the website
|
|
50
45
|
|
|
51
|
-
2. **Install scai globally
|
|
46
|
+
2. **Install scai globally:**
|
|
52
47
|
|
|
53
48
|
```bash
|
|
54
49
|
npm install -g scai
|
|
55
50
|
```
|
|
56
51
|
|
|
57
|
-
3. **
|
|
52
|
+
3. **Initialize the tool and models:**
|
|
58
53
|
|
|
59
54
|
```bash
|
|
60
55
|
scai init
|
|
61
56
|
```
|
|
62
57
|
|
|
63
58
|
This will:
|
|
64
|
-
-
|
|
65
|
-
-
|
|
59
|
+
- Start the Ollama background server
|
|
60
|
+
- Download required models (`llama3`, etc.)
|
|
66
61
|
|
|
67
62
|
---
|
|
68
63
|
|
|
@@ -71,11 +66,8 @@ This will:
|
|
|
71
66
|
### 💬 Suggest a commit message
|
|
72
67
|
|
|
73
68
|
```bash
|
|
74
|
-
# Stage your changes
|
|
75
69
|
git add .
|
|
76
|
-
|
|
77
|
-
# Let scai suggest a commit message
|
|
78
|
-
scai suggest
|
|
70
|
+
scai sugg
|
|
79
71
|
```
|
|
80
72
|
|
|
81
73
|
> Example output:
|
|
@@ -83,62 +75,60 @@ scai suggest
|
|
|
83
75
|
feat(api): add error handling to user service
|
|
84
76
|
```
|
|
85
77
|
|
|
86
|
-
To automatically
|
|
78
|
+
To commit automatically with the suggestion:
|
|
87
79
|
|
|
88
80
|
```bash
|
|
89
|
-
scai
|
|
81
|
+
scai sugg --commit
|
|
90
82
|
```
|
|
91
83
|
|
|
92
84
|
---
|
|
93
85
|
|
|
94
|
-
###
|
|
86
|
+
### ✨ Comment a code file
|
|
95
87
|
|
|
96
88
|
```bash
|
|
97
|
-
scai
|
|
89
|
+
scai comm <file>
|
|
98
90
|
```
|
|
99
91
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
path/to/refactored/file.refactored.js
|
|
104
|
-
```
|
|
92
|
+
Adds clear, helpful comments to the code. Optional flag:
|
|
93
|
+
- `-a, --apply` — Overwrite the original file with the commented version
|
|
105
94
|
|
|
106
95
|
---
|
|
107
96
|
|
|
108
|
-
###
|
|
97
|
+
### 🔍 Summarize a code file
|
|
109
98
|
|
|
110
99
|
```bash
|
|
111
|
-
scai
|
|
100
|
+
scai summ <file>
|
|
112
101
|
```
|
|
113
102
|
|
|
114
|
-
|
|
103
|
+
Prints a summary of what the code does directly to your terminal.
|
|
115
104
|
|
|
116
105
|
---
|
|
117
106
|
|
|
118
|
-
###
|
|
107
|
+
### 📝 Update the changelog
|
|
119
108
|
|
|
120
109
|
```bash
|
|
121
|
-
scai
|
|
110
|
+
scai changelog
|
|
122
111
|
```
|
|
123
112
|
|
|
124
|
-
|
|
113
|
+
Analyzes the current Git diff and updates (or creates) a `CHANGELOG.md` file with relevant public-facing changes.
|
|
125
114
|
|
|
126
115
|
---
|
|
127
116
|
|
|
128
117
|
## 🔐 License & Fair Use
|
|
129
118
|
|
|
130
|
-
**scai is free to use** for individuals, teams, and companies — including in commercial work.
|
|
119
|
+
**scai is free to use** for individuals, teams, and companies — including in commercial work.
|
|
120
|
+
|
|
131
121
|
You may:
|
|
132
122
|
|
|
133
123
|
- ✅ Use it internally in your projects
|
|
134
|
-
- ✅ Use it at work or in commercial
|
|
135
|
-
- ✅ Share and recommend it
|
|
124
|
+
- ✅ Use it at work or in commercial development
|
|
125
|
+
- ✅ Share and recommend it
|
|
136
126
|
|
|
137
|
-
|
|
127
|
+
But you may not:
|
|
138
128
|
|
|
139
|
-
- ❌
|
|
140
|
-
- ❌
|
|
129
|
+
- ❌ Repackage or resell **scai** as a product or SaaS
|
|
130
|
+
- ❌ Claim ownership of the tool
|
|
141
131
|
|
|
142
|
-
|
|
132
|
+
See [LICENSE](./LICENSE) for full terms.
|
|
143
133
|
|
|
144
134
|
---
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/commands/handleChangelogUpdate.ts
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { runPromptPipeline } from '../pipeline/runPipeline.js';
|
|
6
|
+
import { changelogModule } from '../pipeline/modules/changeLogModule.js';
|
|
7
|
+
export async function handleChangelogUpdate() {
|
|
8
|
+
try {
|
|
9
|
+
let diff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
10
|
+
if (!diff) {
|
|
11
|
+
diff = execSync("git diff --cached", { encoding: "utf-8" }).trim();
|
|
12
|
+
}
|
|
13
|
+
if (!diff) {
|
|
14
|
+
console.log("⚠️ No staged or unstaged changes to include in changelog.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const result = await runPromptPipeline([changelogModule], { code: diff });
|
|
18
|
+
if (!result.code.trim()) {
|
|
19
|
+
console.log("✅ No significant changes for changelog.");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const root = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
23
|
+
const changelogPath = path.join(root, "CHANGELOG.md");
|
|
24
|
+
let existing = "";
|
|
25
|
+
try {
|
|
26
|
+
existing = await fs.readFile(changelogPath, "utf-8");
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
console.log("📄 Creating new CHANGELOG.md");
|
|
30
|
+
}
|
|
31
|
+
const today = new Date().toISOString().split("T")[0];
|
|
32
|
+
const newEntry = `\n\n## ${today}\n\n${result.code}`;
|
|
33
|
+
await fs.writeFile(changelogPath, existing + newEntry, "utf-8");
|
|
34
|
+
console.log("📝 CHANGELOG.md updated.");
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.error("❌ Failed to update changelog:", err.message);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -3,11 +3,12 @@ import readline from 'readline';
|
|
|
3
3
|
function askUserToChoose(suggestions) {
|
|
4
4
|
return new Promise((resolve) => {
|
|
5
5
|
console.log('\n💡 AI-suggested commit messages:\n');
|
|
6
|
-
suggestions.forEach((msg, i) => {
|
|
6
|
+
suggestions.slice(0, 3).forEach((msg, i) => {
|
|
7
7
|
console.log(`${i + 1}) ${msg}`);
|
|
8
8
|
});
|
|
9
|
-
console.log(
|
|
10
|
-
console.log(
|
|
9
|
+
console.log('\n---');
|
|
10
|
+
console.log('4) 🔁 Regenerate suggestions');
|
|
11
|
+
console.log('5) ✍️ Write your own commit message');
|
|
11
12
|
const rl = readline.createInterface({
|
|
12
13
|
input: process.stdin,
|
|
13
14
|
output: process.stdout,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { summaryModule } from '../pipeline/modules/summaryModule.js';
|
|
3
|
+
import readline from 'readline';
|
|
4
|
+
export async function summarizeFile(filepath) {
|
|
5
|
+
let code = '';
|
|
6
|
+
if (filepath) {
|
|
7
|
+
try {
|
|
8
|
+
code = await fs.readFile(filepath, 'utf-8');
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
console.error(`❌ Could not read or summarize ${filepath}:`, err.message);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// ⬇️ Add this check right here
|
|
16
|
+
else if (process.stdin.isTTY) {
|
|
17
|
+
console.error('❌ No file provided and no piped input.\n👉 Usage: scai summ <file> or cat file | scai summ');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
const rl = readline.createInterface({
|
|
22
|
+
input: process.stdin,
|
|
23
|
+
output: process.stdout,
|
|
24
|
+
terminal: false,
|
|
25
|
+
});
|
|
26
|
+
for await (const line of rl) {
|
|
27
|
+
code += line + '\n';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (code.trim()) {
|
|
31
|
+
await summaryModule.run({ code });
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.error('❌ No code provided to summarize.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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';
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const { version } = require('../package.json');
|
|
3
6
|
import { checkEnv } from "./commands/EnvCmd.js";
|
|
4
7
|
import { checkGit } from "./commands/GitCmd.js";
|
|
5
8
|
import { suggestCommitMessage } from "./commands/CommitSuggesterCmd.js";
|
|
6
9
|
import { handleRefactor } from "./commands/RefactorCmd.js";
|
|
7
|
-
import { updateReadmeIfNeeded } from "./commands/ReadmeCmd.js";
|
|
8
10
|
import { generateTests } from "./commands/TestGenCmd.js";
|
|
9
|
-
// Import the model check and initialization logic
|
|
10
11
|
import { bootstrap } from './modelSetup.js';
|
|
12
|
+
import { ModelConfig } from './config/ModelConfig.js';
|
|
13
|
+
import { summarizeFile } from "./commands/SummaryCmd.js";
|
|
14
|
+
import { handleChangelogUpdate } from './commands/ChangeLogUpdateCmd.js';
|
|
11
15
|
// Create the CLI instance
|
|
12
16
|
const cmd = new Command('scai')
|
|
13
|
-
.version(
|
|
17
|
+
.version(version)
|
|
18
|
+
.option('--model <model>', 'Set the model to use (e.g., codellama:34b)')
|
|
19
|
+
.option('--lang <lang>', 'Set the target language (ts, java, rust)');
|
|
14
20
|
// Define CLI commands
|
|
15
21
|
cmd
|
|
16
22
|
.command('init')
|
|
@@ -20,30 +26,48 @@ cmd
|
|
|
20
26
|
console.log('✅ Model initialization completed!');
|
|
21
27
|
});
|
|
22
28
|
cmd
|
|
23
|
-
.command('
|
|
24
|
-
.description('Check environment variables')
|
|
25
|
-
.action(checkEnv);
|
|
26
|
-
cmd
|
|
27
|
-
.command('git')
|
|
28
|
-
.description('Check Git status')
|
|
29
|
-
.action(checkGit);
|
|
30
|
-
cmd
|
|
31
|
-
.command('suggest')
|
|
29
|
+
.command('sugg')
|
|
32
30
|
.description('Suggest a commit message from staged changes')
|
|
33
31
|
.option('-c, --commit', 'Automatically commit with suggested message')
|
|
34
32
|
.action(suggestCommitMessage);
|
|
35
33
|
cmd
|
|
36
|
-
.command('
|
|
37
|
-
.description('
|
|
34
|
+
.command('comm <file>')
|
|
35
|
+
.description('Write comments for the given file')
|
|
38
36
|
.option('-a, --apply', 'Apply the refactored version to the original file')
|
|
39
37
|
.action((file, options) => handleRefactor(file, options));
|
|
40
38
|
cmd
|
|
41
|
-
.command('
|
|
42
|
-
.description('Update
|
|
43
|
-
.action(
|
|
39
|
+
.command('changelog')
|
|
40
|
+
.description('Update or create the CHANGELOG.md based on current Git diff')
|
|
41
|
+
.action(async () => {
|
|
42
|
+
await handleChangelogUpdate();
|
|
43
|
+
});
|
|
44
|
+
cmd
|
|
45
|
+
.command('summ [file]')
|
|
46
|
+
.description('Print a summary of the given file to the terminal')
|
|
47
|
+
.action((file) => summarizeFile(file));
|
|
44
48
|
cmd
|
|
45
|
-
.command('
|
|
49
|
+
.command('git')
|
|
50
|
+
.description('Check Git status')
|
|
51
|
+
.action(checkGit);
|
|
52
|
+
cmd
|
|
53
|
+
.command('env')
|
|
54
|
+
.description('Check environment variables')
|
|
55
|
+
.action(checkEnv);
|
|
56
|
+
cmd
|
|
57
|
+
.command('gen-tests <file>')
|
|
46
58
|
.description('Generate a Jest test file for the specified JS/TS module')
|
|
47
59
|
.action((file) => generateTests(file));
|
|
48
|
-
|
|
60
|
+
cmd
|
|
61
|
+
.command('config')
|
|
62
|
+
.description('Show the currently active model and language settings')
|
|
63
|
+
.action(() => {
|
|
64
|
+
ModelConfig.logCurrentConfig();
|
|
65
|
+
});
|
|
66
|
+
// ✅ Now that commands are defined, parse args
|
|
49
67
|
cmd.parse(process.argv);
|
|
68
|
+
// ✅ Apply global options after parsing
|
|
69
|
+
const opts = cmd.opts();
|
|
70
|
+
if (opts.model)
|
|
71
|
+
ModelConfig.setModel(opts.model);
|
|
72
|
+
if (opts.lang)
|
|
73
|
+
ModelConfig.setLanguage(opts.lang);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
2
|
+
export const changelogModule = {
|
|
3
|
+
name: 'changelogModule',
|
|
4
|
+
description: 'Generates changelog entry based on Git diff',
|
|
5
|
+
async run(input) {
|
|
6
|
+
const model = ModelConfig.getModel();
|
|
7
|
+
const prompt = `
|
|
8
|
+
You're an experienced changelog writer. Based on this Git diff, write a markdown bullet-point entry suitable for CHANGELOG.md:
|
|
9
|
+
|
|
10
|
+
--- DIFF START ---
|
|
11
|
+
${input.code}
|
|
12
|
+
--- DIFF END ---
|
|
13
|
+
|
|
14
|
+
✅ If the changes are significant, return a changelog entry.
|
|
15
|
+
❌ If not, return ONLY: "NO UPDATE".
|
|
16
|
+
`.trim();
|
|
17
|
+
const res = await fetch('http://localhost:11434/api/generate', {
|
|
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() ?? '';
|
|
28
|
+
return { code: output === 'NO UPDATE' ? '' : output };
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
+
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
1
2
|
export const addCommentsModule = {
|
|
2
3
|
name: 'addComments',
|
|
3
4
|
description: 'Adds meaningful // comments to each block of code',
|
|
4
5
|
async run(input) {
|
|
6
|
+
const model = ModelConfig.getModel();
|
|
7
|
+
const lang = ModelConfig.getLanguage();
|
|
5
8
|
const prompt = `
|
|
6
|
-
You are a senior
|
|
9
|
+
You are a senior ${lang.toUpperCase()} engineer reviewing source code.
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
Your task is to add clear and helpful single-line // comments to complex or non-obvious parts of the code.
|
|
12
|
+
|
|
13
|
+
⚠️ VERY IMPORTANT RULES:
|
|
14
|
+
- You MUST return the ENTIRE original code.
|
|
15
|
+
- You MUST NOT remove, replace, reformat, or alter ANY code.
|
|
16
|
+
- Only add single-line // comments in appropriate places.
|
|
17
|
+
- Do NOT wrap the code in markdown or code blocks.
|
|
18
|
+
- The code should be valid ${lang.toUpperCase()} after your changes.
|
|
11
19
|
|
|
12
20
|
--- CODE START ---
|
|
13
21
|
${input.code}
|
|
@@ -17,8 +25,8 @@ ${input.code}
|
|
|
17
25
|
method: 'POST',
|
|
18
26
|
headers: { 'Content-Type': 'application/json' },
|
|
19
27
|
body: JSON.stringify({
|
|
20
|
-
model
|
|
21
|
-
prompt
|
|
28
|
+
model,
|
|
29
|
+
prompt,
|
|
22
30
|
stream: false,
|
|
23
31
|
}),
|
|
24
32
|
});
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
3
4
|
export const generateTestsModule = {
|
|
4
5
|
name: 'generateTests',
|
|
5
6
|
description: 'Generate a Jest test file for the class/module',
|
|
6
7
|
async run({ code, filepath }) {
|
|
8
|
+
const model = ModelConfig.getModel();
|
|
9
|
+
const lang = ModelConfig.getLanguage();
|
|
7
10
|
const prompt = `
|
|
8
|
-
You
|
|
11
|
+
You are a senior ${lang.toUpperCase()} engineer. Given the following class or module, generate a Jest test file.
|
|
9
12
|
|
|
10
13
|
Guidelines:
|
|
11
14
|
- Use the 'jest' test framework
|
|
@@ -21,7 +24,7 @@ ${code}
|
|
|
21
24
|
method: 'POST',
|
|
22
25
|
headers: { 'Content-Type': 'application/json' },
|
|
23
26
|
body: JSON.stringify({
|
|
24
|
-
model
|
|
27
|
+
model,
|
|
25
28
|
prompt,
|
|
26
29
|
stream: false
|
|
27
30
|
})
|
|
@@ -1,25 +1,45 @@
|
|
|
1
|
+
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
1
2
|
export const refactorModule = {
|
|
2
3
|
name: 'refactor',
|
|
3
4
|
description: 'Break code into small, clean functions',
|
|
4
|
-
async run(
|
|
5
|
+
async run(input) {
|
|
6
|
+
const model = ModelConfig.getModel();
|
|
7
|
+
const lang = ModelConfig.getLanguage();
|
|
5
8
|
const prompt = `
|
|
6
|
-
You are a senior
|
|
9
|
+
You are a senior ${lang.toUpperCase()} engineer.
|
|
7
10
|
|
|
8
11
|
Refactor the following code:
|
|
9
|
-
- Refactor only long and complex functions
|
|
12
|
+
- Refactor only long and complex functions
|
|
10
13
|
- Keep original names and semantics.
|
|
11
14
|
- Do NOT insert comments
|
|
12
15
|
|
|
13
16
|
--- CODE START ---
|
|
14
|
-
${code}
|
|
17
|
+
${input.code}
|
|
15
18
|
--- CODE END ---
|
|
16
19
|
`.trim();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch('http://localhost:11434/api/generate', {
|
|
22
|
+
method: 'POST',
|
|
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);
|
|
43
|
+
}
|
|
24
44
|
}
|
|
25
45
|
};
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
+
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
1
2
|
export const summaryModule = {
|
|
2
3
|
name: 'summary',
|
|
3
|
-
description: '
|
|
4
|
+
description: 'Prints a summary of changes to the terminal',
|
|
4
5
|
async run({ code }) {
|
|
6
|
+
const model = ModelConfig.getModel();
|
|
7
|
+
const lang = ModelConfig.getLanguage();
|
|
5
8
|
const prompt = `
|
|
6
|
-
You are a senior
|
|
9
|
+
You are a senior ${lang.toUpperCase()} engineer.
|
|
7
10
|
|
|
8
|
-
Take the following code and
|
|
9
|
-
but add a summary at the very end in this format:
|
|
11
|
+
Take the following source code and do NOT modify it in any way. Your task is:
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// - Did Y
|
|
14
|
-
// - etc.
|
|
13
|
+
1. Output the original code exactly as it is.
|
|
14
|
+
2. After the code, append a short summary in this format:
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// Summary of code:
|
|
17
|
+
// - [What the code does at a high level]
|
|
18
|
+
// - [Main features or components]
|
|
19
|
+
// - [Any interesting logic or patterns]
|
|
20
|
+
|
|
21
|
+
⚠️ IMPORTANT:
|
|
22
|
+
- Do NOT omit or alter any part of the original code.
|
|
23
|
+
- Do NOT insert comments inside the code.
|
|
24
|
+
- Only append the summary AFTER the full code.
|
|
18
25
|
|
|
19
26
|
--- CODE START ---
|
|
20
27
|
${code}
|
|
@@ -23,9 +30,18 @@ ${code}
|
|
|
23
30
|
const res = await fetch('http://localhost:11434/api/generate', {
|
|
24
31
|
method: 'POST',
|
|
25
32
|
headers: { 'Content-Type': 'application/json' },
|
|
26
|
-
body: JSON.stringify({ model
|
|
33
|
+
body: JSON.stringify({ model, prompt, stream: false })
|
|
27
34
|
});
|
|
28
35
|
const data = await res.json();
|
|
29
|
-
|
|
36
|
+
const summary = data.response?.trim();
|
|
37
|
+
if (summary) {
|
|
38
|
+
console.log('\n📝 Code Summary:\n');
|
|
39
|
+
console.log(summary);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.warn('⚠️ No summary generated.');
|
|
43
|
+
}
|
|
44
|
+
// Return unchanged input so it’s pipe-safe, if reused in pipelines
|
|
45
|
+
return { code };
|
|
30
46
|
}
|
|
31
47
|
};
|
package/package.json
CHANGED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
// Import required modules
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import readline from 'readline';
|
|
4
|
-
// Function to ask the user to choose a commit message suggestion
|
|
5
|
-
async function askUserToChoose(suggestions) {
|
|
6
|
-
// Create an interface for reading and writing to the console
|
|
7
|
-
const rl = readline.createInterface({
|
|
8
|
-
input: process.stdin,
|
|
9
|
-
output: process.stdout,
|
|
10
|
-
});
|
|
11
|
-
// Show AI-suggested commit messages to the user and ask for their choice
|
|
12
|
-
return new Promise((resolve) => {
|
|
13
|
-
console.log('\n💡 AI-suggested commit messages:\n');
|
|
14
|
-
suggestions.forEach((msg, i) => {
|
|
15
|
-
console.log(`${i + 1}) ${msg}`);
|
|
16
|
-
});
|
|
17
|
-
console.log(`${suggestions.length + 1}) 🔁 Regenerate suggestions`);
|
|
18
|
-
console.log(`${suggestions.length + 2}) ✍️ Write your own commit message`);
|
|
19
|
-
rl.question(`\n👉 Choose a commit message [1-${suggestions.length + 2}]: `, (answer) => {
|
|
20
|
-
// Close the readline interface and resolve the promise with user choice
|
|
21
|
-
rl.close();
|
|
22
|
-
const choice = parseInt(answer, 10);
|
|
23
|
-
if (isNaN(choice) || choice < 1 || choice > suggestions.length + 2) {
|
|
24
|
-
console.log('⚠️ Invalid selection. Using the first suggestion by default.');
|
|
25
|
-
resolve(0);
|
|
26
|
-
}
|
|
27
|
-
else if (choice === suggestions.length + 2) {
|
|
28
|
-
resolve('custom');
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
resolve(choice - 1); // Return 0-based index (0 to 3)
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
// Async function to generate commit message suggestions using an API call
|
|
37
|
-
async function generateSuggestions(prompt) {
|
|
38
|
-
// Fetch the suggestions from an API and return them as an array of strings
|
|
39
|
-
const res = await fetch("http://localhost:11434/api/generate", {
|
|
40
|
-
method: "POST",
|
|
41
|
-
headers: { "Content-Type": "application/json" },
|
|
42
|
-
body: JSON.stringify({
|
|
43
|
-
model: "llama3",
|
|
44
|
-
prompt,
|
|
45
|
-
stream: false,
|
|
46
|
-
}),
|
|
47
|
-
});
|
|
48
|
-
const { response } = await res.json();
|
|
49
|
-
// Check for validity of the LLM response and return an array of commit messages
|
|
50
|
-
if (!response || typeof response !== 'string') {
|
|
51
|
-
throw new Error('Invalid LLM response');
|
|
52
|
-
}
|
|
53
|
-
const lines = response.trim().split('\n').filter(line => /^\d+\.\s+/.test(line));
|
|
54
|
-
return lines.map(line => line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
|
|
55
|
-
}
|
|
56
|
-
// Function to prompt the user for a custom commit message
|
|
57
|
-
async function promptCustomMessage() {
|
|
58
|
-
// Read the user's input for their custom commit message
|
|
59
|
-
return new Promise((resolve) => {
|
|
60
|
-
const rl = readline.createInterface({
|
|
61
|
-
input: process.stdin,
|
|
62
|
-
output: process.stdout,
|
|
63
|
-
});
|
|
64
|
-
rl.question('\n📝 Enter your custom commit message:\n> ', (input) => {
|
|
65
|
-
rl.close();
|
|
66
|
-
resolve(input.trim());
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
// Export the main function to suggest a commit message based on user input and generated suggestions
|
|
71
|
-
export async function suggestCommitMessage(options) {
|
|
72
|
-
try {
|
|
73
|
-
let diff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
74
|
-
if (!diff) {
|
|
75
|
-
diff = execSync("git diff --cached", { encoding: "utf-8" }).trim();
|
|
76
|
-
}
|
|
77
|
-
if (!diff) {
|
|
78
|
-
console.log('⚠️ No staged changes to suggest a message for.');
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const prompt = `Suggest 3 concise, conventional Git commit message options for this diff. Return ONLY the commit messages, numbered 1 to 3, like so:
|
|
82
|
-
1. ...
|
|
83
|
-
2. ...
|
|
84
|
-
3. ...
|
|
85
|
-
|
|
86
|
-
Here is the diff:
|
|
87
|
-
${diff}`;
|
|
88
|
-
let message = null;
|
|
89
|
-
while (message === null) {
|
|
90
|
-
const suggestions = await generateSuggestions(prompt);
|
|
91
|
-
const choice = await askUserToChoose(suggestions);
|
|
92
|
-
if (choice === suggestions.length) {
|
|
93
|
-
// User chose "Regenerate"
|
|
94
|
-
console.log('\n🔄 Regenerating suggestions...\n');
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
if (choice === 'custom') {
|
|
98
|
-
message = await promptCustomMessage();
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
message = suggestions[choice];
|
|
102
|
-
}
|
|
103
|
-
console.log(`\n✅ Selected commit message:\n${message}\n`);
|
|
104
|
-
if (options.commit) {
|
|
105
|
-
const commitDiff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
106
|
-
if (commitDiff) {
|
|
107
|
-
execSync("git add .", { encoding: "utf-8" });
|
|
108
|
-
}
|
|
109
|
-
execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
|
|
110
|
-
console.log('✅ Committed with selected message.');
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
catch (err) {
|
|
114
|
-
console.error('❌ Error in commit message suggestion:', err.message);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
export async function updateReadmeIfNeeded() {
|
|
5
|
-
try {
|
|
6
|
-
let diff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
7
|
-
if (!diff) {
|
|
8
|
-
diff = execSync("git diff --cached", { encoding: "utf-8" }).trim();
|
|
9
|
-
}
|
|
10
|
-
if (!diff) {
|
|
11
|
-
console.log('⚠️ No staged changes to suggest a message for.');
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
const readmePath = path.resolve("README.md");
|
|
15
|
-
let readme = "";
|
|
16
|
-
try {
|
|
17
|
-
readme = await fs.readFile(readmePath, "utf-8");
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
console.log("📄 No existing README.md found, skipping update.");
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
const prompt = `
|
|
24
|
-
You're an experienced documentation writer. Here's the current README:
|
|
25
|
-
|
|
26
|
-
--- README START ---
|
|
27
|
-
${readme}
|
|
28
|
-
--- README END ---
|
|
29
|
-
|
|
30
|
-
Here is a Git diff of recent code changes:
|
|
31
|
-
|
|
32
|
-
--- DIFF START ---
|
|
33
|
-
${diff}
|
|
34
|
-
--- DIFF END ---
|
|
35
|
-
|
|
36
|
-
✅ If the changes are significant and relevant to the public-facing documentation, return an updated README.
|
|
37
|
-
❌ If they are not, return ONLY: "NO UPDATE".
|
|
38
|
-
`;
|
|
39
|
-
const res = await fetch("http://localhost:11434/api/generate", {
|
|
40
|
-
method: "POST",
|
|
41
|
-
headers: { "Content-Type": "application/json" },
|
|
42
|
-
body: JSON.stringify({
|
|
43
|
-
model: "llama3",
|
|
44
|
-
prompt,
|
|
45
|
-
stream: false,
|
|
46
|
-
}),
|
|
47
|
-
});
|
|
48
|
-
const { response } = await res.json();
|
|
49
|
-
const result = response.trim();
|
|
50
|
-
if (result === "NO UPDATE") {
|
|
51
|
-
console.log("✅ No significant changes for README.");
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
await fs.writeFile(readmePath, result, "utf-8");
|
|
55
|
-
console.log("📝 README.md updated based on significant changes.");
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch (err) {
|
|
59
|
-
console.error("❌ Failed to update README:", err.message);
|
|
60
|
-
}
|
|
61
|
-
}
|