@webywz/vibe-commit 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +156 -2
- package/dist/ai.d.ts +9 -0
- package/dist/ai.js +83 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +95 -0
- package/dist/format.d.ts +10 -0
- package/dist/format.js +72 -0
- package/dist/git.d.ts +7 -0
- package/dist/git.js +55 -0
- package/dist/heuristic.d.ts +15 -0
- package/dist/heuristic.js +336 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/parse-args.d.ts +10 -0
- package/dist/parse-args.js +129 -0
- package/dist/parse-diff.d.ts +5 -0
- package/dist/parse-diff.js +113 -0
- package/dist/propose.d.ts +2 -0
- package/dist/propose.js +23 -0
- package/dist/render.d.ts +7 -0
- package/dist/render.js +59 -0
- package/dist/types.d.ts +48 -0
- package/dist/types.js +14 -0
- package/dist/vibe.d.ts +10 -0
- package/dist/vibe.js +116 -0
- package/package.json +61 -4
package/dist/render.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { formatCommit } from "./format.js";
|
|
3
|
+
export function renderProposal(diff, message, hints = {}) {
|
|
4
|
+
const formatted = formatCommit(message);
|
|
5
|
+
if (hints.piped)
|
|
6
|
+
return formatted;
|
|
7
|
+
const totals = diff.files.reduce((acc, file) => {
|
|
8
|
+
acc.add += file.additions;
|
|
9
|
+
acc.del += file.deletions;
|
|
10
|
+
return acc;
|
|
11
|
+
}, { add: 0, del: 0 });
|
|
12
|
+
const fileLabel = diff.files.length === 1 ? "file" : "files";
|
|
13
|
+
const engine = message.engine === "ai" ? "openai" : "heuristic";
|
|
14
|
+
const fileLines = diff.files.slice(0, 10).map((file) => {
|
|
15
|
+
const stats = `+${file.additions} −${file.deletions}`;
|
|
16
|
+
return ` ${pc.dim(file.status)} ${file.path} ${pc.dim(stats)}`;
|
|
17
|
+
});
|
|
18
|
+
if (diff.files.length > 10) {
|
|
19
|
+
fileLines.push(pc.dim(` … ${diff.files.length - 10} more`));
|
|
20
|
+
}
|
|
21
|
+
const lines = [
|
|
22
|
+
"",
|
|
23
|
+
` ${pc.bold("vibe-commit")} ${pc.dim("·")} ${pc.cyan(engine)} ${pc.dim("·")} ${message.type}`,
|
|
24
|
+
"",
|
|
25
|
+
` ${pc.bold(String(diff.files.length))} ${fileLabel} ${pc.dim("·")} ${pc.green(`+${totals.add}`)} ${pc.red(`−${totals.del}`)}`,
|
|
26
|
+
"",
|
|
27
|
+
...fileLines,
|
|
28
|
+
"",
|
|
29
|
+
` ${pc.dim("proposed")}`,
|
|
30
|
+
"",
|
|
31
|
+
...indent(formatted, pc),
|
|
32
|
+
"",
|
|
33
|
+
];
|
|
34
|
+
if (hints.committed) {
|
|
35
|
+
lines.push(` ${pc.green("committed")}`);
|
|
36
|
+
lines.push("");
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
lines.push(` ${pc.dim("next")} vibe-commit --commit`);
|
|
40
|
+
lines.push("");
|
|
41
|
+
}
|
|
42
|
+
return lines.join("\n");
|
|
43
|
+
}
|
|
44
|
+
function indent(formatted, colors) {
|
|
45
|
+
return formatted.split("\n").map((line, index) => {
|
|
46
|
+
if (!line)
|
|
47
|
+
return "";
|
|
48
|
+
if (index === 0)
|
|
49
|
+
return ` ${colors.bold(line)}`;
|
|
50
|
+
return ` ${colors.dim(line)}`;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export function renderError(message) {
|
|
54
|
+
return `${pc.red("vibe-commit")} ${message}`;
|
|
55
|
+
}
|
|
56
|
+
export function renderVersion(version) {
|
|
57
|
+
return `vibe-commit ${version}`;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=render.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export declare const VIBES: readonly ["playful", "pro", "terse"];
|
|
2
|
+
export type Vibe = (typeof VIBES)[number];
|
|
3
|
+
export declare const COMMIT_TYPES: readonly ["feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore"];
|
|
4
|
+
export type CommitType = (typeof COMMIT_TYPES)[number];
|
|
5
|
+
export type FileStatus = "A" | "M" | "D" | "R" | "C" | "T" | "U";
|
|
6
|
+
export interface StagedFile {
|
|
7
|
+
path: string;
|
|
8
|
+
fromPath?: string;
|
|
9
|
+
status: FileStatus;
|
|
10
|
+
additions: number;
|
|
11
|
+
deletions: number;
|
|
12
|
+
}
|
|
13
|
+
export interface DiffHunk {
|
|
14
|
+
path: string;
|
|
15
|
+
header: string;
|
|
16
|
+
lines: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface StagedDiff {
|
|
19
|
+
files: StagedFile[];
|
|
20
|
+
hunks: DiffHunk[];
|
|
21
|
+
raw: string;
|
|
22
|
+
}
|
|
23
|
+
export type Engine = "heuristic" | "ai";
|
|
24
|
+
export interface ProposedMessage {
|
|
25
|
+
type: CommitType;
|
|
26
|
+
scope?: string;
|
|
27
|
+
subject: string;
|
|
28
|
+
body?: string;
|
|
29
|
+
breaking: boolean;
|
|
30
|
+
engine: Engine;
|
|
31
|
+
}
|
|
32
|
+
export interface ProposeOptions {
|
|
33
|
+
type?: CommitType;
|
|
34
|
+
scope?: string;
|
|
35
|
+
vibe: Vibe;
|
|
36
|
+
preferAi?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface CliOptions {
|
|
39
|
+
commit: boolean;
|
|
40
|
+
dryRun: boolean;
|
|
41
|
+
type?: CommitType;
|
|
42
|
+
scope?: string;
|
|
43
|
+
vibe: Vibe;
|
|
44
|
+
noAi: boolean;
|
|
45
|
+
json: boolean;
|
|
46
|
+
help: boolean;
|
|
47
|
+
version: boolean;
|
|
48
|
+
}
|
package/dist/types.js
ADDED
package/dist/vibe.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CommitType, Vibe } from "./types.ts";
|
|
2
|
+
export interface VibeDraft {
|
|
3
|
+
type: CommitType;
|
|
4
|
+
scope?: string;
|
|
5
|
+
subject: string;
|
|
6
|
+
body?: string;
|
|
7
|
+
breaking: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function applyVibe(draft: VibeDraft, vibe: Vibe): VibeDraft;
|
|
10
|
+
export declare function pick(seed: string, options: string[]): string;
|
package/dist/vibe.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { clip } from "./format.js";
|
|
2
|
+
export function applyVibe(draft, vibe) {
|
|
3
|
+
const subject = stylizeSubject(draft, vibe);
|
|
4
|
+
const body = stylizeBody(draft, vibe);
|
|
5
|
+
return { ...draft, subject, body };
|
|
6
|
+
}
|
|
7
|
+
function stylizeSubject(draft, vibe) {
|
|
8
|
+
const core = draft.subject.trim();
|
|
9
|
+
if (vibe === "terse")
|
|
10
|
+
return clip(stripFiller(core), 50);
|
|
11
|
+
if (vibe === "pro")
|
|
12
|
+
return clip(core, 72);
|
|
13
|
+
return clip(playfulSubject(draft.type, core), 72);
|
|
14
|
+
}
|
|
15
|
+
function stylizeBody(draft, vibe) {
|
|
16
|
+
if (vibe === "terse")
|
|
17
|
+
return undefined;
|
|
18
|
+
const body = draft.body?.trim();
|
|
19
|
+
if (!body)
|
|
20
|
+
return undefined;
|
|
21
|
+
if (vibe === "pro")
|
|
22
|
+
return body;
|
|
23
|
+
const extra = playfulBodyNote(draft.type);
|
|
24
|
+
return extra && !body.includes(extra) ? `${body}\n\n${extra}` : body;
|
|
25
|
+
}
|
|
26
|
+
function stripFiller(text) {
|
|
27
|
+
return text
|
|
28
|
+
.replace(/\b(the|a|an|that|which|with|from|into)\b/gi, " ")
|
|
29
|
+
.replace(/\s+/g, " ")
|
|
30
|
+
.trim();
|
|
31
|
+
}
|
|
32
|
+
function playfulSubject(type, core) {
|
|
33
|
+
const seed = `${type}:${core}`;
|
|
34
|
+
const options = playfulOptions(type, core);
|
|
35
|
+
return pick(seed, options);
|
|
36
|
+
}
|
|
37
|
+
function playfulOptions(type, core) {
|
|
38
|
+
switch (type) {
|
|
39
|
+
case "feat":
|
|
40
|
+
return [
|
|
41
|
+
core,
|
|
42
|
+
`ship ${core.replace(/^add\s+/, "")}`,
|
|
43
|
+
`${core} — quietly useful`,
|
|
44
|
+
`give the repo ${core.replace(/^add\s+/, "")}`,
|
|
45
|
+
];
|
|
46
|
+
case "fix":
|
|
47
|
+
return [
|
|
48
|
+
core,
|
|
49
|
+
`stop the ${core.replace(/^fix\s+/, "")} weirdness`,
|
|
50
|
+
`patch ${core.replace(/^fix\s+/, "")} before it spreads`,
|
|
51
|
+
`${core} (the unglamorous kind)`,
|
|
52
|
+
];
|
|
53
|
+
case "docs":
|
|
54
|
+
return [
|
|
55
|
+
core,
|
|
56
|
+
`tell humans about ${core.replace(/^update\s+/, "").replace(/^document\s+/, "")}`,
|
|
57
|
+
`${core} so future-us remembers`,
|
|
58
|
+
];
|
|
59
|
+
case "refactor":
|
|
60
|
+
return [
|
|
61
|
+
core,
|
|
62
|
+
`tidy ${core.replace(/^refactor\s+/, "")} without changing the plot`,
|
|
63
|
+
`${core} — same behavior, less fog`,
|
|
64
|
+
];
|
|
65
|
+
case "test":
|
|
66
|
+
return [
|
|
67
|
+
core,
|
|
68
|
+
`make ${core.replace(/^add\s+/, "").replace(/^test\s+/, "")} prove itself`,
|
|
69
|
+
`${core} so regressions can't hide`,
|
|
70
|
+
];
|
|
71
|
+
case "chore":
|
|
72
|
+
return [
|
|
73
|
+
core,
|
|
74
|
+
`keep ${core.replace(/^update\s+/, "")} in fighting shape`,
|
|
75
|
+
`${core} (housekeeping)`,
|
|
76
|
+
];
|
|
77
|
+
case "ci":
|
|
78
|
+
return [
|
|
79
|
+
core,
|
|
80
|
+
`keep CI honest: ${core.replace(/^update\s+/, "")}`,
|
|
81
|
+
`${core} so green means green`,
|
|
82
|
+
];
|
|
83
|
+
case "perf":
|
|
84
|
+
return [
|
|
85
|
+
core,
|
|
86
|
+
`make ${core.replace(/^improve\s+/, "")} snappier`,
|
|
87
|
+
`${core} — fewer wasted cycles`,
|
|
88
|
+
];
|
|
89
|
+
default:
|
|
90
|
+
return [core];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function playfulBodyNote(type) {
|
|
94
|
+
switch (type) {
|
|
95
|
+
case "feat":
|
|
96
|
+
return "Shipped with a straight face; the fun is in the details.";
|
|
97
|
+
case "fix":
|
|
98
|
+
return "Less mystery, more green tests.";
|
|
99
|
+
case "docs":
|
|
100
|
+
return "Written for the next person who is, statistically, you.";
|
|
101
|
+
default:
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function pick(seed, options) {
|
|
106
|
+
if (options.length === 0)
|
|
107
|
+
return "";
|
|
108
|
+
let hash = 2166136261;
|
|
109
|
+
for (const char of seed) {
|
|
110
|
+
hash ^= char.charCodeAt(0);
|
|
111
|
+
hash = Math.imul(hash, 16777619);
|
|
112
|
+
}
|
|
113
|
+
const index = Math.abs(hash) % options.length;
|
|
114
|
+
return options[index] ?? options[0] ?? "";
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=vibe.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webywz/vibe-commit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Stylish conventional commit messages with personality. Offline-first, AI-optional.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"git",
|
|
7
|
+
"commit",
|
|
8
|
+
"conventional-commits",
|
|
9
|
+
"cli",
|
|
10
|
+
"commit-message",
|
|
11
|
+
"ai",
|
|
12
|
+
"offline"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/webywz/vibe-commit#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/webywz/vibe-commit/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/webywz/vibe-commit.git"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "webywz",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"bin": {
|
|
26
|
+
"vibe-commit": "dist/cli.js"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"main": "./dist/index.js",
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"!dist/**/*.map",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.18"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc && node -e \"require('fs').chmodSync('dist/cli.js', 0o755)\"",
|
|
50
|
+
"clean": "rm -rf dist",
|
|
51
|
+
"prepublishOnly": "npm run build",
|
|
52
|
+
"test": "tsx --test test/*.test.ts",
|
|
53
|
+
"typecheck": "tsc --noEmit"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"picocolors": "^1.1.1"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^24.9.1",
|
|
60
|
+
"tsx": "^4.20.6",
|
|
61
|
+
"typescript": "^5.9.3"
|
|
62
|
+
}
|
|
63
|
+
}
|