@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 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.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
- 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";
@@ -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: 7,
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
- })), { required: true });
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: Boolean,
4866
- description: "Auto-group files into commits and accept messages (no prompts)",
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
  },