@kud/ai-conventional-commit-cli 2.0.1 → 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 {
@@ -1010,7 +1006,15 @@ var ConfigSetCommand = class extends Command {
1010
1006
  key = Option.String();
1011
1007
  value = Option.String();
1012
1008
  async execute() {
1013
- const allowed = /* @__PURE__ */ new Set(["model", "style", "privacy", "styleSamples", "maxTokens", "verbose", "yes"]);
1009
+ const allowed = /* @__PURE__ */ new Set([
1010
+ "model",
1011
+ "style",
1012
+ "privacy",
1013
+ "styleSamples",
1014
+ "maxTokens",
1015
+ "verbose",
1016
+ "yes"
1017
+ ]);
1014
1018
  if (!allowed.has(this.key)) {
1015
1019
  this.context.stderr.write(`Cannot set key: ${this.key}
1016
1020
  `);
@@ -1050,7 +1054,7 @@ var RewordCommand = class extends Command {
1050
1054
  description: "Auto-confirm commit without prompting"
1051
1055
  });
1052
1056
  async execute() {
1053
- const { runReword } = await import("./reword-4VB7EOET.js");
1057
+ const { runReword } = await import("./reword-UA3EG7DK.js");
1054
1058
  const config = await loadConfig();
1055
1059
  if (this.style) config.style = this.style;
1056
1060
  if (this.model) config.model = this.model;
@@ -1070,15 +1074,11 @@ var RewordCommand = class extends Command {
1070
1074
  value: c.hash
1071
1075
  }));
1072
1076
  choices.push({ name: "Cancel", value: "__CANCEL__" });
1073
- const { picked } = await inquirer2.prompt([
1074
- {
1075
- type: "list",
1076
- name: "picked",
1077
- message: "Select a commit to reword",
1078
- choices,
1079
- pageSize: Math.min(choices.length, 15)
1080
- }
1081
- ]);
1077
+ const picked = await select3({
1078
+ message: "Select a commit to reword",
1079
+ choices,
1080
+ pageSize: Math.min(choices.length, 15)
1081
+ });
1082
1082
  if (picked === "__CANCEL__") {
1083
1083
  this.context.stdout.write("Aborted.\n");
1084
1084
  return;
@@ -1118,4 +1118,4 @@ cli.runExit(process.argv.slice(2), {
1118
1118
  stdin: process.stdin,
1119
1119
  stdout: process.stdout,
1120
1120
  stderr: process.stderr
1121
- });
1121
+ }).then(() => process.exit(process.exitCode ?? 0));
@@ -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
+ };