scai 0.1.12 → 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 +49 -42
- package/dist/commands/ChangeLogUpdateCmd.js +39 -0
- package/dist/commands/CommitSuggesterCmd.js +6 -31
- package/dist/commands/SummaryCmd.js +29 -5
- package/dist/config/ModelConfig.js +7 -0
- package/dist/index.js +13 -5
- package/dist/lib/generate.js +22 -0
- package/dist/pipeline/modules/changeLogModule.js +21 -0
- package/dist/pipeline/modules/commentModule.js +12 -15
- 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 +15 -15
- package/package.json +3 -2
- package/dist/commands/CommitSuggesterCmd.refactored.js +0 -106
- package/dist/commands/EnvCmd.test.js +0 -146
- package/dist/commands/ReadmeCmd.js +0 -61
package/README.md
CHANGED
|
@@ -1,41 +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
15
|
## 🚀 Features
|
|
15
16
|
|
|
16
|
-
- ⚡️ Powered by
|
|
17
|
+
- ⚡️ Powered by local Ollama models like `llama3` and `codellama`
|
|
17
18
|
- 🛠️ CLI built with Node.js + TypeScript
|
|
18
19
|
- 🔒 No external services, full privacy by design
|
|
19
|
-
- ✅
|
|
20
|
-
|
|
21
|
-
### 📦 Recent Additions
|
|
22
|
-
|
|
23
|
-
* **Summary Command**: Print a summary of the given file to the terminal (e.g., `scai summ <file>`)
|
|
20
|
+
- ✅ Global options for model and language selection
|
|
24
21
|
|
|
25
22
|
---
|
|
26
23
|
|
|
27
|
-
## ❤️
|
|
24
|
+
## ❤️ Why Local AI?
|
|
28
25
|
|
|
29
26
|
We believe your code — and your workflow — should stay **yours**. scai runs entirely on your machine using open-source models and tools.
|
|
30
27
|
|
|
31
|
-
No internet connection. No vendor lock-in. Just local, private, AI-enhanced developer experience.
|
|
32
|
-
|
|
33
28
|
✅ Works entirely offline
|
|
34
|
-
✅ No API keys
|
|
29
|
+
✅ No API keys or cloud dependencies
|
|
35
30
|
✅ Backed by open-source models
|
|
36
|
-
✅ Designed for CLI-first
|
|
37
|
-
|
|
38
|
-
**scai** follows the Unix philosophy — small, composable tools with powerful output.
|
|
31
|
+
✅ Designed for CLI-first developers
|
|
39
32
|
|
|
40
33
|
---
|
|
41
34
|
|
|
@@ -44,28 +37,27 @@ No internet connection. No vendor lock-in. Just local, private, AI-enhanced deve
|
|
|
44
37
|
1. **Install [Ollama](https://ollama.com)**
|
|
45
38
|
- On **Windows**: [Download Ollama](https://ollama.com/download)
|
|
46
39
|
- On **macOS**:
|
|
47
|
-
-
|
|
40
|
+
- Via Homebrew:
|
|
48
41
|
```bash
|
|
49
42
|
brew install ollama
|
|
50
43
|
```
|
|
51
|
-
- Or download directly from the
|
|
52
|
-
- Ensure it’s added to your system `PATH`
|
|
44
|
+
- Or download directly from the website
|
|
53
45
|
|
|
54
|
-
2. **Install scai globally
|
|
46
|
+
2. **Install scai globally:**
|
|
55
47
|
|
|
56
48
|
```bash
|
|
57
49
|
npm install -g scai
|
|
58
50
|
```
|
|
59
51
|
|
|
60
|
-
3. **
|
|
52
|
+
3. **Initialize the tool and models:**
|
|
61
53
|
|
|
62
54
|
```bash
|
|
63
55
|
scai init
|
|
64
56
|
```
|
|
65
57
|
|
|
66
58
|
This will:
|
|
67
|
-
-
|
|
68
|
-
-
|
|
59
|
+
- Start the Ollama background server
|
|
60
|
+
- Download required models (`llama3`, etc.)
|
|
69
61
|
|
|
70
62
|
---
|
|
71
63
|
|
|
@@ -74,10 +66,7 @@ This will:
|
|
|
74
66
|
### 💬 Suggest a commit message
|
|
75
67
|
|
|
76
68
|
```bash
|
|
77
|
-
# Stage your changes
|
|
78
69
|
git add .
|
|
79
|
-
|
|
80
|
-
# Let scai suggest a commit message
|
|
81
70
|
scai sugg
|
|
82
71
|
```
|
|
83
72
|
|
|
@@ -86,7 +75,7 @@ scai sugg
|
|
|
86
75
|
feat(api): add error handling to user service
|
|
87
76
|
```
|
|
88
77
|
|
|
89
|
-
To automatically
|
|
78
|
+
To commit automatically with the suggestion:
|
|
90
79
|
|
|
91
80
|
```bash
|
|
92
81
|
scai sugg --commit
|
|
@@ -94,41 +83,59 @@ scai sugg --commit
|
|
|
94
83
|
|
|
95
84
|
---
|
|
96
85
|
|
|
97
|
-
###
|
|
86
|
+
### ✨ Comment a code file
|
|
98
87
|
|
|
99
88
|
```bash
|
|
100
|
-
scai
|
|
89
|
+
scai comm <file>
|
|
101
90
|
```
|
|
102
91
|
|
|
103
|
-
|
|
104
|
-
- `-a, --apply`
|
|
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
100
|
scai summ <file>
|
|
112
101
|
```
|
|
113
102
|
|
|
114
|
-
|
|
103
|
+
Prints a summary of what the code does directly to your terminal.
|
|
104
|
+
|
|
105
|
+
To pipe input for summarization:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
cat <file> | scai summ
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### 📝 Update the changelog
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
scai changelog
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Analyzes the current Git diff and updates (or creates) a `CHANGELOG.md` file with relevant public-facing changes.
|
|
115
121
|
|
|
116
122
|
---
|
|
117
123
|
|
|
118
124
|
## 🔐 License & Fair Use
|
|
119
125
|
|
|
120
|
-
**scai is free to use** for individuals, teams, and companies — including in commercial work.
|
|
126
|
+
**scai is free to use** for individuals, teams, and companies — including in commercial work.
|
|
127
|
+
|
|
121
128
|
You may:
|
|
122
129
|
|
|
123
130
|
- ✅ Use it internally in your projects
|
|
124
|
-
- ✅ Use it at work or in commercial
|
|
125
|
-
- ✅ Share and recommend it
|
|
131
|
+
- ✅ Use it at work or in commercial development
|
|
132
|
+
- ✅ Share and recommend it
|
|
126
133
|
|
|
127
|
-
|
|
134
|
+
But you may not:
|
|
128
135
|
|
|
129
|
-
- ❌
|
|
130
|
-
- ❌
|
|
136
|
+
- ❌ Repackage or resell **scai** as a product or SaaS
|
|
137
|
+
- ❌ Claim ownership of the tool
|
|
131
138
|
|
|
132
|
-
|
|
139
|
+
See [LICENSE](./LICENSE) for full terms.
|
|
133
140
|
|
|
134
|
-
---
|
|
141
|
+
---
|
|
@@ -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
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
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
8
|
suggestions.forEach((msg, i) => {
|
|
7
9
|
console.log(`${i + 1}) ${msg}`);
|
|
8
10
|
});
|
|
11
|
+
console.log('\n---');
|
|
9
12
|
console.log(`${suggestions.length + 1}) 🔁 Regenerate suggestions`);
|
|
10
13
|
console.log(`${suggestions.length + 2}) ✍️ Write your own commit message`);
|
|
11
14
|
const rl = readline.createInterface({
|
|
@@ -23,32 +26,11 @@ function askUserToChoose(suggestions) {
|
|
|
23
26
|
resolve('custom');
|
|
24
27
|
}
|
|
25
28
|
else {
|
|
26
|
-
resolve(choice - 1);
|
|
29
|
+
resolve(choice - 1);
|
|
27
30
|
}
|
|
28
31
|
});
|
|
29
32
|
});
|
|
30
33
|
}
|
|
31
|
-
async function generateSuggestions(prompt) {
|
|
32
|
-
const res = await fetch("http://localhost:11434/api/generate", {
|
|
33
|
-
method: "POST",
|
|
34
|
-
headers: { "Content-Type": "application/json" },
|
|
35
|
-
body: JSON.stringify({
|
|
36
|
-
model: "llama3",
|
|
37
|
-
prompt,
|
|
38
|
-
stream: false,
|
|
39
|
-
}),
|
|
40
|
-
});
|
|
41
|
-
const { response } = await res.json();
|
|
42
|
-
if (!response || typeof response !== 'string') {
|
|
43
|
-
throw new Error('Invalid LLM response');
|
|
44
|
-
}
|
|
45
|
-
const lines = response.trim().split('\n').filter(line => /^\d+\.\s+/.test(line));
|
|
46
|
-
const messages = lines.map(line => line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
|
|
47
|
-
if (messages.length === 0) {
|
|
48
|
-
throw new Error('No valid commit messages found in LLM response.');
|
|
49
|
-
}
|
|
50
|
-
return messages;
|
|
51
|
-
}
|
|
52
34
|
function promptCustomMessage() {
|
|
53
35
|
return new Promise((resolve) => {
|
|
54
36
|
const rl = readline.createInterface({
|
|
@@ -71,19 +53,12 @@ export async function suggestCommitMessage(options) {
|
|
|
71
53
|
console.log('⚠️ No staged changes to suggest a message for.');
|
|
72
54
|
return;
|
|
73
55
|
}
|
|
74
|
-
|
|
75
|
-
1. ...
|
|
76
|
-
2. ...
|
|
77
|
-
3. ...
|
|
78
|
-
|
|
79
|
-
Here is the diff:
|
|
80
|
-
${diff}`;
|
|
56
|
+
let suggestions = [];
|
|
81
57
|
let message = null;
|
|
82
58
|
while (message === null) {
|
|
83
|
-
|
|
59
|
+
suggestions = await commitSuggesterModule.run({ code: diff }).then(out => out.suggestions || []);
|
|
84
60
|
const choice = await askUserToChoose(suggestions);
|
|
85
61
|
if (choice === suggestions.length) {
|
|
86
|
-
// User chose "Regenerate"
|
|
87
62
|
console.log('\n🔄 Regenerating suggestions...\n');
|
|
88
63
|
continue;
|
|
89
64
|
}
|
|
@@ -1,12 +1,36 @@
|
|
|
1
|
-
// File: src/commands/SummaryCmd.ts
|
|
2
1
|
import fs from 'fs/promises';
|
|
3
2
|
import { summaryModule } from '../pipeline/modules/summaryModule.js';
|
|
3
|
+
import readline from 'readline';
|
|
4
4
|
export async function summarizeFile(filepath) {
|
|
5
|
-
|
|
6
|
-
|
|
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()) {
|
|
7
31
|
await summaryModule.run({ code });
|
|
8
32
|
}
|
|
9
|
-
|
|
10
|
-
console.error(
|
|
33
|
+
else {
|
|
34
|
+
console.error('❌ No code provided to summarize.');
|
|
11
35
|
}
|
|
12
36
|
}
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
export class ModelConfig {
|
|
2
2
|
static setModel(model) {
|
|
3
3
|
this.model = model;
|
|
4
|
+
console.log(`📦 Model set to: ${model}`);
|
|
4
5
|
}
|
|
5
6
|
static getModel() {
|
|
6
7
|
return this.model;
|
|
7
8
|
}
|
|
8
9
|
static setLanguage(lang) {
|
|
9
10
|
this.language = lang;
|
|
11
|
+
console.log(`🗣️ Language set to: ${lang}`);
|
|
10
12
|
}
|
|
11
13
|
static getLanguage() {
|
|
12
14
|
return this.language;
|
|
13
15
|
}
|
|
16
|
+
static logCurrentConfig() {
|
|
17
|
+
console.log(`🔧 Current configuration:`);
|
|
18
|
+
console.log(` Model : ${this.model}`);
|
|
19
|
+
console.log(` Language: ${this.language}`);
|
|
20
|
+
}
|
|
14
21
|
}
|
|
15
22
|
ModelConfig.model = 'codellama:7b';
|
|
16
23
|
ModelConfig.language = 'ts';
|
package/dist/index.js
CHANGED
|
@@ -7,11 +7,11 @@ import { checkEnv } from "./commands/EnvCmd.js";
|
|
|
7
7
|
import { checkGit } from "./commands/GitCmd.js";
|
|
8
8
|
import { suggestCommitMessage } from "./commands/CommitSuggesterCmd.js";
|
|
9
9
|
import { handleRefactor } from "./commands/RefactorCmd.js";
|
|
10
|
-
import { updateReadmeIfNeeded } from "./commands/ReadmeCmd.js";
|
|
11
10
|
import { generateTests } from "./commands/TestGenCmd.js";
|
|
12
11
|
import { bootstrap } from './modelSetup.js';
|
|
13
12
|
import { ModelConfig } from './config/ModelConfig.js';
|
|
14
13
|
import { summarizeFile } from "./commands/SummaryCmd.js";
|
|
14
|
+
import { handleChangelogUpdate } from './commands/ChangeLogUpdateCmd.js';
|
|
15
15
|
// Create the CLI instance
|
|
16
16
|
const cmd = new Command('scai')
|
|
17
17
|
.version(version)
|
|
@@ -36,11 +36,13 @@ cmd
|
|
|
36
36
|
.option('-a, --apply', 'Apply the refactored version to the original file')
|
|
37
37
|
.action((file, options) => handleRefactor(file, options));
|
|
38
38
|
cmd
|
|
39
|
-
.command('
|
|
40
|
-
.description('Update
|
|
41
|
-
.action(
|
|
39
|
+
.command('changelog')
|
|
40
|
+
.description('Update or create the CHANGELOG.md based on current Git diff')
|
|
41
|
+
.action(async () => {
|
|
42
|
+
await handleChangelogUpdate();
|
|
43
|
+
});
|
|
42
44
|
cmd
|
|
43
|
-
.command('summ
|
|
45
|
+
.command('summ [file]')
|
|
44
46
|
.description('Print a summary of the given file to the terminal')
|
|
45
47
|
.action((file) => summarizeFile(file));
|
|
46
48
|
cmd
|
|
@@ -55,6 +57,12 @@ cmd
|
|
|
55
57
|
.command('gen-tests <file>')
|
|
56
58
|
.description('Generate a Jest test file for the specified JS/TS module')
|
|
57
59
|
.action((file) => generateTests(file));
|
|
60
|
+
cmd
|
|
61
|
+
.command('config')
|
|
62
|
+
.description('Show the currently active model and language settings')
|
|
63
|
+
.action(() => {
|
|
64
|
+
ModelConfig.logCurrentConfig();
|
|
65
|
+
});
|
|
58
66
|
// ✅ Now that commands are defined, parse args
|
|
59
67
|
cmd.parse(process.argv);
|
|
60
68
|
// ✅ Apply global options after parsing
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
2
|
+
import { generate } from '../../lib/generate.js';
|
|
3
|
+
export const changelogModule = {
|
|
4
|
+
name: 'changelogModule',
|
|
5
|
+
description: 'Generates changelog entry based on Git diff',
|
|
6
|
+
async run(input) {
|
|
7
|
+
const model = ModelConfig.getModel();
|
|
8
|
+
const prompt = `
|
|
9
|
+
You're an experienced changelog writer. Based on this Git diff, write a markdown bullet-point entry suitable for CHANGELOG.md:
|
|
10
|
+
|
|
11
|
+
--- DIFF START ---
|
|
12
|
+
${input.code}
|
|
13
|
+
--- DIFF END ---
|
|
14
|
+
|
|
15
|
+
✅ If the changes are significant, return a changelog entry.
|
|
16
|
+
❌ If not, return ONLY: "NO UPDATE".
|
|
17
|
+
`.trim();
|
|
18
|
+
const output = await generate(prompt, model);
|
|
19
|
+
return { code: output === 'NO UPDATE' ? '' : output };
|
|
20
|
+
},
|
|
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',
|
|
@@ -6,26 +7,22 @@ export const addCommentsModule = {
|
|
|
6
7
|
const model = ModelConfig.getModel();
|
|
7
8
|
const lang = ModelConfig.getLanguage();
|
|
8
9
|
const prompt = `
|
|
9
|
-
You are a senior ${lang.toUpperCase()} engineer.
|
|
10
|
+
You are a senior ${lang.toUpperCase()} engineer reviewing source code.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
Your task is to add clear and helpful single-line // comments to complex or non-obvious parts of the code.
|
|
13
|
+
|
|
14
|
+
⚠️ VERY IMPORTANT RULES:
|
|
15
|
+
- You MUST return the ENTIRE original code.
|
|
16
|
+
- You MUST NOT remove, replace, reformat, or alter ANY code.
|
|
17
|
+
- Only add single-line // comments in appropriate places.
|
|
18
|
+
- Do NOT wrap the code in markdown or code blocks.
|
|
19
|
+
- The code should be valid ${lang.toUpperCase()} after your changes.
|
|
14
20
|
|
|
15
21
|
--- CODE START ---
|
|
16
22
|
${input.code}
|
|
17
23
|
--- CODE END ---
|
|
18
24
|
`.trim();
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
headers: { 'Content-Type': 'application/json' },
|
|
22
|
-
body: JSON.stringify({
|
|
23
|
-
model,
|
|
24
|
-
prompt,
|
|
25
|
-
stream: false,
|
|
26
|
-
}),
|
|
27
|
-
});
|
|
28
|
-
const data = await res.json();
|
|
29
|
-
return { code: data.response?.trim() ?? '' };
|
|
25
|
+
const output = await generate(prompt, model);
|
|
26
|
+
return { code: output === 'NO UPDATE' ? '' : output };
|
|
30
27
|
},
|
|
31
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',
|
|
@@ -8,27 +9,26 @@ export const summaryModule = {
|
|
|
8
9
|
const prompt = `
|
|
9
10
|
You are a senior ${lang.toUpperCase()} engineer.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
Take the following source code and do NOT modify it in any way. Your task is:
|
|
13
|
+
|
|
14
|
+
1. Output the original code exactly as it is.
|
|
15
|
+
2. After the code, append a short summary in this format:
|
|
13
16
|
|
|
14
17
|
// Summary of code:
|
|
15
|
-
// - [
|
|
16
|
-
// - [
|
|
17
|
-
// -
|
|
18
|
+
// - [What the code does at a high level]
|
|
19
|
+
// - [Main features or components]
|
|
20
|
+
// - [Any interesting logic or patterns]
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
⚠️ IMPORTANT:
|
|
23
|
+
- Do NOT omit or alter any part of the original code.
|
|
24
|
+
- Do NOT insert comments inside the code.
|
|
25
|
+
- Only append the summary AFTER the full code.
|
|
20
26
|
|
|
21
27
|
--- CODE START ---
|
|
22
28
|
${code}
|
|
23
29
|
--- CODE END ---
|
|
24
|
-
`.trim();
|
|
25
|
-
const
|
|
26
|
-
method: 'POST',
|
|
27
|
-
headers: { 'Content-Type': 'application/json' },
|
|
28
|
-
body: JSON.stringify({ model, prompt, stream: false })
|
|
29
|
-
});
|
|
30
|
-
const data = await res.json();
|
|
31
|
-
const summary = data.response?.trim();
|
|
30
|
+
`.trim();
|
|
31
|
+
const summary = await generate(prompt, model);
|
|
32
32
|
if (summary) {
|
|
33
33
|
console.log('\n📝 Code Summary:\n');
|
|
34
34
|
console.log(summary);
|
|
@@ -36,7 +36,7 @@ ${code}
|
|
|
36
36
|
else {
|
|
37
37
|
console.warn('⚠️ No summary generated.');
|
|
38
38
|
}
|
|
39
|
-
// Return unchanged input
|
|
39
|
+
// Return unchanged input for composability
|
|
40
40
|
return { code };
|
|
41
41
|
}
|
|
42
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",
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
2
|
-
import readline from 'readline';
|
|
3
|
-
async function askUserToChoose(suggestions) {
|
|
4
|
-
return new Promise((resolve) => {
|
|
5
|
-
console.log('\n💡 AI-suggested commit messages:\n'); // Display all suggestions to the user
|
|
6
|
-
suggestions.forEach((msg, i) => {
|
|
7
|
-
console.log(`${i + 1}) ${msg}`);
|
|
8
|
-
});
|
|
9
|
-
console.log(`${suggestions.length + 1}) 🔁 Regenerate suggestions`); // Allow the user to regenerate suggestions if they want
|
|
10
|
-
console.log(`${suggestions.length + 2}) ✍️ Write your own commit message`); // Allow the user to write their own commit message
|
|
11
|
-
const rl = readline.createInterface({
|
|
12
|
-
input: process.stdin,
|
|
13
|
-
output: process.stdout,
|
|
14
|
-
});
|
|
15
|
-
rl.question(`\n👉 Choose a commit message [1-${suggestions.length + 2}]: `, // Ask the user to choose from the suggestions or write their own
|
|
16
|
-
(answer) => {
|
|
17
|
-
rl.close();
|
|
18
|
-
const choice = parseInt(answer, 10); // Parse the user's input as a number
|
|
19
|
-
if (isNaN(choice) || choice < 1 || choice > suggestions.length + 2) { // Check that the user has entered a valid number
|
|
20
|
-
console.log('⚠️ Invalid selection. Using the first suggestion by default.'); // Default to the first suggestion if the input is invalid
|
|
21
|
-
resolve(0); // Resolve the promise with the 0-based index (0 to 3)
|
|
22
|
-
}
|
|
23
|
-
else if (choice === suggestions.length + 2) { // If the user has chosen "Write your own commit message"
|
|
24
|
-
resolve('custom'); // Return 'custom' to indicate that the user wants to write their own commit message
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
resolve(choice - 1); // Return the 0-based index (0 to 3) of the selected suggestion
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
}
|
|
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(); // Parse the response from the API as JSON
|
|
43
|
-
if (!response || typeof response !== 'string') { // Check that the response is valid
|
|
44
|
-
throw new Error('Invalid LLM response'); // Throw an error if the response is invalid
|
|
45
|
-
}
|
|
46
|
-
const lines = response.trim().split('\n').filter(line => /^\d+\.\s+/.test(line)); // Split the response into individual lines and filter out any lines that don't start with a number followed by a period
|
|
47
|
-
const messages = lines.map(line => // Map each line to its commit message suggestion
|
|
48
|
-
line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
|
|
49
|
-
if (messages.length === 0) { // Check that there are any commit message suggestions
|
|
50
|
-
throw new Error('No valid commit messages found in LLM response.'); // Throw an error if there are no valid commit message suggestions
|
|
51
|
-
}
|
|
52
|
-
return messages; // Return the array of commit message suggestions
|
|
53
|
-
}
|
|
54
|
-
function promptCustomMessage() {
|
|
55
|
-
return new Promise((resolve) => {
|
|
56
|
-
const rl = readline.createInterface({
|
|
57
|
-
input: process.stdin,
|
|
58
|
-
output: process.stdout,
|
|
59
|
-
});
|
|
60
|
-
rl.question('\n📝 Enter your custom commit message:\n> ', (input) => {
|
|
61
|
-
rl.close();
|
|
62
|
-
resolve(input.trim()); // Resolve the promise with the trimmed input string
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
export async function suggestCommitMessage(options) {
|
|
67
|
-
try {
|
|
68
|
-
let diff = execSync("git diff", { encoding: "utf-8" }).trim(); // Get the current Git diff
|
|
69
|
-
if (!diff) { // Check that there are any changes to stage
|
|
70
|
-
diff = execSync("git diff --cached", { encoding: "utf-8" }).trim(); // If there are no staged changes, check for unstaged changes
|
|
71
|
-
}
|
|
72
|
-
if (!diff) { // If there are still no changes, display a message and return early
|
|
73
|
-
console.log('⚠️ No staged changes to suggest a message for.');
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const prompt = `Suggest 3 concise, conventional Git commit message options for this diff. Return ONLY the commit messages, numbered 1 to 3, like so:
|
|
77
|
-
1. ...
|
|
78
|
-
2. ...
|
|
79
|
-
3. ...
|
|
80
|
-
|
|
81
|
-
Here is the diff:
|
|
82
|
-
${diff}`; // Create a prompt string for the user to enter their own commit message suggestions
|
|
83
|
-
let message = null;
|
|
84
|
-
while (message === null) { // Loop until the user has entered a valid commit message or chosen to write their own
|
|
85
|
-
const suggestions = await generateSuggestions(prompt); // Generate commit message suggestions based on the diff output from Git
|
|
86
|
-
const choice = await askUserToChoose(suggestions); // Ask the user to choose from the suggestions or write their own
|
|
87
|
-
if (choice === 'custom') { // If the user has chosen "Write your own commit message"
|
|
88
|
-
message = await promptCustomMessage(); // Prompt the user for their custom commit message
|
|
89
|
-
break; // Break out of the loop and use the custom commit message
|
|
90
|
-
}
|
|
91
|
-
message = suggestions[choice]; // Use the selected suggestion as the commit message
|
|
92
|
-
}
|
|
93
|
-
console.log(`\n✅ Selected commit message:\n${message}\n`); // Display the selected commit message to the user
|
|
94
|
-
if (options.commit) { // If the user has specified that they want to commit the changes
|
|
95
|
-
const commitDiff = execSync("git diff", { encoding: "utf-8" }).trim(); // Get the current Git diff again, in case anything has changed since the last call to `generateSuggestions`
|
|
96
|
-
if (commitDiff) { // Check that there are any staged changes
|
|
97
|
-
execSync("git add .", { encoding: "utf-8" }); // Stage all changes before committing
|
|
98
|
-
}
|
|
99
|
-
execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' }); // Commit the changes with the selected commit message
|
|
100
|
-
console.log('✅ Committed with selected message.'); // Display a success message to the user
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
catch (err) { // If there is an error in the code
|
|
104
|
-
console.error('❌ Error in commit message suggestion:', err); // Log the error to the console
|
|
105
|
-
}
|
|
106
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Here;
|
|
3
|
-
's a Jest test file for the `checkEnv` function: `` `typescript
|
|
4
|
-
import { checkEnv } from "../src/utils";
|
|
5
|
-
|
|
6
|
-
describe("checkEnv", () => {
|
|
7
|
-
it("should warn when missing required env vars", () => {
|
|
8
|
-
const processEnv = Object.assign({}, process.env);
|
|
9
|
-
delete processEnv.DB_HOST;
|
|
10
|
-
delete processEnv.API_KEY;
|
|
11
|
-
const consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation();
|
|
12
|
-
checkEnv();
|
|
13
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith("❌ Missing env vars: DB_HOST, API_KEY");
|
|
14
|
-
consoleWarnSpy.mockRestore();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("should log a message when all required env vars are set", () => {
|
|
18
|
-
const processEnv = Object.assign({}, process.env);
|
|
19
|
-
checkEnv();
|
|
20
|
-
expect(console.log).toHaveBeenCalledWith("✅ All env vars are set");
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
` ``;
|
|
24
|
-
In;
|
|
25
|
-
this;
|
|
26
|
-
test;
|
|
27
|
-
file, we;
|
|
28
|
-
first;
|
|
29
|
-
var the = ;
|
|
30
|
-
`checkEnv`;
|
|
31
|
-
function from() { }
|
|
32
|
-
the;
|
|
33
|
-
contains;
|
|
34
|
-
it.We;
|
|
35
|
-
then;
|
|
36
|
-
define;
|
|
37
|
-
two;
|
|
38
|
-
test;
|
|
39
|
-
cases: one;
|
|
40
|
-
for (when; required; environment)
|
|
41
|
-
variables;
|
|
42
|
-
are;
|
|
43
|
-
missing;
|
|
44
|
-
and;
|
|
45
|
-
another;
|
|
46
|
-
for (when; all; required)
|
|
47
|
-
environment;
|
|
48
|
-
variables;
|
|
49
|
-
are;
|
|
50
|
-
set.
|
|
51
|
-
;
|
|
52
|
-
In;
|
|
53
|
-
the;
|
|
54
|
-
first;
|
|
55
|
-
test;
|
|
56
|
-
we;
|
|
57
|
-
use `jest.spyOn()`;
|
|
58
|
-
to;
|
|
59
|
-
mock;
|
|
60
|
-
the `console.warn()`;
|
|
61
|
-
method;
|
|
62
|
-
and;
|
|
63
|
-
replace;
|
|
64
|
-
it;
|
|
65
|
-
with (a)
|
|
66
|
-
spy;
|
|
67
|
-
function () { }
|
|
68
|
-
We;
|
|
69
|
-
then;
|
|
70
|
-
delete the `DB_HOST`;
|
|
71
|
-
and `API_KEY`;
|
|
72
|
-
environment;
|
|
73
|
-
variables;
|
|
74
|
-
and;
|
|
75
|
-
call;
|
|
76
|
-
the `checkEnv()`;
|
|
77
|
-
function () { }
|
|
78
|
-
Finally, we;
|
|
79
|
-
check;
|
|
80
|
-
that;
|
|
81
|
-
the `console.warn()`;
|
|
82
|
-
method;
|
|
83
|
-
was;
|
|
84
|
-
called;
|
|
85
|
-
with (the)
|
|
86
|
-
expected;
|
|
87
|
-
message.
|
|
88
|
-
;
|
|
89
|
-
In;
|
|
90
|
-
the;
|
|
91
|
-
second;
|
|
92
|
-
test;
|
|
93
|
-
we;
|
|
94
|
-
simply;
|
|
95
|
-
call;
|
|
96
|
-
the `checkEnv()`;
|
|
97
|
-
function without() { }
|
|
98
|
-
deleting;
|
|
99
|
-
any;
|
|
100
|
-
environment;
|
|
101
|
-
variables;
|
|
102
|
-
and;
|
|
103
|
-
check;
|
|
104
|
-
that;
|
|
105
|
-
the `console.log()`;
|
|
106
|
-
method;
|
|
107
|
-
was;
|
|
108
|
-
called;
|
|
109
|
-
with (the)
|
|
110
|
-
expected;
|
|
111
|
-
message.
|
|
112
|
-
;
|
|
113
|
-
Note;
|
|
114
|
-
that;
|
|
115
|
-
we;
|
|
116
|
-
use;
|
|
117
|
-
the `mockImplementation()`;
|
|
118
|
-
method;
|
|
119
|
-
to;
|
|
120
|
-
mock;
|
|
121
|
-
the `console.warn()`;
|
|
122
|
-
method;
|
|
123
|
-
and;
|
|
124
|
-
the `mockRestore()`;
|
|
125
|
-
method;
|
|
126
|
-
to;
|
|
127
|
-
restore;
|
|
128
|
-
it;
|
|
129
|
-
after;
|
|
130
|
-
the;
|
|
131
|
-
test;
|
|
132
|
-
is;
|
|
133
|
-
done.This;
|
|
134
|
-
is;
|
|
135
|
-
necessary;
|
|
136
|
-
because;
|
|
137
|
-
Jest;
|
|
138
|
-
does;
|
|
139
|
-
not;
|
|
140
|
-
allow;
|
|
141
|
-
us;
|
|
142
|
-
to;
|
|
143
|
-
modify;
|
|
144
|
-
the;
|
|
145
|
-
original `console.warn()`;
|
|
146
|
-
method.;
|
|
@@ -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
|
-
}
|