@kyubiware/commit-mint 0.7.3 → 0.7.5
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/cli.mjs +109 -40
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -29,7 +29,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
29
29
|
//#region package.json
|
|
30
30
|
var package_default = {
|
|
31
31
|
name: "@kyubiware/commit-mint",
|
|
32
|
-
version: "0.7.
|
|
32
|
+
version: "0.7.5",
|
|
33
33
|
description: "🌿 AI-powered git commit tool — auto-group changed files, generate messages, run pre-commit checks",
|
|
34
34
|
type: "module",
|
|
35
35
|
bin: { "cmint": "./dist/cli.mjs" },
|
|
@@ -1866,25 +1866,65 @@ async function showRecoveryMenu(errors, onRetry, onSkipHooks, onRestage, message
|
|
|
1866
1866
|
}
|
|
1867
1867
|
//#endregion
|
|
1868
1868
|
//#region src/ui/review-message.ts
|
|
1869
|
-
async function
|
|
1870
|
-
const {
|
|
1869
|
+
async function handleEdit(message) {
|
|
1870
|
+
const { text } = await import("@clack/prompts");
|
|
1871
|
+
const edited = await text({
|
|
1872
|
+
message: "Edit commit message:",
|
|
1873
|
+
initialValue: message,
|
|
1874
|
+
validate: (v) => v?.trim() ? void 0 : "Message cannot be empty"
|
|
1875
|
+
});
|
|
1876
|
+
if (isCancel(edited)) {
|
|
1877
|
+
debug("User cancelled edit, returning to review menu");
|
|
1878
|
+
return null;
|
|
1879
|
+
}
|
|
1880
|
+
const newMessage = String(edited).trim();
|
|
1881
|
+
debug("Edited message:", newMessage);
|
|
1882
|
+
return newMessage;
|
|
1883
|
+
}
|
|
1884
|
+
async function handleRegenerate(regenerate) {
|
|
1885
|
+
const { log, text } = await import("@clack/prompts");
|
|
1886
|
+
const hint = await text({
|
|
1887
|
+
message: "Describe what this commit is about to guide regeneration:",
|
|
1888
|
+
validate: (v) => v?.trim() ? void 0 : "Hint cannot be empty"
|
|
1889
|
+
});
|
|
1890
|
+
if (isCancel(hint)) {
|
|
1891
|
+
debug("User cancelled hint entry, returning to review menu");
|
|
1892
|
+
return null;
|
|
1893
|
+
}
|
|
1894
|
+
const hintValue = String(hint).trim();
|
|
1895
|
+
debug("Regenerating with hint:", hintValue);
|
|
1896
|
+
try {
|
|
1897
|
+
const newMessage = await regenerate(hintValue);
|
|
1898
|
+
debug("Regenerated message:", newMessage);
|
|
1899
|
+
return newMessage;
|
|
1900
|
+
} catch (err) {
|
|
1901
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1902
|
+
debug("Regeneration failed:", errMsg);
|
|
1903
|
+
log.warn(red(`Regeneration failed: ${errMsg}`));
|
|
1904
|
+
return null;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
async function reviewCommitMessage(message, options) {
|
|
1908
|
+
const { select } = await import("@clack/prompts");
|
|
1871
1909
|
while (true) {
|
|
1910
|
+
const reviewOptions = [{
|
|
1911
|
+
label: "Use as-is",
|
|
1912
|
+
value: "use"
|
|
1913
|
+
}, {
|
|
1914
|
+
label: "Edit",
|
|
1915
|
+
value: "edit"
|
|
1916
|
+
}];
|
|
1917
|
+
if (options?.regenerate) reviewOptions.push({
|
|
1918
|
+
label: "Regenerate with hint",
|
|
1919
|
+
value: "regenerate"
|
|
1920
|
+
});
|
|
1921
|
+
reviewOptions.push({
|
|
1922
|
+
label: "Cancel",
|
|
1923
|
+
value: "cancel"
|
|
1924
|
+
});
|
|
1872
1925
|
const review = await select({
|
|
1873
1926
|
message: `Review commit message:\n\n ${bold(message)}\n`,
|
|
1874
|
-
options:
|
|
1875
|
-
{
|
|
1876
|
-
label: "Use as-is",
|
|
1877
|
-
value: "use"
|
|
1878
|
-
},
|
|
1879
|
-
{
|
|
1880
|
-
label: "Edit",
|
|
1881
|
-
value: "edit"
|
|
1882
|
-
},
|
|
1883
|
-
{
|
|
1884
|
-
label: "Cancel",
|
|
1885
|
-
value: "cancel"
|
|
1886
|
-
}
|
|
1887
|
-
]
|
|
1927
|
+
options: reviewOptions
|
|
1888
1928
|
});
|
|
1889
1929
|
if (isCancel(review) || review === "cancel") {
|
|
1890
1930
|
debug("User cancelled at review step");
|
|
@@ -1894,17 +1934,8 @@ async function reviewCommitMessage(message) {
|
|
|
1894
1934
|
debug("User accepted message");
|
|
1895
1935
|
return message;
|
|
1896
1936
|
}
|
|
1897
|
-
if (review === "edit")
|
|
1898
|
-
|
|
1899
|
-
const edited = await text({
|
|
1900
|
-
message: "Edit commit message:",
|
|
1901
|
-
initialValue: message,
|
|
1902
|
-
validate: (v) => v?.trim() ? void 0 : "Message cannot be empty"
|
|
1903
|
-
});
|
|
1904
|
-
if (isCancel(edited)) continue;
|
|
1905
|
-
message = String(edited).trim();
|
|
1906
|
-
debug("Edited message:", message);
|
|
1907
|
-
}
|
|
1937
|
+
if (review === "edit") message = await handleEdit(message) ?? message;
|
|
1938
|
+
if (review === "regenerate" && options?.regenerate) message = await handleRegenerate(options.regenerate) ?? message;
|
|
1908
1939
|
}
|
|
1909
1940
|
}
|
|
1910
1941
|
//#endregion
|
|
@@ -2019,7 +2050,19 @@ async function runAutoGroupFlow(changedFiles, flags) {
|
|
|
2019
2050
|
log.info(dim(message));
|
|
2020
2051
|
if (flags.auto) debug("Auto mode: accepting generated message");
|
|
2021
2052
|
else {
|
|
2022
|
-
const reviewed = await reviewCommitMessage(message)
|
|
2053
|
+
const reviewed = await reviewCommitMessage(message, { regenerate: async (hint) => {
|
|
2054
|
+
const combinedHint = flags.hint ? `${flags.hint}\n${hint}` : hint;
|
|
2055
|
+
debug("Regenerating with combined hint:", combinedHint);
|
|
2056
|
+
s.start("Regenerating commit message...");
|
|
2057
|
+
try {
|
|
2058
|
+
const newMessage = await generateMessage(diffResult.diff, combinedHint);
|
|
2059
|
+
s.stop("Message regenerated");
|
|
2060
|
+
return newMessage;
|
|
2061
|
+
} catch (err) {
|
|
2062
|
+
s.stop(red("Regeneration failed"));
|
|
2063
|
+
throw err;
|
|
2064
|
+
}
|
|
2065
|
+
} });
|
|
2023
2066
|
if (reviewed === null) {
|
|
2024
2067
|
outro(dim("Cancelled."));
|
|
2025
2068
|
return "cancelled";
|
|
@@ -2311,7 +2354,6 @@ async function agentCommand(flags) {
|
|
|
2311
2354
|
//#endregion
|
|
2312
2355
|
//#region src/services/update-check.ts
|
|
2313
2356
|
const REGISTRY_URL = "https://registry.npmjs.org/-/package/@kyubiware/commit-mint/dist-tags";
|
|
2314
|
-
const PACKAGE_NAME$1 = "@kyubiware/commit-mint";
|
|
2315
2357
|
const TTL_MS = 1440 * 60 * 1e3;
|
|
2316
2358
|
const FETCH_TIMEOUT_MS = 5e3;
|
|
2317
2359
|
let cachePath = join(os.homedir(), ".cache", "commit-mint", "update-check.json");
|
|
@@ -2396,7 +2438,7 @@ async function fetchLatest(parentSignal) {
|
|
|
2396
2438
|
}
|
|
2397
2439
|
function displayNag(current, latest) {
|
|
2398
2440
|
debug("displayNag: %s → %s", current, latest);
|
|
2399
|
-
const message = `Update available: ${yellow(current)} → ${green(latest)}\nRun ${cyan(
|
|
2441
|
+
const message = `Update available: ${yellow(current)} → ${green(latest)}\nRun ${cyan("cmint update")} to update`;
|
|
2400
2442
|
log.warn(message);
|
|
2401
2443
|
}
|
|
2402
2444
|
/**
|
|
@@ -2404,11 +2446,16 @@ function displayNag(current, latest) {
|
|
|
2404
2446
|
* {@link checkForUpdatesUpfront}. Accepts an optional AbortSignal that
|
|
2405
2447
|
* propagates to the underlying fetch — used by the cancellable spinner.
|
|
2406
2448
|
*
|
|
2449
|
+
* When an update is available, {@link onNag} is invoked (defaulting to
|
|
2450
|
+
* {@link displayNag}). The cancellable spinner path passes a capturing
|
|
2451
|
+
* callback so it can stop the spinner BEFORE the nag prints — otherwise
|
|
2452
|
+
* `log.warn` interleaves with the spinner line and leaves it on screen.
|
|
2453
|
+
*
|
|
2407
2454
|
* Returns an {@link UpdateCheckStatus} so the caller can distinguish a
|
|
2408
2455
|
* real fetch that found the user current (eligible for "You are on the
|
|
2409
2456
|
* latest version" feedback) from a silent cache hit.
|
|
2410
2457
|
*/
|
|
2411
|
-
async function runUpdateCheck(currentVersion, parentSignal) {
|
|
2458
|
+
async function runUpdateCheck(currentVersion, parentSignal, onNag = displayNag) {
|
|
2412
2459
|
debug("runUpdateCheck: currentVersion=%s", currentVersion);
|
|
2413
2460
|
if (shouldSkip(currentVersion)) {
|
|
2414
2461
|
debug("runUpdateCheck: skipped (NO_UPDATE_NOTIFIER / CI / NODE_ENV=test / non-TTY / invalid version)");
|
|
@@ -2419,7 +2466,7 @@ async function runUpdateCheck(currentVersion, parentSignal) {
|
|
|
2419
2466
|
if (cached && Date.now() - cached.checkedAt < TTL_MS) {
|
|
2420
2467
|
debug("runUpdateCheck: cache fresh (<%dh), skipping fetch", TTL_MS / 36e5);
|
|
2421
2468
|
if (semver.gt(cached.latest, currentVersion)) {
|
|
2422
|
-
|
|
2469
|
+
onNag(currentVersion, cached.latest);
|
|
2423
2470
|
return "cache-update";
|
|
2424
2471
|
}
|
|
2425
2472
|
debug("runUpdateCheck: current >= latest, no nag");
|
|
@@ -2437,7 +2484,7 @@ async function runUpdateCheck(currentVersion, parentSignal) {
|
|
|
2437
2484
|
checkedAt: Date.now()
|
|
2438
2485
|
});
|
|
2439
2486
|
if (semver.gt(latest, currentVersion)) {
|
|
2440
|
-
|
|
2487
|
+
onNag(currentVersion, latest);
|
|
2441
2488
|
return "fetch-update";
|
|
2442
2489
|
}
|
|
2443
2490
|
debug("runUpdateCheck: current >= latest, no nag");
|
|
@@ -2560,9 +2607,15 @@ async function runCheckWithSpinner(currentVersion) {
|
|
|
2560
2607
|
reportFetchCurrent(await runUpdateCheck(currentVersion));
|
|
2561
2608
|
return;
|
|
2562
2609
|
}
|
|
2610
|
+
const captured = { nag: null };
|
|
2563
2611
|
let status;
|
|
2564
2612
|
try {
|
|
2565
|
-
status = await runUpdateCheck(currentVersion, controller.signal)
|
|
2613
|
+
status = await runUpdateCheck(currentVersion, controller.signal, (current, latest) => {
|
|
2614
|
+
captured.nag = {
|
|
2615
|
+
current,
|
|
2616
|
+
latest
|
|
2617
|
+
};
|
|
2618
|
+
});
|
|
2566
2619
|
} finally {
|
|
2567
2620
|
handler.cleanup();
|
|
2568
2621
|
}
|
|
@@ -2570,8 +2623,10 @@ async function runCheckWithSpinner(currentVersion) {
|
|
|
2570
2623
|
debug("runCheckWithSpinner: spinner dismissed by user");
|
|
2571
2624
|
s.stop("Skipped");
|
|
2572
2625
|
} else if (status === "fetch-current") s.stop(green("You are on the latest version"));
|
|
2573
|
-
else if (status === "fetch-update"
|
|
2574
|
-
|
|
2626
|
+
else if (status === "fetch-update" || status === "cache-update") {
|
|
2627
|
+
s.stop("");
|
|
2628
|
+
if (captured.nag) displayNag(captured.nag.current, captured.nag.latest);
|
|
2629
|
+
} else if (status === "fetch-failed-or-aborted") s.stop("Update check failed");
|
|
2575
2630
|
else s.stop("");
|
|
2576
2631
|
}
|
|
2577
2632
|
//#endregion
|
|
@@ -2997,11 +3052,13 @@ async function runPreCommitChecks(changedFiles, noCheck) {
|
|
|
2997
3052
|
}
|
|
2998
3053
|
/**
|
|
2999
3054
|
* Re-stage staged files whose working-tree content diverged from the index after checks ran.
|
|
3000
|
-
*
|
|
3055
|
+
* Signals (git status --short, 2-char XY code):
|
|
3056
|
+
* "MM" — tracked file staged-modified, then reformatted on disk
|
|
3057
|
+
* "AM" — newly-added file staged, then reformatted on disk
|
|
3001
3058
|
*/
|
|
3002
3059
|
async function restageFormatterModifications(stagedFileList) {
|
|
3003
3060
|
const checkedSet = new Set(stagedFileList);
|
|
3004
|
-
const modifiedByChecks = (await getChangedFiles()).filter((f) => checkedSet.has(f.path) && f.staged && f.status === "MM").map((f) => f.path);
|
|
3061
|
+
const modifiedByChecks = (await getChangedFiles()).filter((f) => checkedSet.has(f.path) && f.staged && (f.status === "MM" || f.status === "AM")).map((f) => f.path);
|
|
3005
3062
|
if (modifiedByChecks.length === 0) return;
|
|
3006
3063
|
debug("Re-staging %d file(s) modified by checks", modifiedByChecks.length);
|
|
3007
3064
|
await stageFiles(modifiedByChecks);
|
|
@@ -3115,7 +3172,19 @@ async function commitCommand(flags, version) {
|
|
|
3115
3172
|
}
|
|
3116
3173
|
s.stop("Message generated");
|
|
3117
3174
|
}
|
|
3118
|
-
const reviewed = await reviewCommitMessage(message)
|
|
3175
|
+
const reviewed = await reviewCommitMessage(message, { regenerate: async (hint) => {
|
|
3176
|
+
const combinedHint = flags.hint ? `${flags.hint}\n${hint}` : hint;
|
|
3177
|
+
debug("Regenerating with combined hint:", combinedHint);
|
|
3178
|
+
s.start("Regenerating commit message...");
|
|
3179
|
+
try {
|
|
3180
|
+
const newMessage = await generateMessage(diffResult.diff, combinedHint);
|
|
3181
|
+
s.stop("Message regenerated");
|
|
3182
|
+
return newMessage;
|
|
3183
|
+
} catch (err) {
|
|
3184
|
+
s.stop(red("Regeneration failed"));
|
|
3185
|
+
throw err;
|
|
3186
|
+
}
|
|
3187
|
+
} });
|
|
3119
3188
|
if (reviewed === null) {
|
|
3120
3189
|
outro(dim("Cancelled."));
|
|
3121
3190
|
return;
|