@kyubiware/commit-mint 0.10.1 → 0.11.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/dist/bin.mjs +118 -100
- package/dist/bin.mjs.map +1 -1
- package/package.json +1 -1
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.11.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" },
|
|
@@ -1756,8 +1756,8 @@ function buildGroupingUserPrompt(summary) {
|
|
|
1756
1756
|
summary
|
|
1757
1757
|
].join("\n");
|
|
1758
1758
|
}
|
|
1759
|
-
function buildRetryGroupingPrompt() {
|
|
1760
|
-
|
|
1759
|
+
function buildRetryGroupingPrompt(groupCount) {
|
|
1760
|
+
const lines = [
|
|
1761
1761
|
"PREVIOUS ATTEMPT FAILED: You grouped all files into a single group.",
|
|
1762
1762
|
"",
|
|
1763
1763
|
"You MUST split the files into at least 2 groups based on what changed and why.",
|
|
@@ -1778,7 +1778,12 @@ function buildRetryGroupingPrompt() {
|
|
|
1778
1778
|
"files: array of exact file paths from the input",
|
|
1779
1779
|
"",
|
|
1780
1780
|
"Output ONLY valid JSON. No markdown fences, no explanation."
|
|
1781
|
-
]
|
|
1781
|
+
];
|
|
1782
|
+
if (groupCount && groupCount > 0) {
|
|
1783
|
+
lines[0] = "PREVIOUS ATTEMPT FAILED: You created the wrong number of groups.";
|
|
1784
|
+
lines[2] = `You MUST create exactly ${groupCount} groups. Do not create more or fewer.`;
|
|
1785
|
+
}
|
|
1786
|
+
return lines.join("\n");
|
|
1782
1787
|
}
|
|
1783
1788
|
/**
|
|
1784
1789
|
* When a specific group count is requested but there are fewer included files
|
|
@@ -1826,9 +1831,9 @@ async function generateGroups(files, apiKey, model, timeout, provider, proxy, gr
|
|
|
1826
1831
|
debug("generateGroups: parsed %d raw groups", rawGroups.length);
|
|
1827
1832
|
let validated = validateGroups(rawGroups, included);
|
|
1828
1833
|
debug("generateGroups: %d validated groups", validated.length);
|
|
1829
|
-
if (isLowQualityGrouping(rawGroups, included)) {
|
|
1834
|
+
if (isLowQualityGrouping(rawGroups, included, groupCount)) {
|
|
1830
1835
|
debug("generateGroups: low quality result, retrying with stricter prompt");
|
|
1831
|
-
rawGroups = await callGroupingAI(client, resolvedModel, buildRetryGroupingPrompt(), userPrompt);
|
|
1836
|
+
rawGroups = await callGroupingAI(client, resolvedModel, buildRetryGroupingPrompt(groupCount), userPrompt);
|
|
1832
1837
|
debug("generateGroups retry: parsed %d raw groups", rawGroups.length);
|
|
1833
1838
|
validated = validateGroups(rawGroups, included);
|
|
1834
1839
|
debug("generateGroups retry: %d validated groups", validated.length);
|
|
@@ -1864,9 +1869,10 @@ async function callGroupingAI(client, model, systemPrompt, userPrompt) {
|
|
|
1864
1869
|
}
|
|
1865
1870
|
/** Minimum file count where a single-group result is considered low quality */
|
|
1866
1871
|
const MIN_FILES_FOR_QUALITY_CHECK = 5;
|
|
1867
|
-
function isLowQualityGrouping(groups, allFiles) {
|
|
1872
|
+
function isLowQualityGrouping(groups, allFiles, groupCount) {
|
|
1868
1873
|
if (allFiles.length === 0) return false;
|
|
1869
1874
|
if (groups.length === 0) return true;
|
|
1875
|
+
if (groupCount && groupCount > 0 && groups.length !== groupCount) return true;
|
|
1870
1876
|
if (allFiles.length < MIN_FILES_FOR_QUALITY_CHECK) return false;
|
|
1871
1877
|
return groups.length === 1;
|
|
1872
1878
|
}
|
|
@@ -4075,7 +4081,8 @@ async function handleStaging(changedFiles, flags) {
|
|
|
4075
4081
|
}
|
|
4076
4082
|
return {
|
|
4077
4083
|
changedFiles: currentFiles,
|
|
4078
|
-
skipStaging
|
|
4084
|
+
skipStaging,
|
|
4085
|
+
stagedAll: stageAllFlag
|
|
4079
4086
|
};
|
|
4080
4087
|
}
|
|
4081
4088
|
/** Run user-defined pre-commit checks from cmint config */
|
|
@@ -4123,6 +4130,7 @@ async function commitCommand(flags, version) {
|
|
|
4123
4130
|
let changedFiles = await getChangedFiles();
|
|
4124
4131
|
debug("Changed files:", changedFiles.length);
|
|
4125
4132
|
const s = spinner();
|
|
4133
|
+
let stagedAll = true;
|
|
4126
4134
|
try {
|
|
4127
4135
|
if (flags.single) {
|
|
4128
4136
|
debug("Single-commit mode: staging all files");
|
|
@@ -4138,6 +4146,7 @@ async function commitCommand(flags, version) {
|
|
|
4138
4146
|
const result = await handleStaging(changedFiles, flags);
|
|
4139
4147
|
if (!result) return;
|
|
4140
4148
|
changedFiles = result.changedFiles;
|
|
4149
|
+
stagedAll = result.stagedAll;
|
|
4141
4150
|
}
|
|
4142
4151
|
} catch (err) {
|
|
4143
4152
|
s.stop(red("Staging failed."));
|
|
@@ -4146,106 +4155,115 @@ async function commitCommand(flags, version) {
|
|
|
4146
4155
|
outro(red(`Failed to stage files: ${msg}`));
|
|
4147
4156
|
process.exit(1);
|
|
4148
4157
|
}
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
if ("excludedFiles" in diffResult) {
|
|
4158
|
-
debug("All staged files are excluded:", diffResult.excludedFiles);
|
|
4159
|
-
const message = buildExcludedFilesMessage(diffResult.excludedFiles);
|
|
4160
|
-
log.info(diffResult.excludedFiles.map((f) => ` ${f}`).join("\n"));
|
|
4161
|
-
await saveCachedCommit(repoRoot, message);
|
|
4162
|
-
s.start("Running pre-commit hooks...");
|
|
4163
|
-
const result = await commitWithRecovery(message, s, await getHead());
|
|
4164
|
-
if (result === "committed") {
|
|
4165
|
-
outro(green("Done."));
|
|
4166
|
-
return;
|
|
4158
|
+
while (true) {
|
|
4159
|
+
changedFiles = await getChangedFiles();
|
|
4160
|
+
if (!flags.noCheck && await getRunChecks()) await runPreCommitChecks(changedFiles, false);
|
|
4161
|
+
const diffResult = await getStagedDiff();
|
|
4162
|
+
if (!diffResult) {
|
|
4163
|
+
debug("No staged changes found after staging");
|
|
4164
|
+
outro(red("No staged changes found."));
|
|
4165
|
+
process.exit(1);
|
|
4167
4166
|
}
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4167
|
+
let commitResult;
|
|
4168
|
+
if ("excludedFiles" in diffResult) {
|
|
4169
|
+
debug("All staged files are excluded:", diffResult.excludedFiles);
|
|
4170
|
+
const message = buildExcludedFilesMessage(diffResult.excludedFiles);
|
|
4171
|
+
log.info(diffResult.excludedFiles.map((f) => ` ${f}`).join("\n"));
|
|
4172
|
+
await saveCachedCommit(repoRoot, message);
|
|
4173
|
+
s.start("Running pre-commit hooks...");
|
|
4174
|
+
commitResult = await commitWithRecovery(message, s, await getHead());
|
|
4175
|
+
} else {
|
|
4176
|
+
debug("Staged files:", diffResult.files);
|
|
4177
|
+
debug("Diff length:", diffResult.diff.length, "chars");
|
|
4178
|
+
log.info(diffResult.files.map((f) => ` ${f}`).join("\n"));
|
|
4179
|
+
let message;
|
|
4180
|
+
if (flags.message) {
|
|
4181
|
+
debug("Using provided message:", flags.message);
|
|
4182
|
+
message = flags.message;
|
|
4183
|
+
} else {
|
|
4184
|
+
const config = await readConfig();
|
|
4185
|
+
const provider = isValidProvider(config.provider ?? "groq") ? config.provider : "groq";
|
|
4186
|
+
try {
|
|
4187
|
+
await getProviderApiKey(provider);
|
|
4188
|
+
debug("API key found");
|
|
4189
|
+
} catch {
|
|
4190
|
+
debug("No API key found, prompting user");
|
|
4191
|
+
const { text: promptText } = await import("@clack/prompts");
|
|
4192
|
+
const configKey = PROVIDER_ENV_KEYS[provider];
|
|
4193
|
+
const key = await promptText({
|
|
4194
|
+
message: `Enter your ${formatProviderName(provider)} API key:`,
|
|
4195
|
+
placeholder: provider === "groq" ? "gsk_..." : "...",
|
|
4196
|
+
validate: (v) => v?.trim() ? void 0 : "API key is required"
|
|
4197
|
+
});
|
|
4198
|
+
if (isCancel(key)) {
|
|
4199
|
+
outro(dim("Cancelled."));
|
|
4200
|
+
return;
|
|
4201
|
+
}
|
|
4202
|
+
await setConfigValue(configKey, String(key).trim());
|
|
4203
|
+
debug("API key saved to config");
|
|
4204
|
+
}
|
|
4205
|
+
s.start("Generating commit message...");
|
|
4206
|
+
try {
|
|
4207
|
+
const genStart = Date.now();
|
|
4208
|
+
message = await generateMessage(diffResult.diff, flags.hint);
|
|
4209
|
+
debug("generateMessage took %d ms", Date.now() - genStart);
|
|
4210
|
+
debug("Generated message:", message);
|
|
4211
|
+
} catch (err) {
|
|
4212
|
+
s.stop(red("Failed to generate message."));
|
|
4213
|
+
debug("Message generation failed:", err instanceof Error ? err.message : String(err));
|
|
4214
|
+
outro(red(err instanceof Error ? err.message : String(err)));
|
|
4215
|
+
return;
|
|
4216
|
+
}
|
|
4217
|
+
s.stop("Message generated");
|
|
4218
|
+
}
|
|
4219
|
+
if (flags.single || await getAutoAccept()) {
|
|
4220
|
+
debug("Auto-accept ON: skipping review step");
|
|
4221
|
+
log.info(message);
|
|
4222
|
+
} else {
|
|
4223
|
+
const reviewed = await reviewCommitMessage(message, { regenerate: async (hint) => {
|
|
4224
|
+
const combinedHint = flags.hint ? `${flags.hint}\n${hint}` : hint;
|
|
4225
|
+
debug("Regenerating with combined hint:", combinedHint);
|
|
4226
|
+
s.start("Regenerating commit message...");
|
|
4227
|
+
try {
|
|
4228
|
+
const newMessage = await generateMessage(diffResult.diff, combinedHint);
|
|
4229
|
+
s.stop("Message regenerated");
|
|
4230
|
+
return newMessage;
|
|
4231
|
+
} catch (err) {
|
|
4232
|
+
s.stop(red("Regeneration failed"));
|
|
4233
|
+
throw err;
|
|
4234
|
+
}
|
|
4235
|
+
} });
|
|
4236
|
+
if (reviewed === null) {
|
|
4237
|
+
outro(dim("Cancelled."));
|
|
4238
|
+
return;
|
|
4239
|
+
}
|
|
4240
|
+
message = reviewed;
|
|
4196
4241
|
}
|
|
4197
|
-
await
|
|
4198
|
-
debug("
|
|
4242
|
+
await saveCachedCommit(repoRoot, message);
|
|
4243
|
+
debug("Message cached for repo:", repoRoot);
|
|
4244
|
+
s.start("Running pre-commit hooks...");
|
|
4245
|
+
const headBefore = await getHead();
|
|
4246
|
+
debug("HEAD before commit:", headBefore);
|
|
4247
|
+
commitResult = await commitWithRecovery(message, s, headBefore);
|
|
4248
|
+
debug("Commit result:", commitResult);
|
|
4199
4249
|
}
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
message = await generateMessage(diffResult.diff, flags.hint);
|
|
4204
|
-
debug("generateMessage took %d ms", Date.now() - genStart);
|
|
4205
|
-
debug("Generated message:", message);
|
|
4206
|
-
} catch (err) {
|
|
4207
|
-
s.stop(red("Failed to generate message."));
|
|
4208
|
-
debug("Message generation failed:", err instanceof Error ? err.message : String(err));
|
|
4209
|
-
outro(red(err instanceof Error ? err.message : String(err)));
|
|
4250
|
+
if (commitResult === "cancelled") process.exit(1);
|
|
4251
|
+
if (stagedAll) {
|
|
4252
|
+
outro(green("Done."));
|
|
4210
4253
|
return;
|
|
4211
4254
|
}
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
debug("Auto-accept ON: skipping review step");
|
|
4216
|
-
log.info(message);
|
|
4217
|
-
} else {
|
|
4218
|
-
const reviewed = await reviewCommitMessage(message, { regenerate: async (hint) => {
|
|
4219
|
-
const combinedHint = flags.hint ? `${flags.hint}\n${hint}` : hint;
|
|
4220
|
-
debug("Regenerating with combined hint:", combinedHint);
|
|
4221
|
-
s.start("Regenerating commit message...");
|
|
4222
|
-
try {
|
|
4223
|
-
const newMessage = await generateMessage(diffResult.diff, combinedHint);
|
|
4224
|
-
s.stop("Message regenerated");
|
|
4225
|
-
return newMessage;
|
|
4226
|
-
} catch (err) {
|
|
4227
|
-
s.stop(red("Regeneration failed"));
|
|
4228
|
-
throw err;
|
|
4229
|
-
}
|
|
4230
|
-
} });
|
|
4231
|
-
if (reviewed === null) {
|
|
4232
|
-
outro(dim("Cancelled."));
|
|
4255
|
+
const remaining = await getChangedFiles();
|
|
4256
|
+
if (remaining.length === 0) {
|
|
4257
|
+
outro(green("Done."));
|
|
4233
4258
|
return;
|
|
4234
4259
|
}
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
debug("HEAD before commit:", headBefore);
|
|
4242
|
-
const result = await commitWithRecovery(message, s, headBefore);
|
|
4243
|
-
debug("Commit result:", result);
|
|
4244
|
-
if (result === "committed") {
|
|
4245
|
-
outro(green("Done."));
|
|
4246
|
-
return;
|
|
4260
|
+
debug("Uncommitted files remain after commit:", remaining.length);
|
|
4261
|
+
log.info(`${remaining.length} file${remaining.length !== 1 ? "s" : ""} remaining — continuing with the staging menu`);
|
|
4262
|
+
const stagingResult = await handleStaging(remaining, flags);
|
|
4263
|
+
if (!stagingResult) return;
|
|
4264
|
+
changedFiles = stagingResult.changedFiles;
|
|
4265
|
+
stagedAll = stagingResult.stagedAll;
|
|
4247
4266
|
}
|
|
4248
|
-
if (result === "cancelled") process.exit(1);
|
|
4249
4267
|
}
|
|
4250
4268
|
//#endregion
|
|
4251
4269
|
//#region src/cli.ts
|