@kud/ai-conventional-commit-cli 0.11.0 → 0.12.0
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/dist/chunk-H4W6AMGZ.js +549 -0
- package/dist/chunk-YIXP5EWA.js +545 -0
- package/dist/index.js +87 -551
- package/dist/reword-CZDYMQEV.js +150 -0
- package/dist/reword-FE5N4MGV.js +150 -0
- package/package.json +1 -1
- package/dist/index.cjs +0 -1200
- package/dist/index.d.cts +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OpenCodeProvider,
|
|
3
|
+
animateHeaderBase,
|
|
4
|
+
borderLine,
|
|
5
|
+
buildRefineMessages,
|
|
6
|
+
extractJSON,
|
|
7
|
+
formatCommitTitle,
|
|
8
|
+
renderCommitBlock,
|
|
9
|
+
sectionTitle
|
|
10
|
+
} from "./chunk-YIXP5EWA.js";
|
|
11
|
+
|
|
12
|
+
// src/workflow/reword.ts
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import ora from "ora";
|
|
15
|
+
import inquirer from "inquirer";
|
|
16
|
+
import { simpleGit } from "simple-git";
|
|
17
|
+
var git = simpleGit();
|
|
18
|
+
async function getCommitMessage(hash) {
|
|
19
|
+
try {
|
|
20
|
+
const raw = await git.show([`${hash}`, "--quiet", "--format=%P%n%B"]);
|
|
21
|
+
const lines = raw.split("\n");
|
|
22
|
+
const parentsLine = lines.shift() || "";
|
|
23
|
+
const parents = parentsLine.trim().length ? parentsLine.trim().split(/\s+/) : [];
|
|
24
|
+
const message = lines.join("\n").trim();
|
|
25
|
+
if (!message) return null;
|
|
26
|
+
const [first, ...rest] = message.split("\n");
|
|
27
|
+
const body = rest.join("\n").trim() || void 0;
|
|
28
|
+
return { title: first, body, parents };
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function runReword(config, hash) {
|
|
34
|
+
const startedAt = Date.now();
|
|
35
|
+
const commit = await getCommitMessage(hash);
|
|
36
|
+
if (!commit) {
|
|
37
|
+
console.log(`Commit not found: ${hash}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (commit.parents.length > 1) {
|
|
41
|
+
console.log("Refusing to reword a merge commit (multiple parents).");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (process.stdout.isTTY) {
|
|
45
|
+
await animateHeaderBase("ai-conventional-commit", config.model);
|
|
46
|
+
borderLine();
|
|
47
|
+
}
|
|
48
|
+
sectionTitle("Original commit");
|
|
49
|
+
borderLine(chalk.yellow(commit.title));
|
|
50
|
+
if (commit.body) {
|
|
51
|
+
commit.body.split("\n").forEach((l) => l.trim().length ? borderLine(l) : borderLine());
|
|
52
|
+
}
|
|
53
|
+
borderLine();
|
|
54
|
+
const instructions = [
|
|
55
|
+
"Improve clarity & conformity to Conventional Commits while preserving meaning."
|
|
56
|
+
];
|
|
57
|
+
const syntheticPlan = {
|
|
58
|
+
commits: [
|
|
59
|
+
{
|
|
60
|
+
title: commit.title,
|
|
61
|
+
body: commit.body,
|
|
62
|
+
score: 0,
|
|
63
|
+
reasons: []
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
};
|
|
67
|
+
const provider = new OpenCodeProvider(config.model);
|
|
68
|
+
const spinner = ora({ text: "Calling model", spinner: "dots" }).start();
|
|
69
|
+
let refined = null;
|
|
70
|
+
try {
|
|
71
|
+
const messages = buildRefineMessages({
|
|
72
|
+
originalPlan: syntheticPlan,
|
|
73
|
+
index: 0,
|
|
74
|
+
instructions,
|
|
75
|
+
config
|
|
76
|
+
});
|
|
77
|
+
const raw = await provider.chat(messages, { maxTokens: config.maxTokens });
|
|
78
|
+
refined = await extractJSON(raw);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
spinner.fail("Model call failed: " + (e?.message || e));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
spinner.stop();
|
|
84
|
+
if (!refined || !refined.commits.length) {
|
|
85
|
+
console.log("No refined commit produced.");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const candidate = refined.commits[0];
|
|
89
|
+
candidate.title = formatCommitTitle(candidate.title, {
|
|
90
|
+
allowGitmoji: config.style === "gitmoji" || config.style === "gitmoji-pure",
|
|
91
|
+
mode: config.style
|
|
92
|
+
});
|
|
93
|
+
sectionTitle("Proposed commit");
|
|
94
|
+
renderCommitBlock({
|
|
95
|
+
title: chalk.yellow(candidate.title),
|
|
96
|
+
body: candidate.body,
|
|
97
|
+
hideMessageLabel: true
|
|
98
|
+
});
|
|
99
|
+
borderLine();
|
|
100
|
+
const isHead = (await git.revparse(["HEAD"])).startsWith(hash) || await git.revparse([hash]) === await git.revparse(["HEAD"]);
|
|
101
|
+
const { ok } = await inquirer.prompt([
|
|
102
|
+
{
|
|
103
|
+
type: "list",
|
|
104
|
+
name: "ok",
|
|
105
|
+
message: isHead ? "Amend HEAD with this message?" : "Use this new message (show rebase instructions)?",
|
|
106
|
+
choices: [
|
|
107
|
+
{ name: "Yes", value: true },
|
|
108
|
+
{ name: "No", value: false }
|
|
109
|
+
],
|
|
110
|
+
default: 0
|
|
111
|
+
}
|
|
112
|
+
]);
|
|
113
|
+
if (!ok) {
|
|
114
|
+
borderLine("Aborted.");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (isHead) {
|
|
118
|
+
const full = candidate.body ? `${candidate.title}
|
|
119
|
+
|
|
120
|
+
${candidate.body}` : candidate.title;
|
|
121
|
+
try {
|
|
122
|
+
await git.commit(full, { "--amend": null });
|
|
123
|
+
borderLine("Amended HEAD.");
|
|
124
|
+
} catch (e) {
|
|
125
|
+
borderLine("Failed to amend: " + (e?.message || e));
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
const full = candidate.body ? `${candidate.title}
|
|
129
|
+
|
|
130
|
+
${candidate.body}` : candidate.title;
|
|
131
|
+
sectionTitle("Apply manually");
|
|
132
|
+
borderLine("Interactive rebase steps:");
|
|
133
|
+
borderLine(`1. git rebase -i ${hash}~1 --reword`);
|
|
134
|
+
borderLine(
|
|
135
|
+
"2. In the editor, ensure the line for the commit is kept as reword (or change pick \u2192 reword)."
|
|
136
|
+
);
|
|
137
|
+
borderLine("3. When prompted, replace the message with below:");
|
|
138
|
+
borderLine();
|
|
139
|
+
borderLine(candidate.title);
|
|
140
|
+
if (candidate.body) {
|
|
141
|
+
candidate.body.split("\n").forEach((l) => l.trim().length ? borderLine(l) : borderLine());
|
|
142
|
+
}
|
|
143
|
+
borderLine();
|
|
144
|
+
}
|
|
145
|
+
const elapsed = ((Date.now() - startedAt) / 1e3).toFixed(1) + "s";
|
|
146
|
+
borderLine(`Done in ${elapsed}.`);
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
runReword
|
|
150
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OpenCodeProvider,
|
|
3
|
+
animateHeaderBase,
|
|
4
|
+
borderLine,
|
|
5
|
+
buildRefineMessages,
|
|
6
|
+
extractJSON,
|
|
7
|
+
formatCommitTitle,
|
|
8
|
+
renderCommitBlock,
|
|
9
|
+
sectionTitle
|
|
10
|
+
} from "./chunk-H4W6AMGZ.js";
|
|
11
|
+
|
|
12
|
+
// src/workflow/reword.ts
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import ora from "ora";
|
|
15
|
+
import inquirer from "inquirer";
|
|
16
|
+
import { simpleGit } from "simple-git";
|
|
17
|
+
var git = simpleGit();
|
|
18
|
+
async function getCommitMessage(hash) {
|
|
19
|
+
try {
|
|
20
|
+
const raw = await git.show([`${hash}`, "--quiet", "--format=%P%n%B"]);
|
|
21
|
+
const lines = raw.split("\n");
|
|
22
|
+
const parentsLine = lines.shift() || "";
|
|
23
|
+
const parents = parentsLine.trim().length ? parentsLine.trim().split(/\s+/) : [];
|
|
24
|
+
const message = lines.join("\n").trim();
|
|
25
|
+
if (!message) return null;
|
|
26
|
+
const [first, ...rest] = message.split("\n");
|
|
27
|
+
const body = rest.join("\n").trim() || void 0;
|
|
28
|
+
return { title: first, body, parents };
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function runReword(config, hash) {
|
|
34
|
+
const startedAt = Date.now();
|
|
35
|
+
const commit = await getCommitMessage(hash);
|
|
36
|
+
if (!commit) {
|
|
37
|
+
console.log(`Commit not found: ${hash}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (commit.parents.length > 1) {
|
|
41
|
+
console.log("Refusing to reword a merge commit (multiple parents).");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (process.stdout.isTTY) {
|
|
45
|
+
await animateHeaderBase("ai-conventional-commit", config.model);
|
|
46
|
+
borderLine();
|
|
47
|
+
}
|
|
48
|
+
sectionTitle("Original commit");
|
|
49
|
+
borderLine(chalk.yellow(commit.title));
|
|
50
|
+
if (commit.body) {
|
|
51
|
+
commit.body.split("\n").forEach((l) => l.trim().length ? borderLine(l) : borderLine());
|
|
52
|
+
}
|
|
53
|
+
borderLine();
|
|
54
|
+
const instructions = [
|
|
55
|
+
"Improve clarity & conformity to Conventional Commits while preserving meaning."
|
|
56
|
+
];
|
|
57
|
+
const syntheticPlan = {
|
|
58
|
+
commits: [
|
|
59
|
+
{
|
|
60
|
+
title: commit.title,
|
|
61
|
+
body: commit.body,
|
|
62
|
+
score: 0,
|
|
63
|
+
reasons: []
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
};
|
|
67
|
+
const provider = new OpenCodeProvider(config.model);
|
|
68
|
+
const spinner = ora({ text: "Calling model", spinner: "dots" }).start();
|
|
69
|
+
let refined = null;
|
|
70
|
+
try {
|
|
71
|
+
const messages = buildRefineMessages({
|
|
72
|
+
originalPlan: syntheticPlan,
|
|
73
|
+
index: 0,
|
|
74
|
+
instructions,
|
|
75
|
+
config
|
|
76
|
+
});
|
|
77
|
+
const raw = await provider.chat(messages, { maxTokens: config.maxTokens });
|
|
78
|
+
refined = await extractJSON(raw);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
spinner.fail("Model call failed: " + (e?.message || e));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
spinner.stop();
|
|
84
|
+
if (!refined || !refined.commits.length) {
|
|
85
|
+
console.log("No refined commit produced.");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const candidate = refined.commits[0];
|
|
89
|
+
candidate.title = formatCommitTitle(candidate.title, {
|
|
90
|
+
allowGitmoji: config.style === "gitmoji" || config.style === "gitmoji-pure",
|
|
91
|
+
mode: config.style
|
|
92
|
+
});
|
|
93
|
+
sectionTitle("Proposed commit");
|
|
94
|
+
renderCommitBlock({
|
|
95
|
+
title: chalk.yellow(candidate.title),
|
|
96
|
+
body: candidate.body,
|
|
97
|
+
hideMessageLabel: true
|
|
98
|
+
});
|
|
99
|
+
borderLine();
|
|
100
|
+
const isHead = (await git.revparse(["HEAD"])).startsWith(hash) || await git.revparse([hash]) === await git.revparse(["HEAD"]);
|
|
101
|
+
const { ok } = await inquirer.prompt([
|
|
102
|
+
{
|
|
103
|
+
type: "list",
|
|
104
|
+
name: "ok",
|
|
105
|
+
message: isHead ? "Amend HEAD with this message?" : "Use this new message (show rebase instructions)?",
|
|
106
|
+
choices: [
|
|
107
|
+
{ name: "Yes", value: true },
|
|
108
|
+
{ name: "No", value: false }
|
|
109
|
+
],
|
|
110
|
+
default: 0
|
|
111
|
+
}
|
|
112
|
+
]);
|
|
113
|
+
if (!ok) {
|
|
114
|
+
borderLine("Aborted.");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (isHead) {
|
|
118
|
+
const full = candidate.body ? `${candidate.title}
|
|
119
|
+
|
|
120
|
+
${candidate.body}` : candidate.title;
|
|
121
|
+
try {
|
|
122
|
+
await git.commit(full, { "--amend": null });
|
|
123
|
+
borderLine("Amended HEAD.");
|
|
124
|
+
} catch (e) {
|
|
125
|
+
borderLine("Failed to amend: " + (e?.message || e));
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
const full = candidate.body ? `${candidate.title}
|
|
129
|
+
|
|
130
|
+
${candidate.body}` : candidate.title;
|
|
131
|
+
sectionTitle("Apply manually");
|
|
132
|
+
borderLine("Interactive rebase steps:");
|
|
133
|
+
borderLine(`1. git rebase -i ${hash}~1 --reword`);
|
|
134
|
+
borderLine(
|
|
135
|
+
"2. In the editor, ensure the line for the commit is kept as reword (or change pick \u2192 reword)."
|
|
136
|
+
);
|
|
137
|
+
borderLine("3. When prompted, replace the message with below:");
|
|
138
|
+
borderLine();
|
|
139
|
+
borderLine(candidate.title);
|
|
140
|
+
if (candidate.body) {
|
|
141
|
+
candidate.body.split("\n").forEach((l) => l.trim().length ? borderLine(l) : borderLine());
|
|
142
|
+
}
|
|
143
|
+
borderLine();
|
|
144
|
+
}
|
|
145
|
+
const elapsed = ((Date.now() - startedAt) / 1e3).toFixed(1) + "s";
|
|
146
|
+
borderLine(`Done in ${elapsed}.`);
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
runReword
|
|
150
|
+
};
|
package/package.json
CHANGED