@kyubiware/commit-mint 0.9.4 → 0.10.1
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 +2 -1
- package/dist/bin.mjs +51 -15
- package/dist/bin.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,6 +72,7 @@ cmint # interactive cli for committing changes
|
|
|
72
72
|
```bash
|
|
73
73
|
cmint # interactive: stage → checks → review → commit
|
|
74
74
|
cmint -a # auto-group, generate messages, commit everything
|
|
75
|
+
cmint -a 3 # auto-group into exactly 3 commits
|
|
75
76
|
cmint -s # stage all tracked files in single commit, skip staging menu
|
|
76
77
|
cmint config # edit provider, model, locale, etc.
|
|
77
78
|
cmint update # update cmint to the latest published version
|
|
@@ -185,7 +186,7 @@ single raw-stderr entry.
|
|
|
185
186
|
|
|
186
187
|
| Flag | Description |
|
|
187
188
|
|---|---|
|
|
188
|
-
| `-a`, `--auto` | Auto-
|
|
189
|
+
| `-a`, `--auto [N]` | Auto-group files into N commits (default: LLM decides). Use `-a 0` or `-a` for AI-determined groups |
|
|
189
190
|
| `-m`, `--message <msg>` | Use your own message instead of AI generation |
|
|
190
191
|
| `-H`, `--hint <hint>` | Context hint to the AI (e.g. `"refactor only"`) |
|
|
191
192
|
| `-r`, `--retry` | Retry last failed commit (uses cached message) |
|
package/dist/bin.mjs
CHANGED
|
@@ -33,7 +33,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
33
33
|
//#region package.json
|
|
34
34
|
var package_default = {
|
|
35
35
|
name: "@kyubiware/commit-mint",
|
|
36
|
-
version: "0.
|
|
36
|
+
version: "0.10.1",
|
|
37
37
|
description: "🌿 AI-powered git commit tool — auto-group changed files, generate messages, run pre-commit checks",
|
|
38
38
|
type: "module",
|
|
39
39
|
bin: { "cmint": "./dist/bin.mjs" },
|
|
@@ -1727,8 +1727,8 @@ function statusIndicator(status) {
|
|
|
1727
1727
|
function buildFileSummary(files) {
|
|
1728
1728
|
return files.map((f) => `${f.path} (${statusIndicator(f.status)})`).join("\n");
|
|
1729
1729
|
}
|
|
1730
|
-
function buildGroupingSystemPrompt() {
|
|
1731
|
-
|
|
1730
|
+
function buildGroupingSystemPrompt(groupCount) {
|
|
1731
|
+
const lines = [
|
|
1732
1732
|
"You are analyzing changed files in a git repository. Group them into logical commits based on what changed and why. Each group should be a coherent unit of work.",
|
|
1733
1733
|
"",
|
|
1734
1734
|
"Rules:",
|
|
@@ -1745,7 +1745,9 @@ function buildGroupingSystemPrompt() {
|
|
|
1745
1745
|
"files: array of exact file paths from the input",
|
|
1746
1746
|
"",
|
|
1747
1747
|
"Output ONLY valid JSON. No markdown fences, no explanation."
|
|
1748
|
-
]
|
|
1748
|
+
];
|
|
1749
|
+
if (groupCount && groupCount > 0) lines.unshift(`Create exactly ${groupCount} groups.`);
|
|
1750
|
+
return lines.join("\n");
|
|
1749
1751
|
}
|
|
1750
1752
|
function buildGroupingUserPrompt(summary) {
|
|
1751
1753
|
return [
|
|
@@ -1778,7 +1780,24 @@ function buildRetryGroupingPrompt() {
|
|
|
1778
1780
|
"Output ONLY valid JSON. No markdown fences, no explanation."
|
|
1779
1781
|
].join("\n");
|
|
1780
1782
|
}
|
|
1781
|
-
|
|
1783
|
+
/**
|
|
1784
|
+
* When a specific group count is requested but there are fewer included files
|
|
1785
|
+
* than groups, create one-file-per-group groups without calling the AI.
|
|
1786
|
+
* Returns null when the pre-condition is not met.
|
|
1787
|
+
*/
|
|
1788
|
+
function createPerFileGroups(files, groupCount, excluded) {
|
|
1789
|
+
if (!(groupCount && groupCount > 0 && files.length < groupCount)) return null;
|
|
1790
|
+
debug("generateGroups: %d files < %d requested groups, creating per-file groups", files.length, groupCount);
|
|
1791
|
+
return {
|
|
1792
|
+
groups: files.map((f) => ({
|
|
1793
|
+
name: f.path.split("/").pop() || f.path,
|
|
1794
|
+
description: `Changes to ${f.path}`,
|
|
1795
|
+
files: [f.path]
|
|
1796
|
+
})),
|
|
1797
|
+
excluded
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
async function generateGroups(files, apiKey, model, timeout, provider, proxy, groupCount) {
|
|
1782
1801
|
debug("generateGroups: %d files, model=%s", files.length, model ?? "default");
|
|
1783
1802
|
const { included, excluded } = filterExcludedFiles(files);
|
|
1784
1803
|
if (included.length === 0) {
|
|
@@ -1788,8 +1807,10 @@ async function generateGroups(files, apiKey, model, timeout, provider, proxy) {
|
|
|
1788
1807
|
excluded
|
|
1789
1808
|
};
|
|
1790
1809
|
}
|
|
1810
|
+
const perFile = createPerFileGroups(included, groupCount, excluded);
|
|
1811
|
+
if (perFile) return perFile;
|
|
1791
1812
|
const summary = buildFileSummary(included);
|
|
1792
|
-
const systemPrompt = buildGroupingSystemPrompt();
|
|
1813
|
+
const systemPrompt = buildGroupingSystemPrompt(groupCount);
|
|
1793
1814
|
const userPrompt = buildGroupingUserPrompt(summary);
|
|
1794
1815
|
debug("File summary:\n%s", summary);
|
|
1795
1816
|
debug("User prompt length: %d chars", userPrompt.length);
|
|
@@ -2705,13 +2726,14 @@ async function runAutoGroupFlow(changedFiles, flags) {
|
|
|
2705
2726
|
await setConfigValue(configKey, String(key).trim());
|
|
2706
2727
|
debug("API key saved to config");
|
|
2707
2728
|
}
|
|
2729
|
+
const groupCount = typeof flags.auto === "number" ? flags.auto : 0;
|
|
2708
2730
|
const s = spinner();
|
|
2709
2731
|
s.start("Analyzing files...");
|
|
2710
|
-
const validatedGroups = validateGroups((await generateGroups(included, await getProviderApiKey(provider), getModelForProvider(config, provider, PROVIDER_CONFIGS[provider].defaultModel), config.timeout ? parseInt(config.timeout, 10) : void 0, provider, config.proxy)).groups, included);
|
|
2732
|
+
const validatedGroups = validateGroups((await generateGroups(included, await getProviderApiKey(provider), getModelForProvider(config, provider, PROVIDER_CONFIGS[provider].defaultModel), config.timeout ? parseInt(config.timeout, 10) : void 0, provider, config.proxy, groupCount)).groups, included);
|
|
2711
2733
|
s.stop("Files analyzed");
|
|
2712
2734
|
showGroupedFiles(validatedGroups, included);
|
|
2713
2735
|
const autoAccept = await getAutoAccept();
|
|
2714
|
-
const skipPrompts = flags.auto || autoAccept;
|
|
2736
|
+
const skipPrompts = flags.auto !== false || autoAccept;
|
|
2715
2737
|
if (skipPrompts) debug("Skipping grouping confirmation (auto=%s autoAccept=%s)", flags.auto, autoAccept);
|
|
2716
2738
|
else if (!await showGroupingConfirmation(validatedGroups, excluded)) {
|
|
2717
2739
|
outro(dim("Cancelled."));
|
|
@@ -3734,11 +3756,13 @@ function buildMultiSelectRender(message, options, output) {
|
|
|
3734
3756
|
return `${header}\n${dim(S_BAR_END)} ${cancelled}`;
|
|
3735
3757
|
}
|
|
3736
3758
|
default: {
|
|
3759
|
+
const stdio = output ?? process.stdout;
|
|
3760
|
+
const termRows = ("rows" in stdio ? stdio.rows : void 0) ?? 24;
|
|
3737
3761
|
const lines = limitOptions({
|
|
3738
3762
|
cursor,
|
|
3739
3763
|
options,
|
|
3740
3764
|
style: (opt, active) => renderCheckboxOption(opt.label, value.includes(opt.value), active),
|
|
3741
|
-
maxItems:
|
|
3765
|
+
maxItems: Math.min(options.length, Math.max(5, termRows - 3)),
|
|
3742
3766
|
output: output ?? process.stdout
|
|
3743
3767
|
}).map((line) => `${dim(S_BAR)} ${line}`);
|
|
3744
3768
|
const hintLine = "↑/↓ navigate · space select · enter confirm · A select all · N select none";
|
|
@@ -3767,6 +3791,7 @@ async function fileMultiSelect(message, options, opts) {
|
|
|
3767
3791
|
const prompt = new nt({
|
|
3768
3792
|
options,
|
|
3769
3793
|
required,
|
|
3794
|
+
initialValues: opts?.initialValues,
|
|
3770
3795
|
input: opts?.input,
|
|
3771
3796
|
output: opts?.output,
|
|
3772
3797
|
validate: (values) => {
|
|
@@ -3863,11 +3888,14 @@ function buildPromptRenderer(opts, state) {
|
|
|
3863
3888
|
return `${header}\n${dim(S_BAR)} ${styleText(["strikethrough", "dim"], text)}\n${dim(S_BAR_END)}`;
|
|
3864
3889
|
}
|
|
3865
3890
|
default: {
|
|
3891
|
+
const stdio = opts.output ?? process.stdout;
|
|
3892
|
+
const termRows = ("rows" in stdio ? stdio.rows : void 0) ?? 24;
|
|
3893
|
+
const dynamicMax = Math.min(optionList.length, Math.max(5, termRows - 5));
|
|
3866
3894
|
const lines = limitOptions({
|
|
3867
3895
|
cursor: this.cursor,
|
|
3868
3896
|
options: optionList,
|
|
3869
3897
|
style: (opt, active) => renderOption(opt, active),
|
|
3870
|
-
maxItems:
|
|
3898
|
+
maxItems: dynamicMax,
|
|
3871
3899
|
output: opts.output ?? process.stdout
|
|
3872
3900
|
}).map((line) => `${dim(S_BAR)} ${line}`);
|
|
3873
3901
|
const hotkeysHint = toggleList.map((t) => `\`${t.hotkey}\` toggle ${t.label.toLowerCase()}`).join(" • ");
|
|
@@ -3982,7 +4010,10 @@ async function showStagingMenu(files, hasChecks) {
|
|
|
3982
4010
|
const selected = await fileMultiSelect("Select files to stage:", sorted.map((f) => ({
|
|
3983
4011
|
label: `${statusLabel(f.status)} ${f.path}`,
|
|
3984
4012
|
value: f.path
|
|
3985
|
-
})), {
|
|
4013
|
+
})), {
|
|
4014
|
+
required: true,
|
|
4015
|
+
initialValues: sorted.filter((f) => f.staged).map((f) => f.path)
|
|
4016
|
+
});
|
|
3986
4017
|
if (p$1.isCancel(selected)) return null;
|
|
3987
4018
|
return {
|
|
3988
4019
|
files: selected,
|
|
@@ -4096,7 +4127,7 @@ async function commitCommand(flags, version) {
|
|
|
4096
4127
|
if (flags.single) {
|
|
4097
4128
|
debug("Single-commit mode: staging all files");
|
|
4098
4129
|
await stageAll();
|
|
4099
|
-
} else if (flags.auto) {
|
|
4130
|
+
} else if (flags.auto !== false) {
|
|
4100
4131
|
if (flags.message) {
|
|
4101
4132
|
outro(red("--message flag is not compatible with auto-group mode."));
|
|
4102
4133
|
return;
|
|
@@ -4398,9 +4429,10 @@ async function agentCommand(flags) {
|
|
|
4398
4429
|
}
|
|
4399
4430
|
const model = getModelForProvider(config, provider, PROVIDER_CONFIGS[provider].defaultModel);
|
|
4400
4431
|
const timeout = config.timeout ? parseInt(config.timeout, 10) : void 0;
|
|
4432
|
+
const groupCount = typeof flags.auto === "number" ? flags.auto : 0;
|
|
4401
4433
|
let groups;
|
|
4402
4434
|
try {
|
|
4403
|
-
groups = validateGroups((await generateGroups(included, apiKey, model, timeout, provider, config.proxy)).groups, included);
|
|
4435
|
+
groups = validateGroups((await generateGroups(included, apiKey, model, timeout, provider, config.proxy, groupCount)).groups, included);
|
|
4404
4436
|
} catch (err) {
|
|
4405
4437
|
process.exitCode = EXIT_CODES.AI;
|
|
4406
4438
|
writeAgentResult({
|
|
@@ -4862,8 +4894,12 @@ cli({
|
|
|
4862
4894
|
default: false
|
|
4863
4895
|
},
|
|
4864
4896
|
auto: {
|
|
4865
|
-
type:
|
|
4866
|
-
|
|
4897
|
+
type: (raw) => {
|
|
4898
|
+
if (raw === "") return true;
|
|
4899
|
+
const n = Number(raw);
|
|
4900
|
+
return Number.isNaN(n) ? true : n;
|
|
4901
|
+
},
|
|
4902
|
+
description: "Auto-group files into commits. Use -a <N> to request N groups (0 = LLM decides)",
|
|
4867
4903
|
alias: "a",
|
|
4868
4904
|
default: false
|
|
4869
4905
|
},
|