@kud/ai-conventional-commit-cli 0.12.2 → 0.12.6

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 CHANGED
@@ -109,6 +109,12 @@ ai-conventional-commit --style gitmoji-pure
109
109
 
110
110
  # Refine previous session's first commit (shorter wording)
111
111
  ai-conventional-commit refine --shorter
112
+
113
+ # Reword an existing commit (picker)
114
+ ai-conventional-commit reword
115
+
116
+ # Reword HEAD directly (auto-amend)
117
+ ai-conventional-commit reword HEAD
112
118
  ```
113
119
 
114
120
  ## Command Reference
package/dist/index.js CHANGED
@@ -666,82 +666,14 @@ async function runRefine(config, options) {
666
666
  finalSuccess({ count: 1, startedAt });
667
667
  }
668
668
 
669
- // package.json
670
- var package_default = {
671
- name: "@kud/ai-conventional-commit-cli",
672
- version: "0.12.2",
673
- type: "module",
674
- description: "Opinionated, style-aware AI assistant for crafting and splitting git commits (opencode-based, provider-agnostic).",
675
- bin: {
676
- "ai-conventional-commit": "dist/index.js"
677
- },
678
- files: [
679
- "dist",
680
- "README.md",
681
- "LICENSE"
682
- ],
683
- scripts: {
684
- dev: "tsx src/index.ts",
685
- build: "tsup src/index.ts --format esm --dts",
686
- prepublishOnly: "npm run build",
687
- lint: "eslint .",
688
- format: "prettier --write .",
689
- test: "vitest run",
690
- "test:watch": "vitest",
691
- commit: "cz"
692
- },
693
- dependencies: {
694
- "@commitlint/config-conventional": "^19.8.1",
695
- chalk: "^5.6.2",
696
- clipanion: "^3.2.1",
697
- cosmiconfig: "^9.0.0",
698
- execa: "^9.6.0",
699
- "fast-glob": "^3.3.3",
700
- inquirer: "^12.9.4",
701
- keyv: "^5.5.1",
702
- "lru-cache": "^11.2.1",
703
- ora: "^8.2.0",
704
- pathe: "^2.0.3",
705
- "simple-git": "^3.28.0",
706
- "strip-ansi": "^7.1.2",
707
- zod: "^4.1.8"
708
- },
709
- devDependencies: {
710
- "@types/inquirer": "^9.0.9",
711
- "@types/node": "^24.3.1",
712
- "@typescript-eslint/eslint-plugin": "^8.43.0",
713
- "@typescript-eslint/parser": "^8.43.0",
714
- "cz-conventional-changelog": "^3.3.0",
715
- eslint: "^9.35.0",
716
- prettier: "^3.6.2",
717
- tsup: "^8.5.0",
718
- tsx: "^4.20.5",
719
- typescript: "^5.9.2",
720
- vitest: "^3.2.4"
721
- },
722
- config: {
723
- commitizen: {
724
- path: "cz-conventional-changelog"
725
- }
726
- },
727
- engines: {
728
- node: ">=20.0.0"
729
- },
730
- license: "MIT",
731
- repository: {
732
- type: "git",
733
- url: "git+https://github.com/kud/ai-conventional-commit-cli.git"
734
- },
735
- bugs: {
736
- url: "https://github.com/kud/ai-conventional-commit-cli/issues"
737
- },
738
- homepage: "https://github.com/kud/ai-conventional-commit-cli#readme"
739
- };
740
-
741
669
  // src/index.ts
670
+ import { readFileSync as readFileSync2 } from "fs";
671
+ import { fileURLToPath } from "url";
672
+ import { dirname, join as join4 } from "path";
742
673
  import { execa } from "execa";
743
674
  import inquirer4 from "inquirer";
744
- var pkgVersion = package_default.version || "0.0.0";
675
+ var __dirname = dirname(fileURLToPath(import.meta.url));
676
+ var pkgVersion = JSON.parse(readFileSync2(join4(__dirname, "..", "package.json"), "utf8")).version || "0.0.0";
745
677
  var RootCommand = class extends Command {
746
678
  static paths = [[]];
747
679
  static usage = Command.Usage({
@@ -1085,7 +1017,7 @@ var RewordCommand = class extends Command {
1085
1017
  style = Option.String("--style", { required: false, description: "Title style override" });
1086
1018
  model = Option.String("-m,--model", { required: false, description: "Model override" });
1087
1019
  async execute() {
1088
- const { runReword } = await import("./reword-FE5N4MGV.js");
1020
+ const { runReword } = await import("./reword-WFCNTOEU.js");
1089
1021
  const config = await loadConfig();
1090
1022
  if (this.style) config.style = this.style;
1091
1023
  if (this.model) config.model = this.model;
@@ -0,0 +1,203 @@
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
+ await git.raw(["rebase", "--onto", newHash, resolvedHash, "HEAD"]);
188
+ sectionTitle("Updated commit");
189
+ borderLine(`Rewrote ${resolvedHash.slice(0, 7)} \u2192 ${newHash.slice(0, 7)}`);
190
+ renderCommitBlock({ title: candidate.title, body: candidate.body, hideMessageLabel: true });
191
+ borderLine();
192
+ finalSuccess({ count: 1, startedAt });
193
+ } catch (e) {
194
+ borderLine("Automatic rewrite failed: " + (e?.message || e));
195
+ borderLine("If a rebase is in progress, resolve conflicts then run: git rebase --continue");
196
+ borderLine("Or abort with: git rebase --abort");
197
+ borderLine();
198
+ abortMessage();
199
+ }
200
+ }
201
+ export {
202
+ runReword
203
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/ai-conventional-commit-cli",
3
- "version": "0.12.2",
3
+ "version": "0.12.6",
4
4
  "type": "module",
5
5
  "description": "Opinionated, style-aware AI assistant for crafting and splitting git commits (opencode-based, provider-agnostic).",
6
6
  "bin": {