@kud/ai-conventional-commit-cli 0.12.7 → 0.12.10
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/index.js +13 -11
- package/dist/reword-IN2D2J4H.js +212 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -54,6 +54,10 @@ var parseDiffFromRaw = (raw) => {
|
|
|
54
54
|
}
|
|
55
55
|
if (line.startsWith("diff --git")) continue;
|
|
56
56
|
if (line.startsWith("index ")) continue;
|
|
57
|
+
if (/^deleted file mode /.test(line)) {
|
|
58
|
+
if (currentFile) currentFile.deleted = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
57
61
|
if (line.startsWith("--- ")) continue;
|
|
58
62
|
if (line.startsWith("+++ ")) continue;
|
|
59
63
|
if (line.startsWith("@@")) {
|
|
@@ -235,17 +239,16 @@ async function runGenerate(config) {
|
|
|
235
239
|
const del = f.deletions || 0;
|
|
236
240
|
totalAdd += add;
|
|
237
241
|
totalDel += del;
|
|
238
|
-
if (add === 0 && del === 0) {
|
|
239
|
-
borderLine("\u2022 " + f.file);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
242
|
const delta = add + del;
|
|
243
243
|
const barLen = Math.max(1, Math.round(delta / maxDelta * BAR_WIDTH));
|
|
244
244
|
const addPortion = Math.min(barLen, Math.round(barLen * (add / (delta || 1))));
|
|
245
245
|
const delPortion = barLen - addPortion;
|
|
246
246
|
const bar = chalk.green("+".repeat(addPortion)) + chalk.red("-".repeat(delPortion));
|
|
247
247
|
const counts = chalk.green("+" + add) + " " + chalk.red("-" + del);
|
|
248
|
-
|
|
248
|
+
let name = f.file.length > maxName ? f.file.slice(0, maxName - 1) + "\u2026" : f.file;
|
|
249
|
+
if (f.deleted) {
|
|
250
|
+
name += " " + chalk.red("[deleted]");
|
|
251
|
+
}
|
|
249
252
|
borderLine(name.padEnd(maxName) + " | " + counts.padEnd(12) + " " + bar);
|
|
250
253
|
});
|
|
251
254
|
borderLine(
|
|
@@ -418,17 +421,16 @@ async function runSplit(config, desired) {
|
|
|
418
421
|
const del = f.deletions || 0;
|
|
419
422
|
totalAdd += add;
|
|
420
423
|
totalDel += del;
|
|
421
|
-
if (add === 0 && del === 0) {
|
|
422
|
-
borderLine("\u2022 " + f.file);
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
424
|
const delta = add + del;
|
|
426
425
|
const barLen = Math.max(1, Math.round(delta / maxDelta * BAR_WIDTH));
|
|
427
426
|
const addPortion = Math.min(barLen, Math.round(barLen * (add / (delta || 1))));
|
|
428
427
|
const delPortion = barLen - addPortion;
|
|
429
428
|
const bar = chalk2.green("+".repeat(addPortion)) + chalk2.red("-".repeat(delPortion));
|
|
430
429
|
const counts = chalk2.green("+" + add) + " " + chalk2.red("-" + del);
|
|
431
|
-
|
|
430
|
+
let name = f.file.length > maxName ? f.file.slice(0, maxName - 1) + "\u2026" : f.file;
|
|
431
|
+
if (f.deleted) {
|
|
432
|
+
name += " " + chalk2.red("[deleted]");
|
|
433
|
+
}
|
|
432
434
|
borderLine(name.padEnd(maxName) + " | " + counts.padEnd(12) + " " + bar);
|
|
433
435
|
});
|
|
434
436
|
borderLine(
|
|
@@ -1017,7 +1019,7 @@ var RewordCommand = class extends Command {
|
|
|
1017
1019
|
style = Option.String("--style", { required: false, description: "Title style override" });
|
|
1018
1020
|
model = Option.String("-m,--model", { required: false, description: "Model override" });
|
|
1019
1021
|
async execute() {
|
|
1020
|
-
const { runReword } = await import("./reword-
|
|
1022
|
+
const { runReword } = await import("./reword-IN2D2J4H.js");
|
|
1021
1023
|
const config = await loadConfig();
|
|
1022
1024
|
if (this.style) config.style = this.style;
|
|
1023
1025
|
if (this.model) config.model = this.model;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OpenCodeProvider,
|
|
3
|
+
abortMessage,
|
|
4
|
+
animateHeaderBase,
|
|
5
|
+
borderLine,
|
|
6
|
+
buildRefineMessages,
|
|
7
|
+
createPhasedSpinner,
|
|
8
|
+
extractJSON,
|
|
9
|
+
finalSuccess,
|
|
10
|
+
formatCommitTitle,
|
|
11
|
+
renderCommitBlock,
|
|
12
|
+
sectionTitle
|
|
13
|
+
} from "./chunk-H4W6AMGZ.js";
|
|
14
|
+
|
|
15
|
+
// src/workflow/reword.ts
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import ora from "ora";
|
|
18
|
+
import inquirer from "inquirer";
|
|
19
|
+
import { simpleGit } from "simple-git";
|
|
20
|
+
var git = simpleGit();
|
|
21
|
+
async function getCommitMessage(hash) {
|
|
22
|
+
try {
|
|
23
|
+
const raw = await git.show([`${hash}`, "--quiet", "--format=%P%n%B"]);
|
|
24
|
+
const lines = raw.split("\n");
|
|
25
|
+
const parentsLine = lines.shift() || "";
|
|
26
|
+
const parents = parentsLine.trim().length ? parentsLine.trim().split(/\s+/) : [];
|
|
27
|
+
const message = lines.join("\n").trim();
|
|
28
|
+
if (!message) return null;
|
|
29
|
+
const [first, ...rest] = message.split("\n");
|
|
30
|
+
const body = rest.join("\n").trim() || void 0;
|
|
31
|
+
return { title: first, body, parents };
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function isAncestor(ancestor, head) {
|
|
37
|
+
try {
|
|
38
|
+
const mb = (await git.raw(["merge-base", ancestor, head])).trim();
|
|
39
|
+
const anc = (await git.revparse([ancestor])).trim();
|
|
40
|
+
return mb === anc;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function runReword(config, hash) {
|
|
46
|
+
const startedAt = Date.now();
|
|
47
|
+
const commit = await getCommitMessage(hash);
|
|
48
|
+
if (!commit) {
|
|
49
|
+
console.log(`Commit not found: ${hash}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (commit.parents.length > 1) {
|
|
53
|
+
console.log("Refusing to reword a merge commit (multiple parents).");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (process.stdout.isTTY) {
|
|
57
|
+
await animateHeaderBase("ai-conventional-commit", config.model);
|
|
58
|
+
borderLine();
|
|
59
|
+
}
|
|
60
|
+
sectionTitle("Original commit");
|
|
61
|
+
borderLine(chalk.yellow(commit.title));
|
|
62
|
+
if (commit.body) {
|
|
63
|
+
commit.body.split("\n").forEach((l) => l.trim().length ? borderLine(l) : borderLine());
|
|
64
|
+
}
|
|
65
|
+
borderLine();
|
|
66
|
+
const instructions = [
|
|
67
|
+
"Improve clarity & conformity to Conventional Commits while preserving meaning."
|
|
68
|
+
];
|
|
69
|
+
const syntheticPlan = {
|
|
70
|
+
commits: [
|
|
71
|
+
{
|
|
72
|
+
title: commit.title,
|
|
73
|
+
body: commit.body,
|
|
74
|
+
score: 0,
|
|
75
|
+
reasons: []
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
};
|
|
79
|
+
const provider = new OpenCodeProvider(config.model);
|
|
80
|
+
const phased = createPhasedSpinner(ora);
|
|
81
|
+
let refined = null;
|
|
82
|
+
try {
|
|
83
|
+
phased.phase("Preparing prompt");
|
|
84
|
+
const messages = buildRefineMessages({
|
|
85
|
+
originalPlan: syntheticPlan,
|
|
86
|
+
index: 0,
|
|
87
|
+
instructions,
|
|
88
|
+
config
|
|
89
|
+
});
|
|
90
|
+
phased.phase("Calling model");
|
|
91
|
+
const raw = await provider.chat(messages, { maxTokens: config.maxTokens });
|
|
92
|
+
phased.phase("Parsing response");
|
|
93
|
+
refined = await extractJSON(raw);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
phased.spinner.fail("Reword failed: " + (e?.message || e));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
phased.stop();
|
|
99
|
+
if (!refined || !refined.commits.length) {
|
|
100
|
+
console.log("No refined commit produced.");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const candidate = refined.commits[0];
|
|
104
|
+
candidate.title = formatCommitTitle(candidate.title, {
|
|
105
|
+
allowGitmoji: config.style === "gitmoji" || config.style === "gitmoji-pure",
|
|
106
|
+
mode: config.style
|
|
107
|
+
});
|
|
108
|
+
sectionTitle("Proposed commit");
|
|
109
|
+
renderCommitBlock({
|
|
110
|
+
title: chalk.yellow(candidate.title),
|
|
111
|
+
body: candidate.body,
|
|
112
|
+
hideMessageLabel: true
|
|
113
|
+
});
|
|
114
|
+
borderLine();
|
|
115
|
+
const resolvedHash = (await git.revparse([hash])).trim();
|
|
116
|
+
const headHash = (await git.revparse(["HEAD"])).trim();
|
|
117
|
+
const isHead = headHash === resolvedHash || headHash.startsWith(resolvedHash);
|
|
118
|
+
const { ok } = await inquirer.prompt([
|
|
119
|
+
{
|
|
120
|
+
type: "list",
|
|
121
|
+
name: "ok",
|
|
122
|
+
message: isHead ? "Amend HEAD with this message?" : "Apply rewrite (history will change)?",
|
|
123
|
+
choices: [
|
|
124
|
+
{ name: "Yes", value: true },
|
|
125
|
+
{ name: "No", value: false }
|
|
126
|
+
],
|
|
127
|
+
default: 0
|
|
128
|
+
}
|
|
129
|
+
]);
|
|
130
|
+
if (!ok) {
|
|
131
|
+
borderLine();
|
|
132
|
+
abortMessage();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const full = candidate.body ? `${candidate.title}
|
|
136
|
+
|
|
137
|
+
${candidate.body}` : candidate.title;
|
|
138
|
+
if (isHead) {
|
|
139
|
+
try {
|
|
140
|
+
await git.commit(full, { "--amend": null });
|
|
141
|
+
} catch (e) {
|
|
142
|
+
borderLine("Failed to amend HEAD: " + (e?.message || e));
|
|
143
|
+
borderLine();
|
|
144
|
+
abortMessage();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
borderLine();
|
|
148
|
+
finalSuccess({ count: 1, startedAt });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const ancestorOk = await isAncestor(resolvedHash, headHash);
|
|
152
|
+
if (!ancestorOk) {
|
|
153
|
+
borderLine("Selected commit is not an ancestor of HEAD.");
|
|
154
|
+
borderLine("Cannot safely rewrite automatically.");
|
|
155
|
+
borderLine();
|
|
156
|
+
abortMessage();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
let mergesRange = "";
|
|
160
|
+
try {
|
|
161
|
+
mergesRange = (await git.raw(["rev-list", "--merges", `${resolvedHash}..HEAD`])).trim();
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
if (mergesRange) {
|
|
165
|
+
sectionTitle("Unsafe automatic rewrite");
|
|
166
|
+
borderLine("Merge commits detected between target and HEAD.");
|
|
167
|
+
borderLine("Falling back to manual instructions (preserving previous behavior).");
|
|
168
|
+
borderLine();
|
|
169
|
+
sectionTitle("Apply manually");
|
|
170
|
+
borderLine(`1. git rebase -i ${resolvedHash}~1 --reword`);
|
|
171
|
+
borderLine("2. Mark the line as reword if needed.");
|
|
172
|
+
borderLine("3. Replace the message with:");
|
|
173
|
+
borderLine();
|
|
174
|
+
borderLine(candidate.title);
|
|
175
|
+
if (candidate.body) candidate.body.split("\n").forEach((l) => borderLine(l || void 0));
|
|
176
|
+
borderLine();
|
|
177
|
+
abortMessage();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const tree = (await git.raw(["show", "-s", "--format=%T", resolvedHash])).trim();
|
|
182
|
+
const parent = commit.parents[0];
|
|
183
|
+
const args = ["commit-tree", tree];
|
|
184
|
+
if (parent) args.push("-p", parent);
|
|
185
|
+
args.push("-m", full);
|
|
186
|
+
const newHash = (await git.raw(args)).trim();
|
|
187
|
+
const currentBranch = (await git.revparse(["--abbrev-ref", "HEAD"])).trim();
|
|
188
|
+
const rebaseTarget = currentBranch === "HEAD" ? "HEAD" : currentBranch;
|
|
189
|
+
await git.raw(["rebase", "--onto", newHash, resolvedHash, rebaseTarget]);
|
|
190
|
+
const afterBranch = (await git.revparse(["--abbrev-ref", "HEAD"])).trim();
|
|
191
|
+
if (afterBranch === "HEAD" && rebaseTarget !== "HEAD") {
|
|
192
|
+
try {
|
|
193
|
+
await git.checkout([rebaseTarget]);
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
sectionTitle("Updated commit");
|
|
198
|
+
borderLine(`Rewrote ${resolvedHash.slice(0, 7)} \u2192 ${newHash.slice(0, 7)}`);
|
|
199
|
+
renderCommitBlock({ title: candidate.title, body: candidate.body, hideMessageLabel: true });
|
|
200
|
+
borderLine();
|
|
201
|
+
finalSuccess({ count: 1, startedAt });
|
|
202
|
+
} catch (e) {
|
|
203
|
+
borderLine("Automatic rewrite failed: " + (e?.message || e));
|
|
204
|
+
borderLine("If a rebase is in progress, resolve conflicts then run: git rebase --continue");
|
|
205
|
+
borderLine("Or abort with: git rebase --abort");
|
|
206
|
+
borderLine();
|
|
207
|
+
abortMessage();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
export {
|
|
211
|
+
runReword
|
|
212
|
+
};
|
package/package.json
CHANGED