@webywz/vibe-commit 0.1.7 → 0.1.9

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 CHANGED
@@ -55,15 +55,15 @@ vibe-commit install # optional prepare-commit-msg hook
55
55
  | `--scope <scope>` | Override inferred scope · 覆盖推断出的 scope |
56
56
  | `--vibe <style>` | `playful` \| `pro` \| `terse` (default: `pro`) |
57
57
  | `--no-ai` | Skip the API even if `OPENAI_API_KEY` is set · 即使有 Key 也不走 AI |
58
- | `--breaking` | Force breaking change (`!` + `BREAKING CHANGE:` footer) · 强制破坏性变更(`!` + footer) |
58
+ | `--breaking` | Force breaking (`!` + `BREAKING CHANGE:` footer = **subject**) · 强制破坏性变更(`!` + footer 文本即 **subject**) |
59
59
  | `--json` | Machine-readable proposal · 机器可读提案 |
60
60
  | `install [--path]` | Install the prepare-commit-msg hook · 安装钩子 |
61
61
  | `uninstall [--path]` | Remove the managed hook · 卸载钩子 |
62
62
  | `-h` / `-v` | Help / version · 帮助 / 版本 |
63
63
 
64
- Offline heuristic also marks breaking when the staged diff removes public `export`s or bumps a `package.json` major version, and always attaches a `BREAKING CHANGE:` footer when `breaking` is true (so `--json` body matches the formatted message). Dry-run prints a one-line cause; `--json` may include `breakingReason`.
64
+ Offline heuristic also marks breaking when the staged diff removes public `export`s or bumps a `package.json` major version, and always attaches a `BREAKING CHANGE:` footer when `breaking` is true (so `--json` body matches the formatted message). Forced `--breaking` puts the **subject** in the footer; heuristic breaking still prefers a more specific bilingual reason when one is available. Dry-run prints a one-line cause; `--json` may include `breakingReason`.
65
65
 
66
- 离线启发式在删掉公开 `export`、或 `package.json` 主版本升级时也会标 breaking;只要 `breaking` 为真就会补上 `BREAKING CHANGE:` footer(`--json` 的 body 与格式化输出一致)。干跑会打一行原因;`--json` 可带 `breakingReason`。
66
+ 离线启发式在删掉公开 `export`、或 `package.json` 主版本升级时也会标 breaking;只要 `breaking` 为真就会补上 `BREAKING CHANGE:` footer(`--json` 的 body 与格式化输出一致)。强制 `--breaking` 时 footer 文本即 **subject**;启发式推断 breaking 时仍可用更具体的双语原因。干跑会打一行原因;`--json` 可带 `breakingReason`。
67
67
 
68
68
  **Vibes** · 语气
69
69
 
@@ -146,8 +146,8 @@ console.log(formatCommit(message));
146
146
  - new files / `export function` → `feat`
147
147
  3. Optional AI draft with automatic heuristic fallback.
148
148
  可选 AI 草稿,失败自动回退启发式。
149
- 4. **Breaking** when the diff removes a public `export`, bumps a `package.json` major, adds a `BREAKING CHANGE:` footer, or you pass `--breaking`. Dry-run shows why; `--json` may include `breakingReason`.
150
- **破坏性变更**:删公开 `export`、主版本升级、diff 里已有 footer,或传 `--breaking`。干跑说明原因;`--json` 可带 `breakingReason`。
149
+ 4. **Breaking** when the diff removes a public `export`, bumps a `package.json` major, already has a `BREAKING CHANGE:` footer, or you pass `--breaking`. Forced `--breaking` puts the **subject** in the footer; heuristic cases keep a more specific bilingual reason when available. Dry-run shows why; `--json` may include `breakingReason`.
150
+ **破坏性变更**:删公开 `export`、主版本升级、diff 里已有 footer,或传 `--breaking`。强制 `--breaking` 时 footer 用 **subject**;启发式仍可用更具体的双语原因。干跑说明原因;`--json` 可带 `breakingReason`。
151
151
  5. Managed `prepare-commit-msg` hook via `install` / `uninstall`.
152
152
  通过 `install` / `uninstall` 管理 `prepare-commit-msg` 钩子。
153
153
 
@@ -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;
@@ -269,9 +271,13 @@ export function breakingFooterDetail(diff) {
269
271
  }
270
272
  return undefined;
271
273
  }
274
+ /** Generic bilingual footer when no better reason (or empty subject) exists. */
275
+ export const DEFAULT_BREAKING_FOOTER_DETAIL = "incompatible API change · 不兼容的 API 变更";
272
276
  /**
273
277
  * Ensure body carries a conventional BREAKING CHANGE footer when breaking.
274
- * 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.
275
281
  */
276
282
  export function ensureBreakingFooter(message, reason) {
277
283
  if (!message.breaking)
@@ -280,8 +286,13 @@ export function ensureBreakingFooter(message, reason) {
280
286
  if (/BREAKING CHANGE:/i.test(body)) {
281
287
  return body === (message.body ?? "") ? message : { ...message, body };
282
288
  }
283
- const detail = (reason ?? message.subject).replace(/\s+/g, " ").trim() ||
284
- "incompatible API change · 不兼容的 API 变更";
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;
285
296
  const footer = `BREAKING CHANGE: ${detail}`;
286
297
  return {
287
298
  ...message,
@@ -298,7 +309,7 @@ function detectBreakingFooter(diff) {
298
309
  }
299
310
  // Presence of the token alone still counts as breaking.
300
311
  if (diff.hunks.some((hunk) => hunk.lines.some((line) => /^\+\s*BREAKING CHANGE:/i.test(line)))) {
301
- return "incompatible API change · 不兼容的 API 变更";
312
+ return DEFAULT_BREAKING_FOOTER_DETAIL;
302
313
  }
303
314
  return undefined;
304
315
  }
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";
@@ -17,7 +17,7 @@ Options
17
17
  --scope <scope> Override inferred scope
18
18
  --vibe <style> playful | pro | terse (default: pro)
19
19
  --no-ai Skip AI even if OPENAI_API_KEY is set
20
- --breaking Force breaking change (! + BREAKING CHANGE footer)
20
+ --breaking Force ! + BREAKING CHANGE footer (text = subject)
21
21
  --json Print a machine-readable proposal
22
22
  --path <repo> Repo root for install/uninstall (default: cwd)
23
23
  -h, --help Show help
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.7",
3
+ "version": "0.1.9",
4
4
  "description": "Stylish conventional commit messages with personality. Offline-first, AI-optional.",
5
5
  "keywords": [
6
6
  "git",