@kyubiware/commit-mint 0.9.4 → 0.10.0

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
@@ -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-stage tracked files and auto-group into commits |
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.9.4",
36
+ version: "0.10.0",
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
- return [
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
- ].join("\n");
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
- async function generateGroups(files, apiKey, model, timeout, provider, proxy) {
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: 7,
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";
@@ -3863,11 +3887,14 @@ function buildPromptRenderer(opts, state) {
3863
3887
  return `${header}\n${dim(S_BAR)} ${styleText(["strikethrough", "dim"], text)}\n${dim(S_BAR_END)}`;
3864
3888
  }
3865
3889
  default: {
3890
+ const stdio = opts.output ?? process.stdout;
3891
+ const termRows = ("rows" in stdio ? stdio.rows : void 0) ?? 24;
3892
+ const dynamicMax = Math.min(optionList.length, Math.max(5, termRows - 5));
3866
3893
  const lines = limitOptions({
3867
3894
  cursor: this.cursor,
3868
3895
  options: optionList,
3869
3896
  style: (opt, active) => renderOption(opt, active),
3870
- maxItems: 7,
3897
+ maxItems: dynamicMax,
3871
3898
  output: opts.output ?? process.stdout
3872
3899
  }).map((line) => `${dim(S_BAR)} ${line}`);
3873
3900
  const hotkeysHint = toggleList.map((t) => `\`${t.hotkey}\` toggle ${t.label.toLowerCase()}`).join(" • ");
@@ -4096,7 +4123,7 @@ async function commitCommand(flags, version) {
4096
4123
  if (flags.single) {
4097
4124
  debug("Single-commit mode: staging all files");
4098
4125
  await stageAll();
4099
- } else if (flags.auto) {
4126
+ } else if (flags.auto !== false) {
4100
4127
  if (flags.message) {
4101
4128
  outro(red("--message flag is not compatible with auto-group mode."));
4102
4129
  return;
@@ -4398,9 +4425,10 @@ async function agentCommand(flags) {
4398
4425
  }
4399
4426
  const model = getModelForProvider(config, provider, PROVIDER_CONFIGS[provider].defaultModel);
4400
4427
  const timeout = config.timeout ? parseInt(config.timeout, 10) : void 0;
4428
+ const groupCount = typeof flags.auto === "number" ? flags.auto : 0;
4401
4429
  let groups;
4402
4430
  try {
4403
- groups = validateGroups((await generateGroups(included, apiKey, model, timeout, provider, config.proxy)).groups, included);
4431
+ groups = validateGroups((await generateGroups(included, apiKey, model, timeout, provider, config.proxy, groupCount)).groups, included);
4404
4432
  } catch (err) {
4405
4433
  process.exitCode = EXIT_CODES.AI;
4406
4434
  writeAgentResult({
@@ -4862,8 +4890,12 @@ cli({
4862
4890
  default: false
4863
4891
  },
4864
4892
  auto: {
4865
- type: Boolean,
4866
- description: "Auto-group files into commits and accept messages (no prompts)",
4893
+ type: (raw) => {
4894
+ if (raw === "") return true;
4895
+ const n = Number(raw);
4896
+ return Number.isNaN(n) ? true : n;
4897
+ },
4898
+ description: "Auto-group files into commits. Use -a <N> to request N groups (0 = LLM decides)",
4867
4899
  alias: "a",
4868
4900
  default: false
4869
4901
  },