@kyubiware/commit-mint 0.9.2 → 0.9.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.
package/dist/bin.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { cli, command } from "cleye";
3
3
  import * as p$1 from "@clack/prompts";
4
- import { S_BAR, S_BAR_END, S_RADIO_ACTIVE, S_RADIO_INACTIVE, intro, isCancel, limitOptions, log, outro, spinner, symbol } from "@clack/prompts";
4
+ import { S_BAR, S_BAR_END, S_CHECKBOX_ACTIVE, S_CHECKBOX_INACTIVE, S_CHECKBOX_SELECTED, S_RADIO_ACTIVE, S_RADIO_INACTIVE, intro, isCancel, limitOptions, log, outro, spinner, symbol } from "@clack/prompts";
5
5
  import { bold, cyan, dim, green, red, yellow } from "kolorist";
6
6
  import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
7
7
  import os from "node:os";
@@ -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.2",
36
+ version: "0.9.3",
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" },
@@ -2468,6 +2468,21 @@ function truncate(message, maxLength) {
2468
2468
  if (collapsed.length <= maxLength) return collapsed;
2469
2469
  return `${collapsed.slice(0, Math.max(0, maxLength - 1))}…`;
2470
2470
  }
2471
+ /**
2472
+ * Filter noisy lines from vitest/jest test output.
2473
+ * Strips stack trace frames (❯ prefix) and horizontal rule separators (⎯⎯⎯ lines),
2474
+ * while preserving FAIL lines, assertion messages, diff content, and the final summary.
2475
+ */
2476
+ function filterTestOutput(output) {
2477
+ return output.split("\n").filter((line) => {
2478
+ if (/^\s*[❯>]\s/.test(line)) return false;
2479
+ const stripped = line.replace(/\s/g, "");
2480
+ if (stripped.length > 10) {
2481
+ if ((stripped.match(/[⎯\-═]/g) ?? []).length / stripped.length > .5) return false;
2482
+ }
2483
+ return true;
2484
+ }).join("\n").replace(/\n{3,}/g, "\n\n").trim();
2485
+ }
2471
2486
  async function showCheckFailureMenu(errors, rawStderr, onRetry) {
2472
2487
  debug("showCheckFailureMenu: %d errors", errors.length);
2473
2488
  let clipboardCopied = false;
@@ -2507,13 +2522,13 @@ async function showCheckFailureMenu(errors, rawStderr, onRetry) {
2507
2522
  debug("showCheckFailureMenu: user chose %s", choice);
2508
2523
  switch (choice) {
2509
2524
  case "copy":
2510
- if (await copyToClipboard(rawStderr)) {
2525
+ if (await copyToClipboard(filterTestOutput(rawStderr))) {
2511
2526
  clipboardCopied = true;
2512
2527
  p$1.log.step(green("Copied to clipboard."));
2513
2528
  } else p$1.log.warn(red("No clipboard tool found. Install xclip, wl-copy, or xsel."));
2514
2529
  continue;
2515
2530
  case "view":
2516
- p$1.note(rawStderr.trim() || "(no raw output)", "Full error output");
2531
+ p$1.note(filterTestOutput(rawStderr) || "(no raw output)", "Full error output");
2517
2532
  continue;
2518
2533
  case "retry":
2519
2534
  if (onRetry) return "retried";
@@ -3570,6 +3585,52 @@ let m = class {
3570
3585
  }
3571
3586
  }
3572
3587
  };
3588
+ let nt = class extends m {
3589
+ options;
3590
+ cursor = 0;
3591
+ get _value() {
3592
+ return this.options[this.cursor].value;
3593
+ }
3594
+ get _enabledOptions() {
3595
+ return this.options.filter((t) => t.disabled !== !0);
3596
+ }
3597
+ toggleAll() {
3598
+ const t = this._enabledOptions, s = this.value !== void 0 && this.value.length === t.length;
3599
+ this.value = s ? [] : t.map((e) => e.value);
3600
+ }
3601
+ toggleInvert() {
3602
+ const t = this.value;
3603
+ if (!t) return;
3604
+ const s = this._enabledOptions.filter((e) => !t.includes(e.value));
3605
+ this.value = s.map((e) => e.value);
3606
+ }
3607
+ toggleValue() {
3608
+ this.value === void 0 && (this.value = []);
3609
+ const t = this.value.includes(this._value);
3610
+ this.value = t ? this.value.filter((s) => s !== this._value) : [...this.value, this._value];
3611
+ }
3612
+ constructor(t) {
3613
+ super(t, !1), this.options = t.options, this.value = [...t.initialValues ?? []];
3614
+ const s = Math.max(this.options.findIndex(({ value: e }) => e === t.cursorAt), 0);
3615
+ this.cursor = this.options[s].disabled ? f(s, 1, this.options) : s, this.on("key", (e) => {
3616
+ e === "a" && this.toggleAll(), e === "i" && this.toggleInvert();
3617
+ }), this.on("cursor", (e) => {
3618
+ switch (e) {
3619
+ case "left":
3620
+ case "up":
3621
+ this.cursor = f(this.cursor, -1, this.options);
3622
+ break;
3623
+ case "down":
3624
+ case "right":
3625
+ this.cursor = f(this.cursor, 1, this.options);
3626
+ break;
3627
+ case "space":
3628
+ this.toggleValue();
3629
+ break;
3630
+ }
3631
+ });
3632
+ }
3633
+ };
3573
3634
  var ut = class extends m {
3574
3635
  options;
3575
3636
  cursor = 0;
@@ -3598,6 +3659,86 @@ var ut = class extends m {
3598
3659
  }
3599
3660
  };
3600
3661
  //#endregion
3662
+ //#region src/ui/file-multiselect.ts
3663
+ /**
3664
+ * Render a checkbox-style option line with 4 visual states:
3665
+ * active-selected, selected, active, inactive
3666
+ */
3667
+ function renderCheckboxOption(label, selected, active) {
3668
+ if (selected && active) return `${green(S_CHECKBOX_SELECTED)} ${label}`;
3669
+ if (selected) return `${dim(S_CHECKBOX_SELECTED)} ${dim(label)}`;
3670
+ if (active) return `${green(S_CHECKBOX_ACTIVE)} ${label}`;
3671
+ return `${dim(S_CHECKBOX_INACTIVE)} ${dim(label)}`;
3672
+ }
3673
+ /**
3674
+ * Build the render function for a MultiSelectPrompt with file-multiselect
3675
+ * styling and a hotkey hint line at the bottom.
3676
+ */
3677
+ function buildMultiSelectRender(message, options, output) {
3678
+ return function() {
3679
+ const header = `${symbol(this.state)} ${message}`;
3680
+ const value = this.value ?? [];
3681
+ const cursor = this.cursor;
3682
+ switch (this.state) {
3683
+ case "submit": {
3684
+ const labels = value.map((v) => {
3685
+ return options.find((o) => o.value === v)?.label ?? String(v);
3686
+ }).join(dim(", "));
3687
+ return `${header}\n${dim(S_BAR)} ${dim(labels)}`;
3688
+ }
3689
+ case "cancel": {
3690
+ const cancelled = styleText(["strikethrough", "dim"], "Cancelled");
3691
+ return `${header}\n${dim(S_BAR_END)} ${cancelled}`;
3692
+ }
3693
+ default: {
3694
+ const lines = limitOptions({
3695
+ cursor,
3696
+ options,
3697
+ style: (opt, active) => renderCheckboxOption(opt.label, value.includes(opt.value), active),
3698
+ maxItems: 7,
3699
+ output: output ?? process.stdout
3700
+ }).map((line) => `${dim(S_BAR)} ${line}`);
3701
+ const hintLine = "↑/↓ navigate · space select · enter confirm · A select all · N select none";
3702
+ return [
3703
+ header,
3704
+ ...lines,
3705
+ `${dim(S_BAR_END)} ${dim(hintLine)}`
3706
+ ].join("\n");
3707
+ }
3708
+ }
3709
+ };
3710
+ }
3711
+ /**
3712
+ * Multi-select prompt with extended hotkeys for file selection.
3713
+ *
3714
+ * Built-in (from `@clack/core`'s `MultiSelectPrompt`):
3715
+ * `a` — toggle all (select all / deselect all)
3716
+ * `i` — invert selection
3717
+ *
3718
+ * Extended hotkeys:
3719
+ * `A` (shift+A) — select all unconditionally
3720
+ * `N` — select none (deselect all)
3721
+ */
3722
+ async function fileMultiSelect(message, options, opts) {
3723
+ const required = opts?.required ?? true;
3724
+ const prompt = new nt({
3725
+ options,
3726
+ required,
3727
+ input: opts?.input,
3728
+ output: opts?.output,
3729
+ validate: (values) => {
3730
+ if (required && (!values || values.length === 0)) return "Please select at least one option.";
3731
+ },
3732
+ render: buildMultiSelectRender(message, options, opts?.output)
3733
+ });
3734
+ prompt.on("key", (char, key) => {
3735
+ if (!char) return;
3736
+ if (char === "a" && key?.shift) prompt.value = options.map((o) => o.value);
3737
+ if (char === "n") prompt.value = [];
3738
+ });
3739
+ return await prompt.prompt();
3740
+ }
3741
+ //#endregion
3601
3742
  //#region src/ui/toggle-select.ts
3602
3743
  const ON_LABEL = styleText("green", "ON");
3603
3744
  const OFF_LABEL = styleText("dim", "OFF");
@@ -3795,14 +3936,10 @@ async function showStagingMenu(files, hasChecks) {
3795
3936
  files: files.map((f) => f.path),
3796
3937
  all: true
3797
3938
  };
3798
- const selected = await p$1.multiselect({
3799
- message: "Select files to stage:",
3800
- options: sorted.map((f) => ({
3801
- label: `${statusLabel(f.status)} ${f.path}`,
3802
- value: f.path
3803
- })),
3804
- required: true
3805
- });
3939
+ const selected = await fileMultiSelect("Select files to stage:", sorted.map((f) => ({
3940
+ label: `${statusLabel(f.status)} ${f.path}`,
3941
+ value: f.path
3942
+ })), { required: true });
3806
3943
  if (p$1.isCancel(selected)) return null;
3807
3944
  return {
3808
3945
  files: selected,