@kud/ai-conventional-commit-cli 2.0.2 → 2.0.3

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.
@@ -0,0 +1,12 @@
1
+ import {
2
+ getGlobalConfigPath,
3
+ loadConfig,
4
+ loadConfigDetailed,
5
+ saveGlobalConfig
6
+ } from "./chunk-HJR5M6U7.js";
7
+ export {
8
+ getGlobalConfigPath,
9
+ loadConfig,
10
+ loadConfigDetailed,
11
+ saveGlobalConfig
12
+ };
@@ -0,0 +1,12 @@
1
+ import {
2
+ getGlobalConfigPath,
3
+ loadConfig,
4
+ loadConfigDetailed,
5
+ saveGlobalConfig
6
+ } from "./chunk-F3BOAVBY.js";
7
+ export {
8
+ getGlobalConfigPath,
9
+ loadConfig,
10
+ loadConfigDetailed,
11
+ saveGlobalConfig
12
+ };
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  formatCommitTitle,
17
17
  renderCommitBlock,
18
18
  sectionTitle
19
- } from "./chunk-ECRGQKAX.js";
19
+ } from "./chunk-IWJLYYKM.js";
20
20
 
21
21
  // src/index.ts
22
22
  import { Cli, Command, Option } from "clipanion";
@@ -684,7 +684,7 @@ import { readFileSync as readFileSync2 } from "fs";
684
684
  import { fileURLToPath } from "url";
685
685
  import { dirname, join as join4 } from "path";
686
686
  import { execa } from "execa";
687
- import inquirer2 from "inquirer";
687
+ import { select as select3 } from "@inquirer/prompts";
688
688
  var __dirname = dirname(fileURLToPath(import.meta.url));
689
689
  var pkgVersion = JSON.parse(readFileSync2(join4(__dirname, "..", "package.json"), "utf8")).version || "0.0.0";
690
690
  var RootCommand = class extends Command {
@@ -906,14 +906,10 @@ var ModelsCommand = class extends Command {
906
906
  this.context.stdout.write(stdout.trim() + "\n");
907
907
  return;
908
908
  }
909
- const { model } = await inquirer2.prompt([
910
- {
911
- name: "model",
912
- type: "list",
913
- message: "Select a model",
914
- choices: candidates
915
- }
916
- ]);
909
+ const model = await select3({
910
+ message: "Select a model",
911
+ choices: candidates.map((c) => ({ name: c, value: c }))
912
+ });
917
913
  this.context.stdout.write(model + "\n");
918
914
  if (this.save) {
919
915
  try {
@@ -1058,7 +1054,7 @@ var RewordCommand = class extends Command {
1058
1054
  description: "Auto-confirm commit without prompting"
1059
1055
  });
1060
1056
  async execute() {
1061
- const { runReword } = await import("./reword-4VB7EOET.js");
1057
+ const { runReword } = await import("./reword-UA3EG7DK.js");
1062
1058
  const config = await loadConfig();
1063
1059
  if (this.style) config.style = this.style;
1064
1060
  if (this.model) config.model = this.model;
@@ -1078,15 +1074,11 @@ var RewordCommand = class extends Command {
1078
1074
  value: c.hash
1079
1075
  }));
1080
1076
  choices.push({ name: "Cancel", value: "__CANCEL__" });
1081
- const { picked } = await inquirer2.prompt([
1082
- {
1083
- type: "list",
1084
- name: "picked",
1085
- message: "Select a commit to reword",
1086
- choices,
1087
- pageSize: Math.min(choices.length, 15)
1088
- }
1089
- ]);
1077
+ const picked = await select3({
1078
+ message: "Select a commit to reword",
1079
+ choices,
1080
+ pageSize: Math.min(choices.length, 15)
1081
+ });
1090
1082
  if (picked === "__CANCEL__") {
1091
1083
  this.context.stdout.write("Aborted.\n");
1092
1084
  return;
@@ -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
+ };
@@ -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
+ };