@webywz/vibe-commit 0.1.6 → 0.1.8

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.
@@ -21,9 +21,13 @@ export declare function detectBreakingKind(diff: StagedDiff, forced?: boolean):
21
21
  export declare function formatBreakingReason(kind: BreakingKind): string;
22
22
  /** Detail text for the BREAKING CHANGE footer, or undefined when not inferred. */
23
23
  export declare function breakingFooterDetail(diff: StagedDiff): string | undefined;
24
+ /** Generic bilingual footer when no better reason (or empty subject) exists. */
25
+ export declare const DEFAULT_BREAKING_FOOTER_DETAIL = "incompatible API change \u00B7 \u4E0D\u517C\u5BB9\u7684 API \u53D8\u66F4";
24
26
  /**
25
27
  * Ensure body carries a conventional BREAKING CHANGE footer when breaking.
26
- * Skips if the body already mentions one. Uses subject as a last-resort reason.
28
+ * Skips if the body already mentions one.
29
+ * Prefers subject when reason is empty or the generic bilingual default.
30
+ * Heuristic major/export/footer-specific bilingual details are kept as-is.
27
31
  */
28
32
  export declare function ensureBreakingFooter(message: ProposedMessage, reason?: string): ProposedMessage;
29
33
  export declare function collectAddedIdentifiers(hunks: DiffHunk[]): string[];
package/dist/heuristic.js CHANGED
@@ -37,7 +37,9 @@ export function proposeHeuristic(input) {
37
37
  engine: "heuristic",
38
38
  breakingReason: kind ? formatBreakingReason(kind) : undefined,
39
39
  };
40
- return ensureBreakingFooter(message, breakingFooterDetail(diff));
40
+ // Forced --breaking: prefer subject in the footer (not bilingual default).
41
+ const footerReason = kind === "forced" ? message.subject : breakingFooterDetail(diff);
42
+ return ensureBreakingFooter(message, footerReason);
41
43
  }
42
44
  export function inferType(diff) {
43
45
  const files = diff.files;
@@ -260,17 +262,22 @@ export function breakingFooterDetail(diff) {
260
262
  if (removed.length > 0) {
261
263
  const names = removed.slice(0, 3).join(", ");
262
264
  return removed.length === 1
263
- ? `remove public export ${names}`
264
- : `remove public exports ${names}`;
265
+ ? `remove public export ${names} · 删除公开 export ${names}`
266
+ : `remove public exports ${names} · 删除公开 exports ${names}`;
265
267
  }
266
268
  const bump = detectMajorVersionBump(diff);
267
- if (bump)
268
- return `bump major version to ${bump.to}`;
269
+ if (bump) {
270
+ return `bump major version to ${bump.to} · 主版本升至 ${bump.to}`;
271
+ }
269
272
  return undefined;
270
273
  }
274
+ /** Generic bilingual footer when no better reason (or empty subject) exists. */
275
+ export const DEFAULT_BREAKING_FOOTER_DETAIL = "incompatible API change · 不兼容的 API 变更";
271
276
  /**
272
277
  * Ensure body carries a conventional BREAKING CHANGE footer when breaking.
273
- * Skips if the body already mentions one. Uses subject as a last-resort reason.
278
+ * Skips if the body already mentions one.
279
+ * Prefers subject when reason is empty or the generic bilingual default.
280
+ * Heuristic major/export/footer-specific bilingual details are kept as-is.
274
281
  */
275
282
  export function ensureBreakingFooter(message, reason) {
276
283
  if (!message.breaking)
@@ -279,8 +286,13 @@ export function ensureBreakingFooter(message, reason) {
279
286
  if (/BREAKING CHANGE:/i.test(body)) {
280
287
  return body === (message.body ?? "") ? message : { ...message, body };
281
288
  }
282
- const detail = (reason ?? message.subject).replace(/\s+/g, " ").trim() ||
283
- "incompatible API change";
289
+ const subject = message.subject.replace(/\s+/g, " ").trim();
290
+ const normalized = (reason ?? "").replace(/\s+/g, " ").trim();
291
+ const useSubject = Boolean(subject) &&
292
+ (!normalized || normalized === DEFAULT_BREAKING_FOOTER_DETAIL);
293
+ const detail = (useSubject ? subject : normalized) ||
294
+ subject ||
295
+ DEFAULT_BREAKING_FOOTER_DETAIL;
284
296
  const footer = `BREAKING CHANGE: ${detail}`;
285
297
  return {
286
298
  ...message,
@@ -297,7 +309,7 @@ function detectBreakingFooter(diff) {
297
309
  }
298
310
  // Presence of the token alone still counts as breaking.
299
311
  if (diff.hunks.some((hunk) => hunk.lines.some((line) => /^\+\s*BREAKING CHANGE:/i.test(line)))) {
300
- return "incompatible API change";
312
+ return DEFAULT_BREAKING_FOOTER_DETAIL;
301
313
  }
302
314
  return undefined;
303
315
  }
@@ -340,6 +352,20 @@ function exportNameFromDeletedLine(line) {
340
352
  return def[1];
341
353
  return undefined;
342
354
  }
355
+ /** Collect every `"version": "..."` value on a +/- diff line (incl. minified JSON). */
356
+ function versionsOnDiffLine(line) {
357
+ if (!(line.startsWith("-") || line.startsWith("+")))
358
+ return [];
359
+ if (line.startsWith("---") || line.startsWith("+++"))
360
+ return [];
361
+ const versions = [];
362
+ const re = /"version"\s*:\s*"([^"]+)"/g;
363
+ for (const match of line.slice(1).matchAll(re)) {
364
+ if (match[1])
365
+ versions.push(match[1]);
366
+ }
367
+ return versions;
368
+ }
343
369
  function detectMajorVersionBump(diff) {
344
370
  for (const hunk of diff.hunks) {
345
371
  if (!/(^|\/)package\.json$/i.test(hunk.path))
@@ -347,12 +373,13 @@ function detectMajorVersionBump(diff) {
347
373
  const removed = [];
348
374
  const added = [];
349
375
  for (const line of hunk.lines) {
350
- const rem = line.match(/^-\s*"version"\s*:\s*"([^"]+)"/);
351
- const add = line.match(/^\+\s*"version"\s*:\s*"([^"]+)"/);
352
- if (rem?.[1])
353
- removed.push(rem[1]);
354
- if (add?.[1])
355
- added.push(add[1]);
376
+ const versions = versionsOnDiffLine(line);
377
+ if (versions.length === 0)
378
+ continue;
379
+ if (line.startsWith("-"))
380
+ removed.push(...versions);
381
+ else if (line.startsWith("+"))
382
+ added.push(...versions);
356
383
  }
357
384
  for (const from of removed) {
358
385
  for (const to of added) {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { proposeWithAi } from "./ai.ts";
2
2
  export { formatCommit, parseCommit, normalizeSubject } from "./format.ts";
3
3
  export { inspectStaged, commitWithMessage, GitError } from "./git.ts";
4
- export { proposeHeuristic, inferType, inferScope, inferSubject, inferBreaking, detectBreakingKind, formatBreakingReason, breakingFooterDetail, ensureBreakingFooter, classifyPath, } from "./heuristic.ts";
4
+ export { proposeHeuristic, inferType, inferScope, inferSubject, inferBreaking, detectBreakingKind, formatBreakingReason, breakingFooterDetail, DEFAULT_BREAKING_FOOTER_DETAIL, ensureBreakingFooter, classifyPath, } from "./heuristic.ts";
5
5
  export type { BreakingKind } from "./heuristic.ts";
6
6
  export { parseArgs, helpText, UsageError } from "./parse-args.ts";
7
7
  export { assembleDiff, parseUnifiedDiff } from "./parse-diff.ts";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { proposeWithAi } from "./ai.js";
2
2
  export { formatCommit, parseCommit, normalizeSubject } from "./format.js";
3
3
  export { inspectStaged, commitWithMessage, GitError } from "./git.js";
4
- export { proposeHeuristic, inferType, inferScope, inferSubject, inferBreaking, detectBreakingKind, formatBreakingReason, breakingFooterDetail, ensureBreakingFooter, classifyPath, } from "./heuristic.js";
4
+ export { proposeHeuristic, inferType, inferScope, inferSubject, inferBreaking, detectBreakingKind, formatBreakingReason, breakingFooterDetail, DEFAULT_BREAKING_FOOTER_DETAIL, ensureBreakingFooter, classifyPath, } from "./heuristic.js";
5
5
  export { parseArgs, helpText, UsageError } from "./parse-args.js";
6
6
  export { assembleDiff, parseUnifiedDiff } from "./parse-diff.js";
7
7
  export { installHook, uninstallHook, runPrepareCommitMsg, shouldSkipPrepareCommitMsg, hasNonCommentContent, HOOK_BEGIN, HOOK_END, } from "./hook.js";
package/dist/propose.js CHANGED
@@ -30,6 +30,9 @@ export async function proposeMessage(diff, options, env = process.env) {
30
30
  : breaking
31
31
  ? heuristic.breakingReason ?? message.breakingReason
32
32
  : undefined;
33
- return ensureBreakingFooter({ ...message, breaking, breakingReason }, breakingFooterDetail(diff));
33
+ const withMeta = { ...message, breaking, breakingReason };
34
+ // Forced --breaking: footer uses subject, not bilingual default.
35
+ const footerReason = kind === "forced" ? withMeta.subject : breakingFooterDetail(diff);
36
+ return ensureBreakingFooter(withMeta, footerReason);
34
37
  }
35
38
  //# sourceMappingURL=propose.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webywz/vibe-commit",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Stylish conventional commit messages with personality. Offline-first, AI-optional.",
5
5
  "keywords": [
6
6
  "git",