git-aic 1.1.0 → 1.2.1
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 +67 -22
- package/dist/cli.js +99 -7
- package/dist/config.js +20 -0
- package/dist/confirm.js +8 -13
- package/dist/editor.js +83 -0
- package/dist/git.js +36 -0
- package/dist/llm.js +2 -2
- package/dist/prompt.js +49 -43
- package/package.json +2 -2
- package/t.txt +0 -32
package/README.md
CHANGED
|
@@ -21,13 +21,16 @@ Your workflow. Your control.
|
|
|
21
21
|
- **Full Control Over Rules**
|
|
22
22
|
Modify the system prompt to enforce your own commit conventions and formatting style.
|
|
23
23
|
|
|
24
|
+
- **Flexible Prompt Management**
|
|
25
|
+
Edit prompts globally or per repository, and set them from your editor, direct text, or a file.
|
|
26
|
+
|
|
24
27
|
- **Conventional Commits Compliance**
|
|
25
28
|
Strictly follows formats like `feat:`, `fix:`, `refactor:`, `chore:`.
|
|
26
29
|
|
|
27
30
|
- **Commit Confirmation & Editing**
|
|
28
31
|
Before committing, you can:
|
|
29
32
|
- Accept the suggested commit message
|
|
30
|
-
- Edit the message
|
|
33
|
+
- Edit the full message in your editor
|
|
31
34
|
- Reject it
|
|
32
35
|
- Retry generation
|
|
33
36
|
|
|
@@ -122,9 +125,9 @@ To view your current config:
|
|
|
122
125
|
git aic config
|
|
123
126
|
```
|
|
124
127
|
|
|
125
|
-
>
|
|
128
|
+
> **Note:** It is masked by default for security reasons.
|
|
126
129
|
|
|
127
|
-
To view the
|
|
130
|
+
To view the full saved API key:
|
|
128
131
|
|
|
129
132
|
```bash
|
|
130
133
|
git aic config --show
|
|
@@ -160,6 +163,7 @@ git aic
|
|
|
160
163
|
|
|
161
164
|
- Prompts you with a generated commit message.
|
|
162
165
|
- You can **accept, edit, reject, or retry** the message.
|
|
166
|
+
- Editing opens your editor and supports multiline commit messages.
|
|
163
167
|
|
|
164
168
|
### Commit and Link to Issue
|
|
165
169
|
|
|
@@ -191,26 +195,73 @@ git aic config
|
|
|
191
195
|
|
|
192
196
|
- Displays your saved config.
|
|
193
197
|
|
|
194
|
-
|
|
198
|
+
### Manage Prompts
|
|
195
199
|
|
|
196
|
-
|
|
200
|
+
Edit the global prompt:
|
|
197
201
|
|
|
198
|
-
|
|
202
|
+
```bash
|
|
203
|
+
git aic prompt edit
|
|
204
|
+
```
|
|
199
205
|
|
|
200
|
-
|
|
201
|
-
2. Builds a strict system prompt
|
|
202
|
-
3. Sends the diff to Gemini
|
|
203
|
-
4. Enforces Conventional Commit formatting
|
|
204
|
-
5. Prompts for commit confirmation (accept, edit, retry, reject)
|
|
205
|
-
6. Executes `git commit` automatically
|
|
206
|
-
7. Optionally pushes if `-p` flag is used
|
|
206
|
+
Set the global prompt directly from text:
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
```bash
|
|
209
|
+
git aic prompt edit --text "Write concise conventional commits with a short body when needed."
|
|
210
|
+
```
|
|
209
211
|
|
|
212
|
+
Load the global prompt from a file:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
git aic prompt edit --file ./prompt.txt
|
|
210
216
|
```
|
|
211
|
-
|
|
217
|
+
|
|
218
|
+
Reset the global prompt:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
git aic prompt reset
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Edit a repository-local prompt:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
git aic prompt edit --local
|
|
212
228
|
```
|
|
213
229
|
|
|
230
|
+
Set a repository-local prompt from text:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
git aic prompt edit --local --text "Use a short subject and a clear explanatory body."
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Load a repository-local prompt from a file:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
git aic prompt edit --local --file ./commit-prompt.txt
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Reset the local prompt:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
git aic prompt reset --local
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Prompt resolution order:
|
|
249
|
+
|
|
250
|
+
1. local prompt from Git config
|
|
251
|
+
2. global prompt from Git-AIC config
|
|
252
|
+
3. built-in default prompt
|
|
253
|
+
|
|
254
|
+
## How It Works
|
|
255
|
+
|
|
256
|
+
1. Captures your staged Git diff
|
|
257
|
+
2. Builds a strict system prompt
|
|
258
|
+
3. Resolves the active prompt from local, global, or default settings
|
|
259
|
+
4. Sends the prompt and diff to Gemini
|
|
260
|
+
5. Prompts for commit confirmation (accept, edit, retry, reject)
|
|
261
|
+
6. Opens your editor when you choose to edit the generated message
|
|
262
|
+
7. Executes `git commit` automatically
|
|
263
|
+
8. Optionally pushes if `-p` flag is used
|
|
264
|
+
|
|
214
265
|
---
|
|
215
266
|
|
|
216
267
|
## Technologies Used
|
|
@@ -247,7 +298,7 @@ You are **building them to fit your process.**
|
|
|
247
298
|
|
|
248
299
|
## License
|
|
249
300
|
|
|
250
|
-
[**ISC License**](https://github.com/Spectra010s/git-aic/main
|
|
301
|
+
[**ISC License**](https://github.com/Spectra010s/git-aic/blob/main/LICENSE)
|
|
251
302
|
|
|
252
303
|
## Author
|
|
253
304
|
|
|
@@ -261,9 +312,3 @@ Spectra010s
|
|
|
261
312
|
This project is a fork and standalone version of:
|
|
262
313
|
|
|
263
314
|
[https://github.com/samueltuoyo15/Commit-Message-Tool](https://github.com/samueltuoyo15/Commit-Message-Tool)
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-

|
|
268
|
-

|
|
269
|
-

|
package/dist/cli.js
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
|
+
import { join, dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
2
6
|
import { Command } from "commander";
|
|
3
7
|
import { simpleGit } from "simple-git";
|
|
4
8
|
import chalk from "chalk";
|
|
5
|
-
import { getGitDiff } from "./git.js";
|
|
9
|
+
import { ensureInsideGitRepo, getGitDiff, getLocalPrompt, resetLocalPrompt, setLocalPrompt, } from "./git.js";
|
|
6
10
|
import { generateCommitMessage } from "./llm.js";
|
|
7
11
|
import { getUserConfirmation } from "./confirm.js";
|
|
8
|
-
import {
|
|
12
|
+
import { editPromptInEditor } from "./editor.js";
|
|
13
|
+
import { getConfig, resetCustomPrompt, setApiKey, setCustomPrompt, } from "./config.js";
|
|
14
|
+
import { DEFAULT_SYSTEM_PROMPT } from "./prompt.js";
|
|
9
15
|
process.on("SIGINT", () => {
|
|
10
16
|
process.exit(0);
|
|
11
17
|
});
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
12
20
|
const git = simpleGit();
|
|
13
21
|
const program = new Command();
|
|
22
|
+
function getErrorMessage(error) {
|
|
23
|
+
return error instanceof Error ? error.message : String(error);
|
|
24
|
+
}
|
|
14
25
|
program
|
|
15
|
-
.name(
|
|
16
|
-
.description(
|
|
17
|
-
.version(
|
|
26
|
+
.name(pkg.name)
|
|
27
|
+
.description(pkg.description)
|
|
28
|
+
.version(pkg.version)
|
|
18
29
|
.option("-p, --push", "push after committing")
|
|
19
30
|
.option("-i, --issue <number>", "Link commit to GitHub issue");
|
|
20
31
|
program
|
|
@@ -44,8 +55,89 @@ program
|
|
|
44
55
|
console.log(chalk.yellow("No API key set"));
|
|
45
56
|
}
|
|
46
57
|
});
|
|
58
|
+
const promptCommand = program
|
|
59
|
+
.command("prompt")
|
|
60
|
+
.description("Manage the system prompt used for commit generation");
|
|
61
|
+
promptCommand
|
|
62
|
+
.command("edit")
|
|
63
|
+
.description("Edit and save a custom system prompt in your editor")
|
|
64
|
+
.option("--local", "Save the custom prompt in the current repository")
|
|
65
|
+
.option("--global", "Save the custom prompt in the global git-aic config")
|
|
66
|
+
.option("-t, --text <prompt>", "Set the custom prompt from a string")
|
|
67
|
+
.option("-f, --file <path>", "Set the custom prompt from a file")
|
|
68
|
+
.action(async (options) => {
|
|
69
|
+
try {
|
|
70
|
+
const cfg = await getConfig();
|
|
71
|
+
if (options.local && options.global) {
|
|
72
|
+
console.log(chalk.red("Use either --local or --global, not both at the same time."));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
if (options.text && options.file) {
|
|
76
|
+
console.log(chalk.red("Use either --text or --file, not both at the same time."));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
if (options.local) {
|
|
80
|
+
await ensureInsideGitRepo();
|
|
81
|
+
}
|
|
82
|
+
let editedPrompt = "";
|
|
83
|
+
if (options.text) {
|
|
84
|
+
editedPrompt = options.text.trim();
|
|
85
|
+
}
|
|
86
|
+
else if (options.file) {
|
|
87
|
+
editedPrompt = (await fs.readFile(options.file, "utf-8")).trim();
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const startingPrompt = options.local
|
|
91
|
+
? ((await getLocalPrompt()) ?? cfg.customPrompt ?? DEFAULT_SYSTEM_PROMPT)
|
|
92
|
+
: (cfg.customPrompt ?? DEFAULT_SYSTEM_PROMPT);
|
|
93
|
+
editedPrompt = await editPromptInEditor(startingPrompt);
|
|
94
|
+
}
|
|
95
|
+
if (!editedPrompt) {
|
|
96
|
+
console.log(chalk.red("Prompt was empty. Nothing saved."));
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
if (options.local) {
|
|
100
|
+
await setLocalPrompt(editedPrompt);
|
|
101
|
+
console.log(chalk.green("Local system prompt saved successfully!"));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
await setCustomPrompt(editedPrompt);
|
|
105
|
+
console.log(chalk.green("Global system prompt saved successfully!"));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error(chalk.red("Prompt edit failed:"), getErrorMessage(error));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
promptCommand
|
|
114
|
+
.command("reset")
|
|
115
|
+
.description("Reset the system prompt back to the default")
|
|
116
|
+
.option("--local", "Reset the prompt in the current repository")
|
|
117
|
+
.option("--global", "Reset the prompt in the global git-aic config")
|
|
118
|
+
.action(async (options) => {
|
|
119
|
+
try {
|
|
120
|
+
if (options.local && options.global) {
|
|
121
|
+
console.log(chalk.red("Use either --local or --global, not both at the same time."));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
if (options.local) {
|
|
125
|
+
await ensureInsideGitRepo();
|
|
126
|
+
await resetLocalPrompt();
|
|
127
|
+
console.log(chalk.green("Local system prompt reset to default."));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
await resetCustomPrompt();
|
|
131
|
+
console.log(chalk.green("Global system prompt reset to default."));
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.error(chalk.red("Prompt reset failed:"), getErrorMessage(error));
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
47
138
|
program.action(async (options) => {
|
|
48
139
|
try {
|
|
140
|
+
await ensureInsideGitRepo();
|
|
49
141
|
const diff = await getGitDiff();
|
|
50
142
|
if (!diff) {
|
|
51
143
|
console.log(chalk.yellow("No changes to commit!"));
|
|
@@ -92,8 +184,8 @@ program.action(async (options) => {
|
|
|
92
184
|
const e = error;
|
|
93
185
|
isAbort = e.code === "ABORT_ERR";
|
|
94
186
|
}
|
|
95
|
-
const msg = isAbort ? "Operation cancelled" : error;
|
|
96
|
-
console.error(chalk.red("\
|
|
187
|
+
const msg = isAbort ? "Operation cancelled" : getErrorMessage(error);
|
|
188
|
+
console.error(chalk.red("\nCommit failed:"), msg);
|
|
97
189
|
process.exit(isAbort ? 0 : 1);
|
|
98
190
|
}
|
|
99
191
|
});
|
package/dist/config.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "fs/promises";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import os from "os";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
+
import { getLocalPrompt } from "./git.js";
|
|
5
6
|
function getConfigPath() {
|
|
6
7
|
const toolName = "git-aic";
|
|
7
8
|
if (process.platform === "win32") {
|
|
@@ -39,3 +40,22 @@ export async function saveConfig(data) {
|
|
|
39
40
|
export async function setApiKey(key) {
|
|
40
41
|
await saveConfig({ apiKey: key });
|
|
41
42
|
}
|
|
43
|
+
export async function setCustomPrompt(prompt) {
|
|
44
|
+
await saveConfig({ customPrompt: prompt });
|
|
45
|
+
}
|
|
46
|
+
export async function resetCustomPrompt() {
|
|
47
|
+
const { customPrompt, ...rest } = await getConfig();
|
|
48
|
+
void customPrompt;
|
|
49
|
+
const configPath = getConfigPath();
|
|
50
|
+
const dir = path.dirname(configPath);
|
|
51
|
+
await fs.mkdir(dir, { recursive: true });
|
|
52
|
+
await fs.writeFile(configPath, JSON.stringify(rest, null, 2), "utf-8");
|
|
53
|
+
}
|
|
54
|
+
export async function getResolvedPrompt() {
|
|
55
|
+
const localPrompt = await getLocalPrompt();
|
|
56
|
+
if (localPrompt) {
|
|
57
|
+
return localPrompt;
|
|
58
|
+
}
|
|
59
|
+
const config = await getConfig();
|
|
60
|
+
return config.customPrompt;
|
|
61
|
+
}
|
package/dist/confirm.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import readline from "node:readline/promises";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
+
import { editTextInEditor } from "./editor.js";
|
|
3
4
|
export const getUserConfirmation = async (message) => {
|
|
4
5
|
const rl = readline.createInterface({
|
|
5
6
|
input: process.stdin,
|
|
@@ -10,22 +11,16 @@ export const getUserConfirmation = async (message) => {
|
|
|
10
11
|
rl.close();
|
|
11
12
|
const choice = (answer.toLowerCase() || "y").trim();
|
|
12
13
|
if (choice === "e") {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
editRl.write(message);
|
|
20
|
-
let editedMessage = "";
|
|
21
|
-
while (!editedMessage.trim()) {
|
|
22
|
-
editedMessage = await editRl.question("> ");
|
|
14
|
+
let editedMessage = message;
|
|
15
|
+
while (true) {
|
|
16
|
+
editedMessage = await editTextInEditor(editedMessage, "git-aic-commit-", "COMMIT_EDITMSG");
|
|
17
|
+
if (editedMessage.trim()) {
|
|
18
|
+
return { choice: "y", message: editedMessage };
|
|
19
|
+
}
|
|
23
20
|
if (!editedMessage.trim()) {
|
|
24
|
-
console.log(chalk.red("Commit message cannot be empty. Please
|
|
21
|
+
console.log(chalk.red("Commit message cannot be empty. Please edit it again."));
|
|
25
22
|
}
|
|
26
23
|
}
|
|
27
|
-
editRl.close();
|
|
28
|
-
return { choice: "y", message: editedMessage };
|
|
29
24
|
}
|
|
30
25
|
return { choice, message };
|
|
31
26
|
};
|
package/dist/editor.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
function splitCommand(command) {
|
|
6
|
+
const parts = [];
|
|
7
|
+
let current = "";
|
|
8
|
+
let quote = null;
|
|
9
|
+
let escaping = false;
|
|
10
|
+
for (const char of command.trim()) {
|
|
11
|
+
if (escaping) {
|
|
12
|
+
current += char;
|
|
13
|
+
escaping = false;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (char === "\\") {
|
|
17
|
+
escaping = true;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (quote) {
|
|
21
|
+
if (char === quote) {
|
|
22
|
+
quote = null;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
current += char;
|
|
26
|
+
}
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (char === '"' || char === "'") {
|
|
30
|
+
quote = char;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (/\s/.test(char)) {
|
|
34
|
+
if (current) {
|
|
35
|
+
parts.push(current);
|
|
36
|
+
current = "";
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
current += char;
|
|
41
|
+
}
|
|
42
|
+
if (escaping || quote) {
|
|
43
|
+
throw new Error("Editor command has invalid quoting.");
|
|
44
|
+
}
|
|
45
|
+
if (current) {
|
|
46
|
+
parts.push(current);
|
|
47
|
+
}
|
|
48
|
+
return parts;
|
|
49
|
+
}
|
|
50
|
+
export function openInEditor(filePath) {
|
|
51
|
+
const editor = process.env.VISUAL || process.env.EDITOR || "editor";
|
|
52
|
+
const [editorCommand, ...editorArgs] = splitCommand(editor);
|
|
53
|
+
if (!editorCommand) {
|
|
54
|
+
throw new Error("No editor found. Set VISUAL or EDITOR, or install an `editor` command.");
|
|
55
|
+
}
|
|
56
|
+
const result = spawnSync(editorCommand, [...editorArgs, filePath], {
|
|
57
|
+
stdio: "inherit",
|
|
58
|
+
});
|
|
59
|
+
if (result.error) {
|
|
60
|
+
if ("code" in result.error && result.error.code === "ENOENT") {
|
|
61
|
+
throw new Error("No editor found. Set VISUAL or EDITOR, or install an `editor` command.");
|
|
62
|
+
}
|
|
63
|
+
throw result.error;
|
|
64
|
+
}
|
|
65
|
+
if (result.status !== 0) {
|
|
66
|
+
throw new Error(`Editor exited with status ${result.status}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export async function editTextInEditor(initialText, prefix = "git-aic-", fileName = "message.txt") {
|
|
70
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
71
|
+
const tempFile = path.join(tempDir, fileName);
|
|
72
|
+
try {
|
|
73
|
+
await fs.writeFile(tempFile, `${initialText}\n`, "utf-8");
|
|
74
|
+
openInEditor(tempFile);
|
|
75
|
+
return (await fs.readFile(tempFile, "utf-8")).trim();
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function editPromptInEditor(initialPrompt) {
|
|
82
|
+
return editTextInEditor(initialPrompt, "git-aic-prompt-", "prompt.txt");
|
|
83
|
+
}
|
package/dist/git.js
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { simpleGit } from "simple-git";
|
|
2
2
|
const git = simpleGit();
|
|
3
|
+
const LOCAL_PROMPT_KEY = "aic.prompt";
|
|
4
|
+
export async function isInsideGitRepo() {
|
|
5
|
+
try {
|
|
6
|
+
const result = await git.raw(["rev-parse", "--is-inside-work-tree"]);
|
|
7
|
+
return result.trim() === "true";
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function ensureInsideGitRepo() {
|
|
14
|
+
if (!(await isInsideGitRepo())) {
|
|
15
|
+
throw new Error("Not inside a Git repository.");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
3
18
|
export const getGitDiff = async () => {
|
|
4
19
|
try {
|
|
5
20
|
await git.raw(["config", "core.autocrlf", "true"]);
|
|
@@ -18,3 +33,24 @@ export const getGitDiff = async () => {
|
|
|
18
33
|
return "";
|
|
19
34
|
}
|
|
20
35
|
};
|
|
36
|
+
export async function getLocalPrompt() {
|
|
37
|
+
try {
|
|
38
|
+
const prompt = await git.raw(["config", "--local", "--get", LOCAL_PROMPT_KEY]);
|
|
39
|
+
const trimmed = prompt.trim();
|
|
40
|
+
return trimmed || undefined;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export async function setLocalPrompt(prompt) {
|
|
47
|
+
await git.raw(["config", "--local", LOCAL_PROMPT_KEY, prompt]);
|
|
48
|
+
}
|
|
49
|
+
export async function resetLocalPrompt() {
|
|
50
|
+
try {
|
|
51
|
+
await git.raw(["config", "--local", "--unset", LOCAL_PROMPT_KEY]);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
package/dist/llm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { buildPrompt } from "./prompt.js";
|
|
4
|
-
import { getConfig } from "./config.js";
|
|
4
|
+
import { getConfig, getResolvedPrompt } from "./config.js";
|
|
5
5
|
export const generateCommitMessage = async (rawDiff) => {
|
|
6
6
|
const API_URL = "https://generativelanguage.googleapis.com/v1/models/gemini-2.5-flash:generateContent";
|
|
7
7
|
const config = await getConfig();
|
|
@@ -18,7 +18,7 @@ export const generateCommitMessage = async (rawDiff) => {
|
|
|
18
18
|
console.log(chalk.gray("After setting the key, restart your terminal.\n"));
|
|
19
19
|
process.exit(1);
|
|
20
20
|
}
|
|
21
|
-
const prompt = buildPrompt(rawDiff);
|
|
21
|
+
const prompt = buildPrompt(rawDiff, await getResolvedPrompt());
|
|
22
22
|
try {
|
|
23
23
|
const response = await axios.post(API_URL, {
|
|
24
24
|
contents: [{ parts: [{ text: prompt }] }],
|
package/dist/prompt.js
CHANGED
|
@@ -1,44 +1,50 @@
|
|
|
1
|
-
export const
|
|
2
|
-
CRITICAL INSTRUCTIONS - READ CAREFULLY:
|
|
3
|
-
You are an expert Git commit message writer. You MUST follow ALL these rules:
|
|
4
|
-
|
|
5
|
-
1. FORMAT: Use Conventional Commits format: <type>(<scope>): <description>
|
|
6
|
-
- type: MUST be one of: feat, fix, refactor, chore, docs, style, test, perf
|
|
7
|
-
- scope: Should be the module/file affected (e.g., "auth", "api", "ui", "config")
|
|
8
|
-
- description: Clear, imperative description in present tense
|
|
9
|
-
|
|
10
|
-
2. DESCRIPTION REQUIREMENTS:
|
|
11
|
-
- Start with an imperative verb (add, fix, remove, update, refactor, etc.)
|
|
12
|
-
- Be specific about what changed
|
|
13
|
-
- Keep it under 72 characters total (including type and scope)
|
|
14
|
-
- NO trailing punctuation
|
|
15
|
-
- NO emojis ever
|
|
16
|
-
- MUST be a complete sentence
|
|
17
|
-
|
|
18
|
-
3. MESSAGE STRUCTURE:
|
|
19
|
-
- The entire commit message must be exactly one line
|
|
20
|
-
- Format: type(scope): description
|
|
21
|
-
- Example: "feat(auth): add password reset functionality"
|
|
22
|
-
- Example: "fix(api): handle null response in user endpoint"
|
|
23
|
-
- Example: "refactor(ui): simplify component state management"
|
|
24
|
-
|
|
25
|
-
4. QUALITY CHECKS - YOUR OUTPUT MUST PASS:
|
|
26
|
-
- Contains opening and closing parentheses
|
|
27
|
-
- Has a colon after the parentheses
|
|
28
|
-
- Description exists and is not empty
|
|
29
|
-
- Total length ≤ 72 characters
|
|
30
|
-
- No markdown formatting
|
|
31
|
-
- No code blocks
|
|
32
|
-
- No explanations or notes
|
|
33
|
-
|
|
34
|
-
5. FAILURE MODE:
|
|
35
|
-
- If you cannot generate a proper message, return exactly: "chore: update code"
|
|
36
|
-
|
|
37
|
-
YOUR TASK:
|
|
38
|
-
Analyze this git diff and generate exactly ONE proper commit message following all rules above.
|
|
39
|
-
|
|
40
|
-
Git diff:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Commit message:
|
|
1
|
+
export const DEFAULT_SYSTEM_PROMPT = `
|
|
2
|
+
CRITICAL INSTRUCTIONS - READ CAREFULLY:
|
|
3
|
+
You are an expert Git commit message writer. You MUST follow ALL these rules:
|
|
4
|
+
|
|
5
|
+
1. FORMAT: Use Conventional Commits format: <type>(<scope>): <description>
|
|
6
|
+
- type: MUST be one of: feat, fix, refactor, chore, docs, style, test, perf
|
|
7
|
+
- scope: Should be the module/file affected (e.g., "auth", "api", "ui", "config")
|
|
8
|
+
- description: Clear, imperative description in present tense
|
|
9
|
+
|
|
10
|
+
2. DESCRIPTION REQUIREMENTS:
|
|
11
|
+
- Start with an imperative verb (add, fix, remove, update, refactor, etc.)
|
|
12
|
+
- Be specific about what changed
|
|
13
|
+
- Keep it under 72 characters total (including type and scope)
|
|
14
|
+
- NO trailing punctuation
|
|
15
|
+
- NO emojis ever
|
|
16
|
+
- MUST be a complete sentence
|
|
17
|
+
|
|
18
|
+
3. MESSAGE STRUCTURE:
|
|
19
|
+
- The entire commit message must be exactly one line
|
|
20
|
+
- Format: type(scope): description
|
|
21
|
+
- Example: "feat(auth): add password reset functionality"
|
|
22
|
+
- Example: "fix(api): handle null response in user endpoint"
|
|
23
|
+
- Example: "refactor(ui): simplify component state management"
|
|
24
|
+
|
|
25
|
+
4. QUALITY CHECKS - YOUR OUTPUT MUST PASS:
|
|
26
|
+
- Contains opening and closing parentheses
|
|
27
|
+
- Has a colon after the parentheses
|
|
28
|
+
- Description exists and is not empty
|
|
29
|
+
- Total length ≤ 72 characters
|
|
30
|
+
- No markdown formatting
|
|
31
|
+
- No code blocks
|
|
32
|
+
- No explanations or notes
|
|
33
|
+
|
|
34
|
+
5. FAILURE MODE:
|
|
35
|
+
- If you cannot generate a proper message, return exactly: "chore: update code"
|
|
36
|
+
|
|
37
|
+
YOUR TASK:
|
|
38
|
+
Analyze this git diff and generate exactly ONE proper commit message following all rules above.
|
|
39
|
+
|
|
40
|
+
Git diff:
|
|
41
|
+
{{diff}}
|
|
42
|
+
|
|
43
|
+
Commit message:
|
|
44
44
|
`.trim();
|
|
45
|
+
export const buildPrompt = (diff, systemPrompt = DEFAULT_SYSTEM_PROMPT) => {
|
|
46
|
+
if (systemPrompt.includes("{{diff}}")) {
|
|
47
|
+
return systemPrompt.split("{{diff}}").join(diff);
|
|
48
|
+
}
|
|
49
|
+
return `${systemPrompt.trim()}\n\nGit diff:\n${diff}\n\nCommit message:`.trim();
|
|
50
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-aic",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "AI-powered Git commit generator using Google Gemini",
|
|
5
5
|
"homepage": "https://github.com/Spectra010s/git-aic",
|
|
6
6
|
"bugs": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"type": "module",
|
|
16
16
|
"main": "./dist/cli.js",
|
|
17
17
|
"bin": {
|
|
18
|
-
"git-aic": "
|
|
18
|
+
"git-aic": "dist/cli.js"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "tsc",
|
package/t.txt
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# Git Aic v1.1.0
|
|
2
|
-
|
|
3
|
-
## Highlights
|
|
4
|
-
- Fully standalone CLI — removed dependency on the original form network.
|
|
5
|
-
- AI-powered commit message generation with Google Gemini.
|
|
6
|
-
- Commit confirmation now gracefully exits if canceled.
|
|
7
|
-
- Empty commit messages are prevented during editing in confirmation prompt.
|
|
8
|
-
- `--issue` option added to link commits to GitHub issues.
|
|
9
|
-
- Flattened project structure for cleaner code organization.
|
|
10
|
-
- Axios errors for LLM requests are now handled properly to avoid crashes.
|
|
11
|
-
- CLI config system for managing your API key.
|
|
12
|
-
- Environment variable fallback supported.
|
|
13
|
-
- README updated with full usage, installation, and features.
|
|
14
|
-
|
|
15
|
-
## Improvements
|
|
16
|
-
- TypeScript & type-safe implementation.
|
|
17
|
-
- Improved user experience — prompts only run when called.
|
|
18
|
-
- Conventional commit compliance enforced.
|
|
19
|
-
- Clear separation between developer install (contributors) and user install (global npm).
|
|
20
|
-
|
|
21
|
-
## Notes
|
|
22
|
-
- Editable prompt system is not yet included — planned for v1.2.0.
|
|
23
|
-
- Users can now run `git aic` globally via npm.
|
|
24
|
-
|
|
25
|
-
## What's Changed
|
|
26
|
-
• refactor(project): flatten project structure and update config by @Spectra010s in https://github.com/Spectra010s/git-aic/pull/1
|
|
27
|
-
• feat(cli): add --issue option to link commit to an issue, closes #3 by @Spectra010s in https://github.com/Spectra010s/git-aic/pull/4
|
|
28
|
-
• prevent empty commit message when editing in confirmation prompt by @Spectra010s in https://github.com/Spectra010s/git-aic/pull/6
|
|
29
|
-
• fix(cli): handle graceful process exit on cancellation, closes #7 by @Spectra010s in https://github.com/Spectra010s/git-aic/pull/8
|
|
30
|
-
• fix(llm): handle Axios errors for LLM requests by @Spectra010s in https://github.com/Spectra010s/git-aic/pull/10
|
|
31
|
-
|
|
32
|
-
Full Changelog: https://github.com/Spectra010s/git-aic/commits/v1.1.0
|