@kyubiware/commit-mint 0.9.3 β 0.9.4
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 +3 -3
- package/dist/bin.mjs +92 -50
- package/dist/bin.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -274,7 +274,7 @@ returns either.
|
|
|
274
274
|
| Form | Behavior |
|
|
275
275
|
|---|---|
|
|
276
276
|
| `string` | Matched files are appended as trailing arguments. Paths with spaces are quoted automatically. |
|
|
277
|
-
| `string[]` | Commands run sequentially, each as a separate command.
|
|
277
|
+
| `string[]` | Commands run sequentially, each as a separate command. All commands run regardless of failures. |
|
|
278
278
|
| `(files) => string \| string[]` | Function receives the matched files. Use when the command depends on the file list. |
|
|
279
279
|
|
|
280
280
|
**String command:**
|
|
@@ -313,8 +313,8 @@ export default {
|
|
|
313
313
|
|
|
314
314
|
- Checks run after `git add`, before the AI call.
|
|
315
315
|
- Globs are processed in declaration order.
|
|
316
|
-
- Commands run sequentially per glob.
|
|
317
|
-
and
|
|
316
|
+
- Commands run sequentially per glob. All commands and all globs always run;
|
|
317
|
+
failures are collected and returned together.
|
|
318
318
|
- 60s timeout per command. ENOENT (command not found) and timeouts are
|
|
319
319
|
reported back to the menu as their own error.
|
|
320
320
|
- Skipped entirely with `cmint -N` or the `c` hotkey toggle.
|
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.
|
|
36
|
+
version: "0.9.4",
|
|
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" },
|
|
@@ -2287,30 +2287,29 @@ function resolveCommands(commands, matchedFiles) {
|
|
|
2287
2287
|
/**
|
|
2288
2288
|
* Run resolved commands for a single glob entry, appending results.
|
|
2289
2289
|
* Function-originated commands run as-is; string commands get matched files appended.
|
|
2290
|
-
*
|
|
2290
|
+
* All commands run regardless of individual failures.
|
|
2291
2291
|
*/
|
|
2292
|
-
async function runCommandsForGlob(cmds, matchedFiles, timeout, results, repoRoot) {
|
|
2292
|
+
async function runCommandsForGlob(cmds, matchedFiles, timeout, results, repoRoot, observer) {
|
|
2293
2293
|
for (const { command, fromFunction } of cmds) {
|
|
2294
2294
|
const fullCommand = fromFunction ? command : buildCommand(command, matchedFiles);
|
|
2295
|
+
const tool = extractToolName(fullCommand) ?? fullCommand.split(" ")[0];
|
|
2295
2296
|
debug("runCommandsForGlob: running '%s'", fullCommand);
|
|
2297
|
+
observer?.onStart?.(tool, fullCommand, matchedFiles);
|
|
2296
2298
|
const result = await runCommand(fullCommand, timeout, repoRoot);
|
|
2297
2299
|
results.push({
|
|
2298
2300
|
...result,
|
|
2299
2301
|
files: matchedFiles
|
|
2300
2302
|
});
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
return false;
|
|
2304
|
-
}
|
|
2303
|
+
observer?.onResult?.(result);
|
|
2304
|
+
if (!result.ok) debug("runCommandsForGlob: check failed, continuing to next command");
|
|
2305
2305
|
}
|
|
2306
|
-
return true;
|
|
2307
2306
|
}
|
|
2308
2307
|
/**
|
|
2309
2308
|
* Run all user-defined checks from .cmintrc against staged files.
|
|
2310
2309
|
* Returns a no-op result when no config exists.
|
|
2311
|
-
*
|
|
2310
|
+
* All checks always run; failures are collected and returned together.
|
|
2312
2311
|
*/
|
|
2313
|
-
async function runAllChecks(repoRoot, stagedFiles, timeout) {
|
|
2312
|
+
async function runAllChecks(repoRoot, stagedFiles, timeout, observer) {
|
|
2314
2313
|
debug("runAllChecks: %d staged files, checking for config in %s", stagedFiles.length, repoRoot);
|
|
2315
2314
|
if (!await detectConfig(repoRoot)) {
|
|
2316
2315
|
debug("runAllChecks: no config found, skipping checks");
|
|
@@ -2329,10 +2328,7 @@ async function runAllChecks(repoRoot, stagedFiles, timeout) {
|
|
|
2329
2328
|
continue;
|
|
2330
2329
|
}
|
|
2331
2330
|
debug("runAllChecks: pattern '%s' matched %d files", glob, matchedFiles.length);
|
|
2332
|
-
|
|
2333
|
-
ok: false,
|
|
2334
|
-
results
|
|
2335
|
-
};
|
|
2331
|
+
await runCommandsForGlob(resolveCommands(commands, matchedFiles), matchedFiles, timeout, results, repoRoot, observer);
|
|
2336
2332
|
}
|
|
2337
2333
|
const ok = results.every((r) => r.ok);
|
|
2338
2334
|
debug("runAllChecks: complete β ok=%s, %d results", ok, results.length);
|
|
@@ -2414,7 +2410,9 @@ function extractEslintDiagnostics(raw) {
|
|
|
2414
2410
|
function extractTestFailures(raw) {
|
|
2415
2411
|
const failures = [];
|
|
2416
2412
|
const seen = /* @__PURE__ */ new Set();
|
|
2417
|
-
|
|
2413
|
+
const lines = raw.split("\n");
|
|
2414
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2415
|
+
const line = lines[i];
|
|
2418
2416
|
const match = TEST_FILE_FAIL.exec(line);
|
|
2419
2417
|
if (!match) continue;
|
|
2420
2418
|
const file = (match[1] ?? "").trim();
|
|
@@ -2423,13 +2421,35 @@ function extractTestFailures(raw) {
|
|
|
2423
2421
|
const key = `${file}\u0000${name}`;
|
|
2424
2422
|
if (seen.has(key)) continue;
|
|
2425
2423
|
seen.add(key);
|
|
2424
|
+
const message = scanForwardForMessage(lines, i + 1, TEST_FILE_FAIL);
|
|
2426
2425
|
failures.push({
|
|
2427
2426
|
file,
|
|
2428
|
-
name
|
|
2427
|
+
name,
|
|
2428
|
+
message
|
|
2429
2429
|
});
|
|
2430
2430
|
}
|
|
2431
2431
|
return failures;
|
|
2432
2432
|
}
|
|
2433
|
+
/**
|
|
2434
|
+
* Scan forward from `startIndex` to find the first assertion error message line
|
|
2435
|
+
* after a FAIL header. Skips blank lines, separator dashes, stack frames,
|
|
2436
|
+
* subsequent FAIL lines, and vitest summary footers.
|
|
2437
|
+
*/
|
|
2438
|
+
function scanForwardForMessage(lines, startIndex, testFileFailRegex) {
|
|
2439
|
+
for (let j = startIndex; j < lines.length; j++) {
|
|
2440
|
+
const rawLine = lines[j];
|
|
2441
|
+
const trimmed = rawLine.trim();
|
|
2442
|
+
if (!trimmed) continue;
|
|
2443
|
+
const stripped = trimmed.replace(/\s/g, "");
|
|
2444
|
+
if (stripped.length > 5) {
|
|
2445
|
+
if ((stripped.match(/[β―\-β]/g) ?? []).length / stripped.length > .5) continue;
|
|
2446
|
+
}
|
|
2447
|
+
if (/^\s*[β―>]\s/.test(rawLine)) continue;
|
|
2448
|
+
if (testFileFailRegex.test(trimmed)) break;
|
|
2449
|
+
if (/^(Test Files|Tests)\s/.test(trimmed)) break;
|
|
2450
|
+
return truncate(trimmed, MAX_SUMMARY_LINE_LENGTH);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2433
2453
|
function formatTestFailureSummary(failures, tool) {
|
|
2434
2454
|
const total = failures.length;
|
|
2435
2455
|
const visible = failures.slice(0, MAX_TEST_FAILURES);
|
|
@@ -2440,13 +2460,19 @@ function formatTestFailureSummary(failures, tool) {
|
|
|
2440
2460
|
const lines = [` ${red("β’")} [${tool}] ${total} failed ${testNoun} in ${fileCount} ${fileNoun}`];
|
|
2441
2461
|
const byFile = /* @__PURE__ */ new Map();
|
|
2442
2462
|
for (const failure of visible) {
|
|
2443
|
-
const
|
|
2444
|
-
|
|
2445
|
-
|
|
2463
|
+
const entries = byFile.get(failure.file) ?? [];
|
|
2464
|
+
entries.push({
|
|
2465
|
+
name: failure.name,
|
|
2466
|
+
message: failure.message
|
|
2467
|
+
});
|
|
2468
|
+
byFile.set(failure.file, entries);
|
|
2446
2469
|
}
|
|
2447
|
-
for (const [file,
|
|
2470
|
+
for (const [file, entries] of byFile) {
|
|
2448
2471
|
lines.push(` ${truncate(file, MAX_SUMMARY_LINE_LENGTH)}`);
|
|
2449
|
-
for (const
|
|
2472
|
+
for (const entry of entries) {
|
|
2473
|
+
lines.push(` ${red("Γ")} ${truncate(entry.name, MAX_SUMMARY_LINE_LENGTH)}`);
|
|
2474
|
+
if (entry.message) lines.push(` ${dim("β")} ${entry.message}`);
|
|
2475
|
+
}
|
|
2450
2476
|
}
|
|
2451
2477
|
if (hidden > 0) lines.push(dim(` +${hidden} more failed ${hidden === 1 ? "test" : "tests"}. View full output for details.`));
|
|
2452
2478
|
return lines.join("\n");
|
|
@@ -2543,24 +2569,42 @@ async function showCheckFailureMenu(errors, rawStderr, onRetry) {
|
|
|
2543
2569
|
}
|
|
2544
2570
|
}
|
|
2545
2571
|
//#endregion
|
|
2546
|
-
//#region src/ui/check-
|
|
2572
|
+
//#region src/ui/check-progress.ts
|
|
2547
2573
|
/**
|
|
2548
|
-
*
|
|
2574
|
+
* Creates a live check progress display.
|
|
2575
|
+
*
|
|
2576
|
+
* - Each check gets its own line. While a check runs, a spinner animates
|
|
2577
|
+
* in the status position. When it completes, the spinner becomes a β or β.
|
|
2578
|
+
* - There is no separate "Running checksβ¦" header β the spinner IS the
|
|
2579
|
+
* check line.
|
|
2580
|
+
* - When all checks finish, a `log.info` summary line is emitted.
|
|
2549
2581
|
*
|
|
2550
|
-
*
|
|
2551
|
-
*
|
|
2552
|
-
* - On failure: stops with "N checks failed" (pluralized). Raw error output
|
|
2553
|
-
* is intentionally NOT printed here β callers handle failure display
|
|
2554
|
-
* (menu, raw print, etc.).
|
|
2582
|
+
* Returns a `CheckObserver` (pass directly to `runAllChecks`) plus a `finish()`
|
|
2583
|
+
* method that prints the final summary.
|
|
2555
2584
|
*/
|
|
2556
|
-
function
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2585
|
+
function createCheckProgressDisplay() {
|
|
2586
|
+
const s = spinner();
|
|
2587
|
+
const toolResults = [];
|
|
2588
|
+
let started = false;
|
|
2589
|
+
return {
|
|
2590
|
+
onStart(_tool, _command, _matchedFiles) {
|
|
2591
|
+
if (!started) {
|
|
2592
|
+
s.start(_tool);
|
|
2593
|
+
started = true;
|
|
2594
|
+
} else s.message(_tool);
|
|
2595
|
+
},
|
|
2596
|
+
onResult(result) {
|
|
2597
|
+
toolResults.push({
|
|
2598
|
+
tool: result.tool,
|
|
2599
|
+
ok: result.ok
|
|
2600
|
+
});
|
|
2601
|
+
s.message(`${result.ok ? "β" : "β"} ${result.tool}`);
|
|
2602
|
+
},
|
|
2603
|
+
finish(ok) {
|
|
2604
|
+
s.stop(ok ? green("All checks passed") : red("Some checks failed"));
|
|
2605
|
+
if (toolResults.length > 0) log.info(toolResults.map((r) => ` ${r.ok ? green("β") : red("β")} ${r.tool}`).join("\n"));
|
|
2606
|
+
}
|
|
2607
|
+
};
|
|
2564
2608
|
}
|
|
2565
2609
|
//#endregion
|
|
2566
2610
|
//#region src/commands/check-phase.ts
|
|
@@ -2569,7 +2613,7 @@ function stopCheckSpinner(spinner, results) {
|
|
|
2569
2613
|
*
|
|
2570
2614
|
* Single entry point for the check-execution pipeline shared by `runPreCommitChecks`
|
|
2571
2615
|
* (post-staging, normal commit flow) and `runAutoGroupFlow` (pre-staging, auto-group
|
|
2572
|
-
* flow). Encapsulates: detectConfig guard β
|
|
2616
|
+
* flow). Encapsulates: detectConfig guard β live progress display β runAllChecks β retry loop with
|
|
2573
2617
|
* `showCheckFailureMenu`.
|
|
2574
2618
|
*
|
|
2575
2619
|
* Caller responsibilities:
|
|
@@ -2585,10 +2629,9 @@ function stopCheckSpinner(spinner, results) {
|
|
|
2585
2629
|
async function runCheckPhaseInteractive(repoRoot, files, timeout, onRetry) {
|
|
2586
2630
|
if (!await detectConfig(repoRoot)) return "passed";
|
|
2587
2631
|
debug("Running user checks on %d files...", files.length);
|
|
2588
|
-
const
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
stopCheckSpinner(ck, checkResults);
|
|
2632
|
+
const display = createCheckProgressDisplay();
|
|
2633
|
+
let checkResults = await runAllChecks(repoRoot, files, timeout, display);
|
|
2634
|
+
display.finish(checkResults.ok);
|
|
2592
2635
|
debug("Check results: ok=%s, count=%d", checkResults.ok, checkResults.results.length);
|
|
2593
2636
|
while (!checkResults.ok) {
|
|
2594
2637
|
const rawOutput = checkResults.results.filter((r) => !r.ok).map((r) => `[${r.tool}]\n${r.stdout}\n${r.stderr}`.trim()).join("\n\n");
|
|
@@ -2599,9 +2642,9 @@ async function runCheckPhaseInteractive(repoRoot, files, timeout, onRetry) {
|
|
|
2599
2642
|
if (menuResult === "retried") {
|
|
2600
2643
|
debug("Re-running checks after retry...");
|
|
2601
2644
|
if (onRetry) await onRetry();
|
|
2602
|
-
|
|
2603
|
-
checkResults = await runAllChecks(repoRoot, files, timeout);
|
|
2604
|
-
|
|
2645
|
+
const retryDisplay = createCheckProgressDisplay();
|
|
2646
|
+
checkResults = await runAllChecks(repoRoot, files, timeout, retryDisplay);
|
|
2647
|
+
retryDisplay.finish(checkResults.ok);
|
|
2605
2648
|
debug("Retry check results: ok=%s, count=%d", checkResults.ok, checkResults.results.length);
|
|
2606
2649
|
continue;
|
|
2607
2650
|
}
|
|
@@ -3806,8 +3849,8 @@ function buildPromptRenderer(opts, state) {
|
|
|
3806
3849
|
const toggleList = opts.toggles;
|
|
3807
3850
|
return function() {
|
|
3808
3851
|
const sym = symbol(this.state);
|
|
3809
|
-
const statusLines = toggleList.map((t) => renderToggleState(t, state[t.hotkey])).join("\n");
|
|
3810
|
-
const header = `${sym} ${opts.message}\n${
|
|
3852
|
+
const statusLines = toggleList.map((t) => `${dim(S_BAR)} ${renderToggleState(t, state[t.hotkey])}`).join("\n");
|
|
3853
|
+
const header = `${sym} ${opts.message}\n${statusLines}`;
|
|
3811
3854
|
switch (this.state) {
|
|
3812
3855
|
case "submit": {
|
|
3813
3856
|
const selected = optionList[this.cursor];
|
|
@@ -3972,10 +4015,9 @@ async function handleStaging(changedFiles, flags) {
|
|
|
3972
4015
|
await stageAll();
|
|
3973
4016
|
const allFiles = await getStagedFiles();
|
|
3974
4017
|
if (await detectConfig(repoRoot)) {
|
|
3975
|
-
const
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
stopCheckSpinner(ckSpinner, ckResult);
|
|
4018
|
+
const display = createCheckProgressDisplay();
|
|
4019
|
+
const ckResult = await runAllChecks(repoRoot, allFiles, 6e4, display);
|
|
4020
|
+
display.finish(ckResult.ok);
|
|
3979
4021
|
if (!ckResult.ok) for (const r of ckResult.results.filter((r) => !r.ok)) log.info(r.stderr?.trim() || r.stdout?.trim() || `Check failed: ${r.command}`);
|
|
3980
4022
|
}
|
|
3981
4023
|
currentFiles = await getChangedFiles();
|