@webywz/vibe-commit 0.1.13 → 0.1.14

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
@@ -17,7 +17,7 @@
17
17
  </p>
18
18
 
19
19
  Companion spirit to [diff-vibe](https://github.com/webywz/diff-vibe): commits here, pull requests there.
20
- Pair walkthrough (EN/中文) and a copy-pasteable helper: [`examples/pair-workflow.md`](./examples/pair-workflow.md) · [`examples/commit-msg.sh`](./examples/commit-msg.sh) (ship in the npm tarball).
20
+ Pair walkthrough (EN/中文), helper script, and CI sample: [`examples/pair-workflow.md`](./examples/pair-workflow.md) · [`examples/commit-msg.sh`](./examples/commit-msg.sh) · [`examples/github-actions.yml`](./examples/github-actions.yml) (ship in the npm tarball).
21
21
 
22
22
  与 [diff-vibe](https://github.com/webywz/diff-vibe) 成对:这边写 commit,那边写 PR。
23
23
  成对流程与可复制脚本见 [`examples/`](./examples/)(随 npm 包发布)。
@@ -45,6 +45,7 @@ vibe-commit # propose from staged diff
45
45
  vibe-commit --vibe playful --commit # write the commit
46
46
  vibe-commit --breaking --dry-run # force ! + BREAKING CHANGE footer
47
47
  vibe-commit --dry-run # print only (default; wins over --commit)
48
+ vibe-commit --github --quiet --no-ai # Actions annotations (CI)
48
49
  git commit -m "$(vibe-commit --dry-run)"
49
50
  vibe-commit install # optional prepare-commit-msg hook
50
51
  ```
@@ -59,6 +60,9 @@ vibe-commit install # optional prepare-commit-msg hook
59
60
  | `--no-ai` | Skip the API even if `OPENAI_API_KEY` is set · 即使有 Key 也不走 AI |
60
61
  | `--breaking` | Force breaking (`!` + `BREAKING CHANGE:` footer = **subject**) · 强制破坏性变更(`!` + footer 文本即 **subject**) |
61
62
  | `--json` | Machine-readable proposal · 机器可读提案 |
63
+ | `--github` | Emit GitHub Actions `::notice` / `::warning` / `::error` annotations; write `GITHUB_OUTPUT` + `GITHUB_STEP_SUMMARY` when set · 输出 Actions 注解,并在环境变量存在时写 Job 输出与 Summary |
64
+ | `--quiet` | Suppress the pretty terminal report; `--json` / `--github` still emit · 抑制漂亮终端报告 |
65
+ | `--max-annotations <n>` | Cap `--github` annotation lines (default: 20; invalid → UsageError) · 限制注解条数 |
62
66
  | `install [--path]` | Install the prepare-commit-msg hook · 安装钩子 |
63
67
  | `uninstall [--path]` | Remove the managed hook · 卸载钩子 |
64
68
  | `-h` / `-v` | Help / version · 帮助 / 版本 |
@@ -75,7 +79,7 @@ npx --yes @webywz/vibe-commit --commit
75
79
  npx --yes @webywz/diff-vibe@^0.1.8 --base main --create
76
80
  ```
77
81
 
78
- Commits here · PRs there. Full walkthrough (EN/中文) and `commit-msg.sh` live in [`examples/pair-workflow.md`](./examples/pair-workflow.md) and [`examples/commit-msg.sh`](./examples/commit-msg.sh). Companion-side snippets: [diff-vibe examples](https://github.com/webywz/diff-vibe/tree/main/examples).
82
+ Commits here · PRs there. Full walkthrough (EN/中文), `commit-msg.sh`, and Actions sample live in [`examples/`](./examples/). Companion-side snippets: [diff-vibe examples](https://github.com/webywz/diff-vibe/tree/main/examples).
79
83
 
80
84
  这边写 commit,那边写 PR。完整成对流程与脚本见 [`examples/`](./examples/)。成对仓库示例:[diff-vibe examples](https://github.com/webywz/diff-vibe/tree/main/examples)。
81
85
 
@@ -164,6 +168,8 @@ console.log(formatCommit(message));
164
168
  **破坏性变更**:删公开 `export`、主版本升级、diff 里已有 footer,或传 `--breaking`。强制 `--breaking` 时 footer 用 **subject**;启发式仍可用更具体的双语原因。干跑说明原因;`--json` 可带 `breakingReason`。
165
169
  5. Managed `prepare-commit-msg` hook via `install` / `uninstall`.
166
170
  通过 `install` / `uninstall` 管理 `prepare-commit-msg` 钩子。
171
+ 6. **GitHub Actions** mode: `--github` emits Checks annotations; optional `--quiet` / `--max-annotations`; writes job outputs + step summary when `GITHUB_OUTPUT` / `GITHUB_STEP_SUMMARY` are set. See [`examples/github-actions.yml`](./examples/github-actions.yml).
172
+ **GitHub Actions** 模式:`--github` 输出 Checks 注解;可选 `--quiet` / `--max-annotations`;环境变量存在时写 Job 输出与 Summary。示例见 [`examples/github-actions.yml`](./examples/github-actions.yml)。
167
173
 
168
174
  ---
169
175
 
@@ -173,6 +179,7 @@ console.log(formatCommit(message));
173
179
  - Source · [github.com/webywz/vibe-commit](https://github.com/webywz/vibe-commit)
174
180
  - Related · [diff-vibe](https://github.com/webywz/diff-vibe) · [prompt-diff](https://github.com/webywz/prompt-diff)
175
181
  - Pair workflow · [examples/pair-workflow.md](./examples/pair-workflow.md)
182
+ - GitHub Actions · [examples/github-actions.yml](./examples/github-actions.yml)
176
183
  - License · [MIT](./LICENSE)
177
184
 
178
185
  ```bash
package/dist/cli.js CHANGED
@@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url";
5
5
  import pc from "picocolors";
6
6
  import { formatCommit } from "./format.js";
7
7
  import { commitWithMessage, GitError, inspectStaged } from "./git.js";
8
+ import { appendGitHubJobOutput, appendGitHubStepSummary, formatGitHubAnnotations, } from "./github.js";
8
9
  import { installHook, uninstallHook, runPrepareCommitMsg } from "./hook.js";
9
10
  import { helpText, parseArgs, UsageError } from "./parse-args.js";
10
11
  import { proposeMessage } from "./propose.js";
@@ -50,18 +51,48 @@ export async function main(argv = process.argv.slice(2)) {
50
51
  });
51
52
  const formatted = formatCommit(message);
52
53
  const shouldCommit = options.commit && !options.dryRun;
54
+ if (options.github) {
55
+ const ann = formatGitHubAnnotations(message, diff, {
56
+ maxAnnotations: options.maxAnnotations,
57
+ });
58
+ // Keep --json stdout machine-parseable; annotations ride on stderr.
59
+ if (options.json)
60
+ process.stderr.write(ann);
61
+ else
62
+ process.stdout.write(ann);
63
+ try {
64
+ appendGitHubJobOutput(message, diff);
65
+ }
66
+ catch (err) {
67
+ process.stderr.write(`vibe-commit: GITHUB_OUTPUT skipped (${err instanceof Error ? err.message : err})\n`);
68
+ }
69
+ try {
70
+ appendGitHubStepSummary(message, diff);
71
+ }
72
+ catch (err) {
73
+ process.stderr.write(`vibe-commit: GITHUB_STEP_SUMMARY skipped (${err instanceof Error ? err.message : err})\n`);
74
+ }
75
+ }
53
76
  if (options.json) {
54
77
  process.stdout.write(`${JSON.stringify({ ...message, formatted }, null, 2)}\n`);
55
78
  }
56
- else if (piped) {
57
- process.stdout.write(`${formatted}\n`);
58
- }
59
- else {
60
- process.stdout.write(renderProposal(diff, message));
79
+ else if (!options.quiet) {
80
+ if (options.github) {
81
+ process.stdout.write("::group::vibe-commit report\n");
82
+ }
83
+ if (piped) {
84
+ process.stdout.write(`${formatted}\n`);
85
+ }
86
+ else {
87
+ process.stdout.write(renderProposal(diff, message));
88
+ }
89
+ if (options.github) {
90
+ process.stdout.write("::endgroup::\n");
91
+ }
61
92
  }
62
93
  if (shouldCommit) {
63
94
  commitWithMessage(formatted);
64
- if (!piped && !options.json) {
95
+ if (!piped && !options.json && !options.quiet) {
65
96
  process.stdout.write(` ${pc.green("committed")}\n\n`);
66
97
  }
67
98
  }
@@ -0,0 +1,32 @@
1
+ import type { ProposedMessage, StagedDiff } from "./types.ts";
2
+ export interface GitHubFormatOptions {
3
+ /** Soft cap for PR annotation noise. Default 20. */
4
+ maxAnnotations?: number;
5
+ }
6
+ type AnnotationType = "error" | "warning" | "notice";
7
+ /** Escape payload text for workflow command messages. */
8
+ export declare function escapeData(value: string): string;
9
+ /** Escape workflow command property values (file, title, …). */
10
+ export declare function escapeProperty(value: string): string;
11
+ export declare function annotation(type: AnnotationType, message: string, props?: {
12
+ file?: string;
13
+ line?: number;
14
+ title?: string;
15
+ }): string;
16
+ /** Heuristic risk tier → annotation level (breaking / fix weigh heavier). */
17
+ export declare function proposalAnnotationType(proposal: ProposedMessage): AnnotationType;
18
+ /**
19
+ * Emit GitHub Actions workflow commands so PR Checks show the proposed commit.
20
+ * Offline / local-safe: pure string formatting, no network.
21
+ */
22
+ export declare function formatGitHubAnnotations(proposal: ProposedMessage, diff: StagedDiff, options?: GitHubFormatOptions): string;
23
+ /** Append job outputs when running inside Actions (`GITHUB_OUTPUT`). */
24
+ export declare function appendGitHubJobOutput(proposal: ProposedMessage, diff: StagedDiff): void;
25
+ /** Build a short Markdown Job Summary for Actions (no env I/O). */
26
+ export declare function formatGitHubStepSummary(proposal: ProposedMessage, diff: StagedDiff): string;
27
+ /**
28
+ * Append a concise Job Summary when running inside Actions (`GITHUB_STEP_SUMMARY`).
29
+ * No-op when the env var is unset.
30
+ */
31
+ export declare function appendGitHubStepSummary(proposal: ProposedMessage, diff: StagedDiff): void;
32
+ export {};
package/dist/github.js ADDED
@@ -0,0 +1,162 @@
1
+ import { appendFileSync } from "node:fs";
2
+ import { formatCommit } from "./format.js";
3
+ /** Escape payload text for workflow command messages. */
4
+ export function escapeData(value) {
5
+ return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
6
+ }
7
+ /** Escape workflow command property values (file, title, …). */
8
+ export function escapeProperty(value) {
9
+ return escapeData(value).replace(/:/g, "%3A").replace(/,/g, "%2C");
10
+ }
11
+ export function annotation(type, message, props = {}) {
12
+ const parts = [];
13
+ if (props.file)
14
+ parts.push(`file=${escapeProperty(props.file)}`);
15
+ if (props.line !== undefined && Number.isFinite(props.line)) {
16
+ parts.push(`line=${Math.max(1, Math.trunc(props.line))}`);
17
+ }
18
+ if (props.title)
19
+ parts.push(`title=${escapeProperty(props.title)}`);
20
+ const meta = parts.length > 0 ? ` ${parts.join(",")}` : "";
21
+ return `::${type}${meta}::${escapeData(message)}`;
22
+ }
23
+ /** Heuristic risk tier → annotation level (breaking / fix weigh heavier). */
24
+ export function proposalAnnotationType(proposal) {
25
+ if (proposal.breaking)
26
+ return "error";
27
+ switch (proposal.type) {
28
+ case "fix":
29
+ return "warning";
30
+ case "feat":
31
+ case "perf":
32
+ case "refactor":
33
+ return "notice";
34
+ default:
35
+ return "notice";
36
+ }
37
+ }
38
+ function oneLine(value, max = 200) {
39
+ const flat = value.replace(/\s+/g, " ").trim();
40
+ if (flat.length <= max)
41
+ return flat;
42
+ return `${flat.slice(0, max - 1)}…`;
43
+ }
44
+ function headerLine(proposal) {
45
+ return formatCommit({ ...proposal, body: undefined }).split("\n")[0] ?? "";
46
+ }
47
+ /**
48
+ * Emit GitHub Actions workflow commands so PR Checks show the proposed commit.
49
+ * Offline / local-safe: pure string formatting, no network.
50
+ */
51
+ export function formatGitHubAnnotations(proposal, diff, options = {}) {
52
+ const max = options.maxAnnotations ?? 20;
53
+ const lines = [];
54
+ const push = (line) => {
55
+ if (lines.length < max)
56
+ lines.push(line);
57
+ };
58
+ const primary = diff.files[0]?.path;
59
+ const title = headerLine(proposal);
60
+ push(annotation(proposalAnnotationType(proposal), title, {
61
+ file: primary,
62
+ title: "vibe-commit · title",
63
+ }));
64
+ if (proposal.breaking) {
65
+ const reason = proposal.breakingReason?.trim() ||
66
+ "Breaking change detected in staged diff.";
67
+ push(annotation("error", oneLine(reason), {
68
+ file: primary,
69
+ title: "vibe-commit · breaking",
70
+ }));
71
+ }
72
+ if (proposal.body?.trim()) {
73
+ push(annotation("notice", oneLine(proposal.body), {
74
+ file: primary,
75
+ title: "vibe-commit · body",
76
+ }));
77
+ }
78
+ for (const file of diff.files) {
79
+ const status = file.status === "A"
80
+ ? "add"
81
+ : file.status === "D"
82
+ ? "delete"
83
+ : file.status === "R"
84
+ ? "rename"
85
+ : "update";
86
+ push(annotation("notice", `${status} \`${file.path}\` (+${file.additions} −${file.deletions})`, {
87
+ file: file.path,
88
+ title: "vibe-commit · file",
89
+ }));
90
+ }
91
+ const totals = diff.files.reduce((acc, file) => {
92
+ acc.add += file.additions;
93
+ acc.del += file.deletions;
94
+ return acc;
95
+ }, { add: 0, del: 0 });
96
+ push(annotation("notice", `${diff.files.length} files · +${totals.add} / −${totals.del} · engine ${proposal.engine}`, { title: "vibe-commit · meta" }));
97
+ return lines.length > 0 ? `${lines.join("\n")}\n` : "";
98
+ }
99
+ /** Append job outputs when running inside Actions (`GITHUB_OUTPUT`). */
100
+ export function appendGitHubJobOutput(proposal, diff) {
101
+ const path = process.env.GITHUB_OUTPUT;
102
+ if (!path)
103
+ return;
104
+ const body = [
105
+ `title=${oneLine(headerLine(proposal), 256)}`,
106
+ `subject=${oneLine(proposal.subject, 200)}`,
107
+ `type=${proposal.type}`,
108
+ `scope=${proposal.scope ?? ""}`,
109
+ `breaking=${proposal.breaking}`,
110
+ `engine=${proposal.engine}`,
111
+ `findings=${diff.files.length}`,
112
+ ].join("\n");
113
+ appendFileSync(path, `${body}\n`, "utf8");
114
+ }
115
+ /** Build a short Markdown Job Summary for Actions (no env I/O). */
116
+ export function formatGitHubStepSummary(proposal, diff) {
117
+ const totals = diff.files.reduce((acc, file) => {
118
+ acc.add += file.additions;
119
+ acc.del += file.deletions;
120
+ return acc;
121
+ }, { add: 0, del: 0 });
122
+ const lines = [
123
+ "## vibe-commit",
124
+ "",
125
+ `| | |`,
126
+ `| --- | --- |`,
127
+ `| **Title** | ${headerLine(proposal)} |`,
128
+ `| **Type** | \`${proposal.type}\`${proposal.scope ? ` (\`${proposal.scope}\`)` : ""} |`,
129
+ `| **Breaking** | ${proposal.breaking} |`,
130
+ `| **Engine** | ${proposal.engine} |`,
131
+ `| **Files** | ${diff.files.length} (+${totals.add} / −${totals.del}) staged |`,
132
+ "",
133
+ ];
134
+ if (proposal.breakingReason?.trim()) {
135
+ lines.push(`**Breaking:** _${oneLine(proposal.breakingReason, 280)}_`, "");
136
+ }
137
+ else if (proposal.body?.trim()) {
138
+ lines.push(`_${oneLine(proposal.body, 280)}_`, "");
139
+ }
140
+ const findings = [];
141
+ for (const file of diff.files.slice(0, 8)) {
142
+ findings.push(`- \`${file.path}\` (${file.status}, +${file.additions} −${file.deletions})`);
143
+ }
144
+ if (findings.length > 0) {
145
+ lines.push("### Staged files", "", ...findings, "");
146
+ }
147
+ else {
148
+ lines.push("_No staged files._", "");
149
+ }
150
+ return lines.join("\n");
151
+ }
152
+ /**
153
+ * Append a concise Job Summary when running inside Actions (`GITHUB_STEP_SUMMARY`).
154
+ * No-op when the env var is unset.
155
+ */
156
+ export function appendGitHubStepSummary(proposal, diff) {
157
+ const path = process.env.GITHUB_STEP_SUMMARY;
158
+ if (!path)
159
+ return;
160
+ appendFileSync(path, `${formatGitHubStepSummary(proposal, diff)}\n`, "utf8");
161
+ }
162
+ //# sourceMappingURL=github.js.map
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { inspectStaged, commitWithMessage, GitError } from "./git.ts";
4
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
+ export { annotation, appendGitHubJobOutput, appendGitHubStepSummary, escapeData, escapeProperty, formatGitHubAnnotations, formatGitHubStepSummary, proposalAnnotationType, } from "./github.ts";
7
8
  export { assembleDiff, parseUnifiedDiff } from "./parse-diff.ts";
8
9
  export { installHook, uninstallHook, runPrepareCommitMsg, shouldSkipPrepareCommitMsg, hasNonCommentContent, HOOK_BEGIN, HOOK_END, } from "./hook.ts";
9
10
  export { proposeMessage } from "./propose.ts";
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export { formatCommit, parseCommit, normalizeSubject } from "./format.js";
3
3
  export { inspectStaged, commitWithMessage, GitError } from "./git.js";
4
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
+ export { annotation, appendGitHubJobOutput, appendGitHubStepSummary, escapeData, escapeProperty, formatGitHubAnnotations, formatGitHubStepSummary, proposalAnnotationType, } from "./github.js";
6
7
  export { assembleDiff, parseUnifiedDiff } from "./parse-diff.js";
7
8
  export { installHook, uninstallHook, runPrepareCommitMsg, shouldSkipPrepareCommitMsg, hasNonCommentContent, HOOK_BEGIN, HOOK_END, } from "./hook.js";
8
9
  export { proposeMessage } from "./propose.js";
@@ -1,6 +1,7 @@
1
1
  import { type CliOptions, type CommitType, type Vibe } from "./types.ts";
2
2
  export declare function defaultVibeFromEnv(env?: NodeJS.ProcessEnv): Vibe;
3
3
  export declare function parseArgs(argv: string[], env?: NodeJS.ProcessEnv): CliOptions;
4
+ export declare function parseMaxAnnotations(value: string | undefined): number;
4
5
  export declare function parseType(value: string | undefined): CommitType;
5
6
  export declare function parseVibe(value: string | undefined): Vibe;
6
7
  export declare function sanitizeScope(value: string): string;
@@ -1,16 +1,17 @@
1
1
  import { COMMIT_TYPES, VIBES, } from "./types.js";
2
2
  const HELP = `vibe-commit — stylish conventional commits, offline-first
3
+ 有品味的 Conventional Commits(离线优先)
3
4
 
4
- Usage
5
+ Usage · 用法
5
6
  vibe-commit [options]
6
7
  vibe-commit install [--path <repo>]
7
8
  vibe-commit uninstall [--path <repo>]
8
9
 
9
- Commands
10
+ Commands · 命令
10
11
  install Install a prepare-commit-msg hook in the repo
11
12
  uninstall Remove the managed prepare-commit-msg hook
12
13
 
13
- Options
14
+ Options · 参数
14
15
  --commit Create the commit with the proposed message
15
16
  --dry-run Print the message only (default)
16
17
  --type <type> feat | fix | docs | style | refactor | perf | test | build | ci | chore
@@ -19,6 +20,11 @@ Options
19
20
  --no-ai Skip AI even if OPENAI_API_KEY is set
20
21
  --breaking Force ! + BREAKING CHANGE footer (text = subject)
21
22
  --json Print a machine-readable proposal
23
+ --github Emit GitHub Actions annotations (::notice/warning/error)
24
+ 输出 Actions 注解;有 GITHUB_OUTPUT / GITHUB_STEP_SUMMARY 时写输出与 Job Summary
25
+ --quiet Suppress the pretty terminal report (--json / --github still emit)
26
+ 抑制漂亮终端报告;--json / --github 仍照常
27
+ --max-annotations <n> Cap GitHub annotation lines (default: 20) · 限制注解条数
22
28
  --path <repo> Repo root for install/uninstall (default: cwd)
23
29
  -h, --help Show help
24
30
  -v, --version Show version
@@ -30,16 +36,28 @@ Env
30
36
  VIBE / VIBE_COMMIT_VIBE Default vibe when --vibe is omitted
31
37
  VIBE_COMMIT_HOOK_AI Set to 1 to allow AI inside the git hook (off by default)
32
38
 
39
+ With --github, annotations go to stdout (or stderr when combined with --json) so
40
+ PR Checks can surface the proposed commit title / body / staged files. Job outputs
41
+ title/subject/type/scope/breaking/engine/findings are written when GITHUB_OUTPUT
42
+ is set (findings = staged file count). A short Markdown job summary is appended when
43
+ GITHUB_STEP_SUMMARY is set. --quiet hides the pretty report only.
44
+
45
+ 配合 --github 时注解走 stdout(与 --json 联用则走 stderr,保持 JSON 干净)。
46
+ 有 GITHUB_OUTPUT 时写入 title/subject 等字段;有 GITHUB_STEP_SUMMARY 时追加 Job Summary。
47
+ --quiet 只隐藏漂亮终端报告。
48
+
33
49
  Git hook
34
50
  After \`vibe-commit install\`, plain \`git commit\` (no -m) fills an empty /
35
51
  comment-only message file with a vibe-commit proposal. Skips merge, squash,
36
52
  amend (source=commit), and any message that already has non-comment text.
37
53
 
38
- Examples
54
+ Examples · 示例
39
55
  git add -p
40
56
  vibe-commit
41
57
  vibe-commit --vibe playful --commit
42
58
  vibe-commit --breaking --dry-run
59
+ vibe-commit --github --quiet --no-ai
60
+ vibe-commit --github --json --max-annotations 10
43
61
  git commit -m "$(vibe-commit --dry-run)"
44
62
  vibe-commit install
45
63
  vibe-commit uninstall --path ~/projects/app
@@ -56,6 +74,9 @@ export function parseArgs(argv, env = process.env) {
56
74
  vibe: defaultVibeFromEnv(env),
57
75
  noAi: false,
58
76
  json: false,
77
+ github: false,
78
+ quiet: false,
79
+ maxAnnotations: 20,
59
80
  help: false,
60
81
  version: false,
61
82
  breaking: false,
@@ -96,6 +117,21 @@ export function parseArgs(argv, env = process.env) {
96
117
  options.json = true;
97
118
  continue;
98
119
  }
120
+ if (arg === "--github") {
121
+ options.github = true;
122
+ continue;
123
+ }
124
+ if (arg === "--quiet") {
125
+ options.quiet = true;
126
+ continue;
127
+ }
128
+ if (arg === "--max-annotations" || arg.startsWith("--max-annotations=")) {
129
+ const value = arg.includes("=")
130
+ ? arg.slice("--max-annotations=".length)
131
+ : argv[++i];
132
+ options.maxAnnotations = parseMaxAnnotations(value);
133
+ continue;
134
+ }
99
135
  if (arg === "--breaking") {
100
136
  options.breaking = true;
101
137
  continue;
@@ -160,6 +196,16 @@ function parseCommand(value) {
160
196
  return null;
161
197
  }
162
198
  }
199
+ export function parseMaxAnnotations(value) {
200
+ const raw = value?.trim();
201
+ if (!raw)
202
+ throw new UsageError("Missing value for --max-annotations");
203
+ const n = Number(raw);
204
+ if (!Number.isInteger(n) || n < 1) {
205
+ throw new UsageError("--max-annotations must be a positive integer");
206
+ }
207
+ return n;
208
+ }
163
209
  export function parseType(value) {
164
210
  const raw = value?.trim().toLowerCase();
165
211
  if (!raw)
package/dist/types.d.ts CHANGED
@@ -49,6 +49,12 @@ export interface CliOptions {
49
49
  vibe: Vibe;
50
50
  noAi: boolean;
51
51
  json: boolean;
52
+ /** Emit GitHub Actions annotations / job outputs / step summary. */
53
+ github: boolean;
54
+ /** Suppress the pretty terminal report (--json / --github still emit). */
55
+ quiet: boolean;
56
+ /** Soft cap for --github annotation lines (default 20). */
57
+ maxAnnotations: number;
52
58
  help: boolean;
53
59
  version: boolean;
54
60
  /** Force breaking change marker and footer. */
@@ -0,0 +1,49 @@
1
+ # Example: surface a proposed commit message as Actions annotations + job summary.
2
+ # Copy into your app repo as .github/workflows/vibe-commit.yml
3
+ # (typically on pull_request so Checks show the draft commit).
4
+ #
5
+ # 示例:把拟写的 commit message 做成 Actions 注解 + Job Summary。
6
+ # 复制到业务仓的 .github/workflows/vibe-commit.yml(通常挂在 pull_request 上)。
7
+ #
8
+ # Note: vibe-commit reads *staged* changes. Stage intentionally in CI, or
9
+ # `git add -A` / `git add -u` before the step if you want the working tree.
10
+
11
+ name: Vibe commit
12
+
13
+ on:
14
+ pull_request:
15
+
16
+ jobs:
17
+ vibe-commit:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+
22
+ - uses: actions/setup-node@v4
23
+ with:
24
+ node-version: 20
25
+
26
+ - name: Stage working tree (demo)
27
+ run: git add -A
28
+
29
+ - name: Propose commit (annotations + summary)
30
+ id: vibe
31
+ run: |
32
+ npx --yes @webywz/vibe-commit@^0.1.14 \
33
+ --github \
34
+ --quiet \
35
+ --max-annotations 20 \
36
+ --no-ai
37
+ # --github → ::notice / ::warning / ::error on the PR Checks UI
38
+ # --quiet → suppress the pretty terminal report (annotations / summary / outputs still emit)
39
+ # --max-annotations → soft-cap annotation noise (default 20)
40
+ # When GITHUB_OUTPUT is set, also writes: title, subject, type, scope, breaking, engine, findings
41
+ # findings = staged file count
42
+ # When GITHUB_STEP_SUMMARY is set, appends a short Markdown job summary
43
+
44
+ # Optional: use job outputs from a later step
45
+ # - name: Echo title
46
+ # run: echo "proposed ${{ steps.vibe.outputs.title }} (findings=${{ steps.vibe.outputs.findings }})"
47
+
48
+ # Optional: Job Summary is written automatically when GITHUB_STEP_SUMMARY is set
49
+ # (Actions injects it). No extra step required.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webywz/vibe-commit",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Stylish conventional commit messages with personality. Offline-first, AI-optional.",
5
5
  "keywords": [
6
6
  "git",