@theoribbi/claude-code-autocommit 1.0.0

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 ADDED
@@ -0,0 +1,157 @@
1
+ # claude-code-autocommit
2
+
3
+ An MCP server that generates [Conventional Commits](https://www.conventionalcommits.org/) messages from git changes with minimal token consumption.
4
+
5
+ ## Installation
6
+
7
+ ### Configuration Files
8
+
9
+ Claude Code looks for MCP server configuration in these locations:
10
+
11
+ | File | Scope |
12
+ |------|-------|
13
+ | `~/.claude/settings.json` | Global (all projects) |
14
+ | `<project>/.claude/settings.json` | Project-specific |
15
+
16
+ ### Using npx (recommended)
17
+
18
+ Add the following to your settings file:
19
+
20
+ **Global** (`~/.claude/settings.json`):
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "autocommit": {
25
+ "command": "npx",
26
+ "args": ["-y", "claude-code-autocommit"]
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ **Or project-specific** (`.claude/settings.json` in your project root):
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "autocommit": {
37
+ "command": "npx",
38
+ "args": ["-y", "claude-code-autocommit"]
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ### Manual installation
45
+
46
+ ```bash
47
+ npm install -g claude-code-autocommit
48
+ ```
49
+
50
+ Then add to your settings file (`~/.claude/settings.json` or `.claude/settings.json`):
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "autocommit": {
56
+ "command": "claude-code-autocommit"
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ > **Note**: After modifying the settings file, restart Claude Code for the changes to take effect.
63
+
64
+ ## Tools
65
+
66
+ ### `analyze_changes`
67
+
68
+ Analyzes git changes and returns a token-efficient summary.
69
+
70
+ **Parameters:**
71
+ - `include_staged` (boolean, default: true) - Include staged changes
72
+ - `include_unstaged` (boolean, default: false) - Include unstaged changes
73
+ - `cwd` (string, optional) - Working directory
74
+
75
+ **Returns:**
76
+ - File paths with status (A/M/D/R) and change counts
77
+ - Total additions/deletions
78
+ - File extensions and top directories
79
+ - Suggested commit type and scope
80
+
81
+ ### `generate_commit_message`
82
+
83
+ Generates a Conventional Commits message based on the changes.
84
+
85
+ **Parameters:**
86
+ - `type` (string, optional) - Override commit type (feat, fix, docs, etc.)
87
+ - `scope` (string, optional) - Override scope
88
+ - `description` (string, optional) - Override description
89
+ - `breaking` (boolean, default: false) - Mark as breaking change
90
+ - `include_staged` (boolean, default: true) - Include staged changes
91
+ - `include_unstaged` (boolean, default: false) - Include unstaged changes
92
+ - `cwd` (string, optional) - Working directory
93
+
94
+ **Returns:**
95
+ - Full commit message
96
+ - Message components (type, scope, description)
97
+ - Confidence level (high/medium/low)
98
+
99
+ ### `execute_commit`
100
+
101
+ Executes a git commit with the provided message.
102
+
103
+ **Parameters:**
104
+ - `message` (string, required) - The commit message
105
+ - `confirmed` (boolean, required) - Must be `true` to execute (safety check)
106
+ - `cwd` (string, optional) - Working directory
107
+
108
+ **Returns:**
109
+ - Success status
110
+ - Commit hash (on success)
111
+ - Error message (on failure)
112
+
113
+ ## Type Detection Heuristics
114
+
115
+ | Pattern | Type |
116
+ |---------|------|
117
+ | `*.test.ts`, `__tests__/` | test |
118
+ | `*.md`, `docs/`, `README` | docs |
119
+ | `.github/workflows/` | ci |
120
+ | `package.json`, `tsconfig.json` | build |
121
+ | `*.css`, `.prettier*` | style |
122
+ | `.gitignore`, `*.lock` | chore |
123
+ | New files in `src/` | feat |
124
+ | Modifications | fix |
125
+
126
+ ## Scope Detection
127
+
128
+ Scopes are extracted from directory structure:
129
+ - `src/auth/login.ts` → scope: `auth`
130
+ - `packages/core/` → scope: `core`
131
+ - Multiple directories → no scope
132
+
133
+ ## Example Usage
134
+
135
+ ```
136
+ 1. Stage your changes: git add .
137
+ 2. Ask Claude: "Generate a commit message for my staged changes"
138
+ 3. Review the suggested message
139
+ 4. Ask Claude: "Commit with that message"
140
+ ```
141
+
142
+ ## Development
143
+
144
+ ```bash
145
+ # Install dependencies
146
+ npm install
147
+
148
+ # Build
149
+ npm run build
150
+
151
+ # Run locally
152
+ node dist/index.js
153
+ ```
154
+
155
+ ## License
156
+
157
+ MIT
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,748 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+
6
+ // src/server.ts
7
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+
9
+ // src/tools/analyze-changes.ts
10
+ import { z } from "zod";
11
+
12
+ // src/git/analyzer.ts
13
+ import { spawn } from "child_process";
14
+
15
+ // src/commit/type-detector.ts
16
+ var TYPE_PATTERNS = [
17
+ {
18
+ type: "test",
19
+ patterns: [
20
+ /\.test\.[jt]sx?$/,
21
+ /\.spec\.[jt]sx?$/,
22
+ /__tests__\//,
23
+ /test\//,
24
+ /tests\//,
25
+ /\.cy\.[jt]sx?$/,
26
+ /cypress\//
27
+ ],
28
+ priority: 10
29
+ },
30
+ {
31
+ type: "docs",
32
+ patterns: [
33
+ /\.md$/i,
34
+ /^docs\//,
35
+ /^documentation\//,
36
+ /^README/i,
37
+ /CHANGELOG/i,
38
+ /LICENSE/i,
39
+ /\.txt$/
40
+ ],
41
+ priority: 9
42
+ },
43
+ {
44
+ type: "ci",
45
+ patterns: [
46
+ /^\.github\/workflows\//,
47
+ /^\.github\/actions\//,
48
+ /^\.circleci\//,
49
+ /^\.travis\.yml$/,
50
+ /^\.gitlab-ci\.yml$/,
51
+ /^Jenkinsfile$/,
52
+ /^\.buildkite\//
53
+ ],
54
+ priority: 8
55
+ },
56
+ {
57
+ type: "build",
58
+ patterns: [
59
+ /^package\.json$/,
60
+ /^package-lock\.json$/,
61
+ /^yarn\.lock$/,
62
+ /^pnpm-lock\.yaml$/,
63
+ /^tsconfig.*\.json$/,
64
+ /^webpack\./,
65
+ /^vite\.config\./,
66
+ /^rollup\.config\./,
67
+ /^esbuild\./,
68
+ /^tsup\.config\./,
69
+ /^Makefile$/,
70
+ /^CMakeLists\.txt$/,
71
+ /^build\./,
72
+ /^Dockerfile$/,
73
+ /^docker-compose/
74
+ ],
75
+ priority: 7
76
+ },
77
+ {
78
+ type: "style",
79
+ patterns: [
80
+ /\.css$/,
81
+ /\.scss$/,
82
+ /\.sass$/,
83
+ /\.less$/,
84
+ /\.styled\.[jt]sx?$/,
85
+ /^\.prettier/,
86
+ /^\.eslint/,
87
+ /^\.stylelint/,
88
+ /^\.editorconfig$/
89
+ ],
90
+ priority: 6
91
+ },
92
+ {
93
+ type: "chore",
94
+ patterns: [
95
+ /^\.gitignore$/,
96
+ /^\.gitattributes$/,
97
+ /^\.npmignore$/,
98
+ /^\.nvmrc$/,
99
+ /^\.node-version$/,
100
+ /^\.env\.example$/,
101
+ /\.lock$/
102
+ ],
103
+ priority: 5
104
+ },
105
+ {
106
+ type: "perf",
107
+ patterns: [/perf\//, /benchmark\//, /\.bench\.[jt]sx?$/],
108
+ priority: 4
109
+ }
110
+ ];
111
+ function matchesPattern(path, patterns) {
112
+ return patterns.some((pattern) => pattern.test(path));
113
+ }
114
+ function detectType(files) {
115
+ if (files.length === 0) return null;
116
+ const typeCounts = /* @__PURE__ */ new Map();
117
+ for (const file of files) {
118
+ for (const { type, patterns } of TYPE_PATTERNS) {
119
+ if (matchesPattern(file.path, patterns)) {
120
+ typeCounts.set(type, (typeCounts.get(type) || 0) + 1);
121
+ break;
122
+ }
123
+ }
124
+ }
125
+ const totalMatchedFiles = Array.from(typeCounts.values()).reduce(
126
+ (a, b) => a + b,
127
+ 0
128
+ );
129
+ if (totalMatchedFiles === files.length && typeCounts.size === 1) {
130
+ return typeCounts.keys().next().value ?? null;
131
+ }
132
+ for (const [type, count] of typeCounts) {
133
+ if (count > files.length / 2) {
134
+ return type;
135
+ }
136
+ }
137
+ const newFiles = files.filter((f) => f.status === "A");
138
+ const modifiedFiles = files.filter((f) => f.status === "M");
139
+ const deletedFiles = files.filter((f) => f.status === "D");
140
+ if (deletedFiles.length === files.length) {
141
+ return "chore";
142
+ }
143
+ const newSrcFiles = newFiles.filter(
144
+ (f) => f.path.startsWith("src/") || f.path.startsWith("lib/") || f.path.startsWith("app/")
145
+ );
146
+ if (newSrcFiles.length > files.length / 2) {
147
+ return "feat";
148
+ }
149
+ if (modifiedFiles.length > files.length / 2) {
150
+ return "fix";
151
+ }
152
+ if (newFiles.length > 0) {
153
+ return "feat";
154
+ }
155
+ return "fix";
156
+ }
157
+
158
+ // src/commit/scope-detector.ts
159
+ function extractScope(path) {
160
+ const parts = path.split("/");
161
+ if (parts[0] === "packages" && parts.length > 2) {
162
+ return parts[1];
163
+ }
164
+ if (parts[0] === "src" && parts.length > 2) {
165
+ return parts[1];
166
+ }
167
+ if (parts[0] === "lib" && parts.length > 2) {
168
+ return parts[1];
169
+ }
170
+ if (parts[0] === "app" && parts.length > 2) {
171
+ return parts[1];
172
+ }
173
+ if (parts[0] === "components" && parts.length > 1) {
174
+ return toKebabCase(parts[1]);
175
+ }
176
+ if ((parts[0] === "pages" || parts[0] === "routes") && parts.length > 1) {
177
+ return parts[1];
178
+ }
179
+ if (parts[0] === "api" && parts.length > 1) {
180
+ return parts[1];
181
+ }
182
+ if (parts[0] === "features" && parts.length > 1) {
183
+ return parts[1];
184
+ }
185
+ if (parts[0] === "modules" && parts.length > 1) {
186
+ return parts[1];
187
+ }
188
+ if (parts.length === 1) {
189
+ const match = path.match(/^\.?([a-zA-Z]+)/);
190
+ if (match) {
191
+ return match[1].toLowerCase();
192
+ }
193
+ }
194
+ return null;
195
+ }
196
+ function toKebabCase(str) {
197
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
198
+ }
199
+ function detectScope(files) {
200
+ if (files.length === 0) return null;
201
+ const scopes = files.map((f) => extractScope(f.path)).filter((s) => s !== null);
202
+ if (scopes.length === 0) return null;
203
+ const scopeCounts = /* @__PURE__ */ new Map();
204
+ for (const scope of scopes) {
205
+ scopeCounts.set(scope, (scopeCounts.get(scope) || 0) + 1);
206
+ }
207
+ if (scopeCounts.size === 1) {
208
+ return scopes[0];
209
+ }
210
+ for (const [scope, count] of scopeCounts) {
211
+ if (count > files.length * 0.7) {
212
+ return scope;
213
+ }
214
+ }
215
+ return null;
216
+ }
217
+
218
+ // src/git/analyzer.ts
219
+ function runGitCommand(args, cwd) {
220
+ return new Promise((resolve, reject) => {
221
+ const proc = spawn("git", args, { cwd });
222
+ let stdout = "";
223
+ let stderr = "";
224
+ proc.stdout.on("data", (data) => {
225
+ stdout += data.toString();
226
+ });
227
+ proc.stderr.on("data", (data) => {
228
+ stderr += data.toString();
229
+ });
230
+ proc.on("close", (code) => {
231
+ if (code === 0) {
232
+ resolve({ stdout, stderr });
233
+ } else {
234
+ reject(new Error(`Git command failed: ${stderr || stdout}`));
235
+ }
236
+ });
237
+ proc.on("error", (err) => {
238
+ reject(err);
239
+ });
240
+ });
241
+ }
242
+ function parseNumstat(output) {
243
+ const stats = /* @__PURE__ */ new Map();
244
+ if (!output.trim()) return stats;
245
+ const lines = output.trim().split("\n");
246
+ for (const line of lines) {
247
+ const parts = line.split(" ");
248
+ if (parts.length >= 3) {
249
+ const [addStr, delStr, ...pathParts] = parts;
250
+ const path = pathParts.join(" ");
251
+ const add = addStr === "-" ? 0 : parseInt(addStr, 10);
252
+ const del = delStr === "-" ? 0 : parseInt(delStr, 10);
253
+ stats.set(path, { add, del });
254
+ }
255
+ }
256
+ return stats;
257
+ }
258
+ function parseNameStatus(output) {
259
+ const statuses = /* @__PURE__ */ new Map();
260
+ if (!output.trim()) return statuses;
261
+ const lines = output.trim().split("\n");
262
+ for (const line of lines) {
263
+ const parts = line.split(" ");
264
+ if (parts.length >= 2) {
265
+ const statusCode = parts[0].charAt(0);
266
+ if (parts[0].startsWith("R") || parts[0].startsWith("C")) {
267
+ const oldPath = parts[1];
268
+ const newPath = parts[2];
269
+ statuses.set(newPath, { status: statusCode, oldPath });
270
+ } else {
271
+ statuses.set(parts[1], { status: statusCode });
272
+ }
273
+ }
274
+ }
275
+ return statuses;
276
+ }
277
+ function getExtension(path) {
278
+ const match = path.match(/\.([^./]+)$/);
279
+ return match ? match[1] : "";
280
+ }
281
+ function getTopDirectory(path) {
282
+ const parts = path.split("/");
283
+ return parts.length > 1 ? parts[0] : "";
284
+ }
285
+ async function analyzeChanges(options = {}) {
286
+ const {
287
+ includeStaged = true,
288
+ includeUnstaged = false,
289
+ cwd = process.cwd()
290
+ } = options;
291
+ const files = [];
292
+ const extensionSet = /* @__PURE__ */ new Set();
293
+ const directorySet = /* @__PURE__ */ new Set();
294
+ const baseArgs = includeStaged && !includeUnstaged ? ["--cached"] : [];
295
+ if (!includeStaged && includeUnstaged) {
296
+ } else if (includeStaged && includeUnstaged) {
297
+ baseArgs.length = 0;
298
+ baseArgs.push("HEAD");
299
+ }
300
+ const [numstatResult, nameStatusResult] = await Promise.all([
301
+ runGitCommand(["diff", ...baseArgs, "--numstat"], cwd),
302
+ runGitCommand(["diff", ...baseArgs, "--name-status"], cwd)
303
+ ]);
304
+ const numstatMap = parseNumstat(numstatResult.stdout);
305
+ const nameStatusMap = parseNameStatus(nameStatusResult.stdout);
306
+ for (const [path, { status, oldPath }] of nameStatusMap) {
307
+ const stats = numstatMap.get(path) || numstatMap.get(oldPath || "") || { add: 0, del: 0 };
308
+ const fileChange = {
309
+ path,
310
+ status,
311
+ additions: stats.add,
312
+ deletions: stats.del
313
+ };
314
+ if (oldPath) {
315
+ fileChange.oldPath = oldPath;
316
+ }
317
+ files.push(fileChange);
318
+ const ext = getExtension(path);
319
+ if (ext) extensionSet.add(ext);
320
+ const dir = getTopDirectory(path);
321
+ if (dir) directorySet.add(dir);
322
+ }
323
+ const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
324
+ const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);
325
+ const suggestedType = detectType(files);
326
+ const suggestedScope = detectScope(files);
327
+ return {
328
+ files,
329
+ totalFiles: files.length,
330
+ totalAdditions,
331
+ totalDeletions,
332
+ extensions: Array.from(extensionSet),
333
+ topDirectories: Array.from(directorySet),
334
+ suggestedType,
335
+ suggestedScope
336
+ };
337
+ }
338
+
339
+ // src/tools/analyze-changes.ts
340
+ var AnalyzeChangesSchema = z.object({
341
+ include_staged: z.boolean().optional().default(true).describe("Include staged changes in analysis"),
342
+ include_unstaged: z.boolean().optional().default(false).describe("Include unstaged changes in analysis"),
343
+ cwd: z.string().optional().describe("Working directory (defaults to current directory)")
344
+ });
345
+ function registerAnalyzeChangesTool(server) {
346
+ server.tool(
347
+ "analyze_changes",
348
+ "Analyze git changes and return a token-efficient summary with file paths, status, additions/deletions, and suggested commit type/scope",
349
+ AnalyzeChangesSchema.shape,
350
+ async (params) => {
351
+ const { include_staged, include_unstaged, cwd } = AnalyzeChangesSchema.parse(params);
352
+ try {
353
+ const summary = await analyzeChanges({
354
+ includeStaged: include_staged,
355
+ includeUnstaged: include_unstaged,
356
+ cwd: cwd || process.cwd()
357
+ });
358
+ const output = {
359
+ files: summary.files.map((f) => ({
360
+ path: f.path,
361
+ status: f.status,
362
+ changes: `+${f.additions}/-${f.deletions}`,
363
+ ...f.oldPath ? { oldPath: f.oldPath } : {}
364
+ })),
365
+ summary: {
366
+ totalFiles: summary.totalFiles,
367
+ totalAdditions: summary.totalAdditions,
368
+ totalDeletions: summary.totalDeletions,
369
+ extensions: summary.extensions,
370
+ directories: summary.topDirectories
371
+ },
372
+ suggested: {
373
+ type: summary.suggestedType,
374
+ scope: summary.suggestedScope
375
+ }
376
+ };
377
+ return {
378
+ content: [
379
+ {
380
+ type: "text",
381
+ text: JSON.stringify(output, null, 2)
382
+ }
383
+ ]
384
+ };
385
+ } catch (error) {
386
+ return {
387
+ content: [
388
+ {
389
+ type: "text",
390
+ text: `Error analyzing changes: ${error instanceof Error ? error.message : "Unknown error"}`
391
+ }
392
+ ],
393
+ isError: true
394
+ };
395
+ }
396
+ }
397
+ );
398
+ }
399
+
400
+ // src/tools/generate-message.ts
401
+ import { z as z2 } from "zod";
402
+
403
+ // src/commit/generator.ts
404
+ function generateDescription(summary) {
405
+ const { files, totalAdditions, totalDeletions } = summary;
406
+ if (files.length === 0) {
407
+ return "no changes";
408
+ }
409
+ if (files.length === 1) {
410
+ const file = files[0];
411
+ const fileName = file.path.split("/").pop() || file.path;
412
+ const action = getActionVerb(file.status);
413
+ return `${action} ${fileName}`;
414
+ }
415
+ const directories = new Set(files.map((f) => f.path.split("/").slice(0, -1).join("/")));
416
+ if (directories.size === 1) {
417
+ const dir = directories.values().next().value;
418
+ const dirName = dir ? dir.split("/").pop() || dir : "root";
419
+ return `update ${files.length} files in ${dirName}`;
420
+ }
421
+ const added = files.filter((f) => f.status === "A").length;
422
+ const modified = files.filter((f) => f.status === "M").length;
423
+ const deleted = files.filter((f) => f.status === "D").length;
424
+ const renamed = files.filter((f) => f.status === "R").length;
425
+ const parts = [];
426
+ if (added > 0) parts.push(`add ${added} file${added > 1 ? "s" : ""}`);
427
+ if (modified > 0) parts.push(`update ${modified} file${modified > 1 ? "s" : ""}`);
428
+ if (deleted > 0) parts.push(`remove ${deleted} file${deleted > 1 ? "s" : ""}`);
429
+ if (renamed > 0) parts.push(`rename ${renamed} file${renamed > 1 ? "s" : ""}`);
430
+ if (parts.length === 0) {
431
+ return `update ${files.length} files`;
432
+ }
433
+ return parts.join(", ");
434
+ }
435
+ function getActionVerb(status) {
436
+ switch (status) {
437
+ case "A":
438
+ return "add";
439
+ case "M":
440
+ return "update";
441
+ case "D":
442
+ return "remove";
443
+ case "R":
444
+ return "rename";
445
+ case "C":
446
+ return "copy";
447
+ default:
448
+ return "update";
449
+ }
450
+ }
451
+ function formatCommitMessage(type, scope, description, breaking) {
452
+ const breakingMark = breaking ? "!" : "";
453
+ const scopePart = scope ? `(${scope})` : "";
454
+ return `${type}${scopePart}${breakingMark}: ${description}`;
455
+ }
456
+ function generateCommitMessage(summary, options = {}) {
457
+ const type = options.type || summary.suggestedType || "chore";
458
+ const scope = options.scope !== void 0 ? options.scope : summary.suggestedScope;
459
+ const description = options.description || generateDescription(summary);
460
+ const breaking = options.breaking || false;
461
+ const full = formatCommitMessage(type, scope, description, breaking);
462
+ let confidence = "medium";
463
+ if (options.type && options.description) {
464
+ confidence = "high";
465
+ } else if (summary.totalFiles === 1) {
466
+ confidence = "high";
467
+ } else if (summary.totalFiles > 10) {
468
+ confidence = "low";
469
+ } else if (summary.suggestedType !== null) {
470
+ confidence = "medium";
471
+ }
472
+ return {
473
+ message: {
474
+ type,
475
+ scope,
476
+ description,
477
+ breaking,
478
+ full
479
+ },
480
+ confidence
481
+ };
482
+ }
483
+
484
+ // src/tools/generate-message.ts
485
+ var CommitTypeEnum = z2.enum([
486
+ "feat",
487
+ "fix",
488
+ "docs",
489
+ "style",
490
+ "refactor",
491
+ "test",
492
+ "build",
493
+ "ci",
494
+ "chore",
495
+ "perf",
496
+ "revert"
497
+ ]);
498
+ var GenerateMessageSchema = z2.object({
499
+ type: CommitTypeEnum.optional().describe(
500
+ "Override the commit type (feat, fix, docs, etc.)"
501
+ ),
502
+ scope: z2.string().nullable().optional().describe("Override the scope (e.g., auth, api, ui)"),
503
+ description: z2.string().optional().describe("Override the description text"),
504
+ breaking: z2.boolean().optional().default(false).describe("Mark as breaking change"),
505
+ include_staged: z2.boolean().optional().default(true).describe("Include staged changes"),
506
+ include_unstaged: z2.boolean().optional().default(false).describe("Include unstaged changes"),
507
+ cwd: z2.string().optional().describe("Working directory (defaults to current directory)")
508
+ });
509
+ function registerGenerateMessageTool(server) {
510
+ server.tool(
511
+ "generate_commit_message",
512
+ "Generate a Conventional Commits message based on git changes. Can auto-detect type and scope, or accept overrides.",
513
+ GenerateMessageSchema.shape,
514
+ async (params) => {
515
+ const {
516
+ type,
517
+ scope,
518
+ description,
519
+ breaking,
520
+ include_staged,
521
+ include_unstaged,
522
+ cwd
523
+ } = GenerateMessageSchema.parse(params);
524
+ try {
525
+ const summary = await analyzeChanges({
526
+ includeStaged: include_staged,
527
+ includeUnstaged: include_unstaged,
528
+ cwd: cwd || process.cwd()
529
+ });
530
+ if (summary.totalFiles === 0) {
531
+ return {
532
+ content: [
533
+ {
534
+ type: "text",
535
+ text: JSON.stringify({
536
+ error: "No changes found to generate commit message",
537
+ suggestion: "Stage some changes first with 'git add'"
538
+ })
539
+ }
540
+ ],
541
+ isError: true
542
+ };
543
+ }
544
+ const result = generateCommitMessage(summary, {
545
+ type,
546
+ scope: scope ?? void 0,
547
+ description,
548
+ breaking
549
+ });
550
+ const output = {
551
+ message: result.message.full,
552
+ components: {
553
+ type: result.message.type,
554
+ scope: result.message.scope,
555
+ description: result.message.description,
556
+ breaking: result.message.breaking
557
+ },
558
+ confidence: result.confidence,
559
+ analysis: {
560
+ totalFiles: summary.totalFiles,
561
+ totalAdditions: summary.totalAdditions,
562
+ totalDeletions: summary.totalDeletions
563
+ }
564
+ };
565
+ return {
566
+ content: [
567
+ {
568
+ type: "text",
569
+ text: JSON.stringify(output, null, 2)
570
+ }
571
+ ]
572
+ };
573
+ } catch (error) {
574
+ return {
575
+ content: [
576
+ {
577
+ type: "text",
578
+ text: `Error generating commit message: ${error instanceof Error ? error.message : "Unknown error"}`
579
+ }
580
+ ],
581
+ isError: true
582
+ };
583
+ }
584
+ }
585
+ );
586
+ }
587
+
588
+ // src/tools/execute-commit.ts
589
+ import { z as z3 } from "zod";
590
+
591
+ // src/git/executor.ts
592
+ import { spawn as spawn2 } from "child_process";
593
+ function runGitCommand2(args, cwd) {
594
+ return new Promise((resolve, reject) => {
595
+ const proc = spawn2("git", args, { cwd });
596
+ let stdout = "";
597
+ let stderr = "";
598
+ proc.stdout.on("data", (data) => {
599
+ stdout += data.toString();
600
+ });
601
+ proc.stderr.on("data", (data) => {
602
+ stderr += data.toString();
603
+ });
604
+ proc.on("close", (code) => {
605
+ resolve({ stdout, stderr, code: code ?? 1 });
606
+ });
607
+ proc.on("error", (err) => {
608
+ reject(err);
609
+ });
610
+ });
611
+ }
612
+ async function executeCommit(message, confirmed, cwd = process.cwd()) {
613
+ if (!confirmed) {
614
+ return {
615
+ success: false,
616
+ error: "Commit not confirmed. Set confirmed=true to execute the commit."
617
+ };
618
+ }
619
+ if (!message || message.trim().length === 0) {
620
+ return {
621
+ success: false,
622
+ error: "Commit message cannot be empty."
623
+ };
624
+ }
625
+ try {
626
+ const statusResult = await runGitCommand2(
627
+ ["diff", "--cached", "--quiet"],
628
+ cwd
629
+ );
630
+ if (statusResult.code === 0) {
631
+ return {
632
+ success: false,
633
+ error: "No staged changes to commit."
634
+ };
635
+ }
636
+ const commitResult = await runGitCommand2(["commit", "-m", message], cwd);
637
+ if (commitResult.code !== 0) {
638
+ return {
639
+ success: false,
640
+ error: commitResult.stderr || commitResult.stdout
641
+ };
642
+ }
643
+ const hashResult = await runGitCommand2(["rev-parse", "HEAD"], cwd);
644
+ const commitHash = hashResult.stdout.trim();
645
+ return {
646
+ success: true,
647
+ commitHash
648
+ };
649
+ } catch (error) {
650
+ return {
651
+ success: false,
652
+ error: error instanceof Error ? error.message : "Unknown error occurred"
653
+ };
654
+ }
655
+ }
656
+
657
+ // src/tools/execute-commit.ts
658
+ var ExecuteCommitSchema = z3.object({
659
+ message: z3.string().min(1).describe("The commit message to use"),
660
+ confirmed: z3.boolean().describe("Must be true to execute the commit (safety check)"),
661
+ cwd: z3.string().optional().describe("Working directory (defaults to current directory)")
662
+ });
663
+ function registerExecuteCommitTool(server) {
664
+ server.tool(
665
+ "execute_commit",
666
+ "Execute a git commit with the provided message. Requires confirmed=true as a safety measure.",
667
+ ExecuteCommitSchema.shape,
668
+ async (params) => {
669
+ const { message, confirmed, cwd } = ExecuteCommitSchema.parse(params);
670
+ try {
671
+ const result = await executeCommit(
672
+ message,
673
+ confirmed,
674
+ cwd || process.cwd()
675
+ );
676
+ if (result.success) {
677
+ return {
678
+ content: [
679
+ {
680
+ type: "text",
681
+ text: JSON.stringify({
682
+ success: true,
683
+ commitHash: result.commitHash,
684
+ message
685
+ })
686
+ }
687
+ ]
688
+ };
689
+ } else {
690
+ return {
691
+ content: [
692
+ {
693
+ type: "text",
694
+ text: JSON.stringify({
695
+ success: false,
696
+ error: result.error
697
+ })
698
+ }
699
+ ],
700
+ isError: true
701
+ };
702
+ }
703
+ } catch (error) {
704
+ return {
705
+ content: [
706
+ {
707
+ type: "text",
708
+ text: `Error executing commit: ${error instanceof Error ? error.message : "Unknown error"}`
709
+ }
710
+ ],
711
+ isError: true
712
+ };
713
+ }
714
+ }
715
+ );
716
+ }
717
+
718
+ // src/server.ts
719
+ function createServer() {
720
+ const server = new McpServer({
721
+ name: "claude-code-autocommit",
722
+ version: "1.0.0"
723
+ });
724
+ registerAnalyzeChangesTool(server);
725
+ registerGenerateMessageTool(server);
726
+ registerExecuteCommitTool(server);
727
+ return server;
728
+ }
729
+
730
+ // src/index.ts
731
+ async function main() {
732
+ const server = createServer();
733
+ const transport = new StdioServerTransport();
734
+ await server.connect(transport);
735
+ process.on("SIGINT", async () => {
736
+ await server.close();
737
+ process.exit(0);
738
+ });
739
+ process.on("SIGTERM", async () => {
740
+ await server.close();
741
+ process.exit(0);
742
+ });
743
+ }
744
+ main().catch((error) => {
745
+ console.error("Fatal error:", error);
746
+ process.exit(1);
747
+ });
748
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/server.ts","../src/tools/analyze-changes.ts","../src/git/analyzer.ts","../src/commit/type-detector.ts","../src/commit/scope-detector.ts","../src/tools/generate-message.ts","../src/commit/generator.ts","../src/tools/execute-commit.ts","../src/git/executor.ts"],"sourcesContent":["import { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { createServer } from \"./server.js\";\n\nasync function main() {\n const server = createServer();\n const transport = new StdioServerTransport();\n\n await server.connect(transport);\n\n // Handle graceful shutdown\n process.on(\"SIGINT\", async () => {\n await server.close();\n process.exit(0);\n });\n\n process.on(\"SIGTERM\", async () => {\n await server.close();\n process.exit(0);\n });\n}\n\nmain().catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n});\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { registerAnalyzeChangesTool } from \"./tools/analyze-changes.js\";\nimport { registerGenerateMessageTool } from \"./tools/generate-message.js\";\nimport { registerExecuteCommitTool } from \"./tools/execute-commit.js\";\n\nexport function createServer(): McpServer {\n const server = new McpServer({\n name: \"claude-code-autocommit\",\n version: \"1.0.0\",\n });\n\n // Register all tools\n registerAnalyzeChangesTool(server);\n registerGenerateMessageTool(server);\n registerExecuteCommitTool(server);\n\n return server;\n}\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport { analyzeChanges } from \"../git/analyzer.js\";\n\nconst AnalyzeChangesSchema = z.object({\n include_staged: z\n .boolean()\n .optional()\n .default(true)\n .describe(\"Include staged changes in analysis\"),\n include_unstaged: z\n .boolean()\n .optional()\n .default(false)\n .describe(\"Include unstaged changes in analysis\"),\n cwd: z\n .string()\n .optional()\n .describe(\"Working directory (defaults to current directory)\"),\n});\n\nexport function registerAnalyzeChangesTool(server: McpServer): void {\n server.tool(\n \"analyze_changes\",\n \"Analyze git changes and return a token-efficient summary with file paths, status, additions/deletions, and suggested commit type/scope\",\n AnalyzeChangesSchema.shape,\n async (params) => {\n const { include_staged, include_unstaged, cwd } = AnalyzeChangesSchema.parse(params);\n\n try {\n const summary = await analyzeChanges({\n includeStaged: include_staged,\n includeUnstaged: include_unstaged,\n cwd: cwd || process.cwd(),\n });\n\n // Format output to minimize tokens while preserving useful info\n const output = {\n files: summary.files.map((f) => ({\n path: f.path,\n status: f.status,\n changes: `+${f.additions}/-${f.deletions}`,\n ...(f.oldPath ? { oldPath: f.oldPath } : {}),\n })),\n summary: {\n totalFiles: summary.totalFiles,\n totalAdditions: summary.totalAdditions,\n totalDeletions: summary.totalDeletions,\n extensions: summary.extensions,\n directories: summary.topDirectories,\n },\n suggested: {\n type: summary.suggestedType,\n scope: summary.suggestedScope,\n },\n };\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(output, null, 2),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error analyzing changes: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n}\n","import { spawn } from \"node:child_process\";\nimport type {\n AnalyzeOptions,\n ChangesSummary,\n FileChange,\n FileStatus,\n} from \"./types.js\";\nimport { detectType } from \"../commit/type-detector.js\";\nimport { detectScope } from \"../commit/scope-detector.js\";\n\nfunction runGitCommand(\n args: string[],\n cwd: string\n): Promise<{ stdout: string; stderr: string }> {\n return new Promise((resolve, reject) => {\n const proc = spawn(\"git\", args, { cwd });\n let stdout = \"\";\n let stderr = \"\";\n\n proc.stdout.on(\"data\", (data) => {\n stdout += data.toString();\n });\n\n proc.stderr.on(\"data\", (data) => {\n stderr += data.toString();\n });\n\n proc.on(\"close\", (code) => {\n if (code === 0) {\n resolve({ stdout, stderr });\n } else {\n reject(new Error(`Git command failed: ${stderr || stdout}`));\n }\n });\n\n proc.on(\"error\", (err) => {\n reject(err);\n });\n });\n}\n\nfunction parseNumstat(output: string): Map<string, { add: number; del: number }> {\n const stats = new Map<string, { add: number; del: number }>();\n if (!output.trim()) return stats;\n\n const lines = output.trim().split(\"\\n\");\n for (const line of lines) {\n const parts = line.split(\"\\t\");\n if (parts.length >= 3) {\n const [addStr, delStr, ...pathParts] = parts;\n const path = pathParts.join(\"\\t\");\n const add = addStr === \"-\" ? 0 : parseInt(addStr, 10);\n const del = delStr === \"-\" ? 0 : parseInt(delStr, 10);\n stats.set(path, { add, del });\n }\n }\n return stats;\n}\n\nfunction parseNameStatus(output: string): Map<string, { status: FileStatus; oldPath?: string }> {\n const statuses = new Map<string, { status: FileStatus; oldPath?: string }>();\n if (!output.trim()) return statuses;\n\n const lines = output.trim().split(\"\\n\");\n for (const line of lines) {\n const parts = line.split(\"\\t\");\n if (parts.length >= 2) {\n const statusCode = parts[0].charAt(0) as FileStatus;\n if (parts[0].startsWith(\"R\") || parts[0].startsWith(\"C\")) {\n // Rename or copy: status\\told_path\\tnew_path\n const oldPath = parts[1];\n const newPath = parts[2];\n statuses.set(newPath, { status: statusCode, oldPath });\n } else {\n statuses.set(parts[1], { status: statusCode });\n }\n }\n }\n return statuses;\n}\n\nfunction getExtension(path: string): string {\n const match = path.match(/\\.([^./]+)$/);\n return match ? match[1] : \"\";\n}\n\nfunction getTopDirectory(path: string): string {\n const parts = path.split(\"/\");\n return parts.length > 1 ? parts[0] : \"\";\n}\n\nexport async function analyzeChanges(\n options: AnalyzeOptions = {}\n): Promise<ChangesSummary> {\n const {\n includeStaged = true,\n includeUnstaged = false,\n cwd = process.cwd(),\n } = options;\n\n const files: FileChange[] = [];\n const extensionSet = new Set<string>();\n const directorySet = new Set<string>();\n\n // Build git diff arguments\n const baseArgs = includeStaged && !includeUnstaged ? [\"--cached\"] : [];\n if (!includeStaged && includeUnstaged) {\n // Only unstaged changes (no --cached)\n } else if (includeStaged && includeUnstaged) {\n // Include both: use HEAD\n baseArgs.length = 0;\n baseArgs.push(\"HEAD\");\n }\n\n // Run both commands in parallel for efficiency\n const [numstatResult, nameStatusResult] = await Promise.all([\n runGitCommand([\"diff\", ...baseArgs, \"--numstat\"], cwd),\n runGitCommand([\"diff\", ...baseArgs, \"--name-status\"], cwd),\n ]);\n\n const numstatMap = parseNumstat(numstatResult.stdout);\n const nameStatusMap = parseNameStatus(nameStatusResult.stdout);\n\n // Merge the data\n for (const [path, { status, oldPath }] of nameStatusMap) {\n const stats = numstatMap.get(path) || numstatMap.get(oldPath || \"\") || { add: 0, del: 0 };\n\n const fileChange: FileChange = {\n path,\n status,\n additions: stats.add,\n deletions: stats.del,\n };\n\n if (oldPath) {\n fileChange.oldPath = oldPath;\n }\n\n files.push(fileChange);\n\n const ext = getExtension(path);\n if (ext) extensionSet.add(ext);\n\n const dir = getTopDirectory(path);\n if (dir) directorySet.add(dir);\n }\n\n const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);\n const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);\n\n const suggestedType = detectType(files);\n const suggestedScope = detectScope(files);\n\n return {\n files,\n totalFiles: files.length,\n totalAdditions,\n totalDeletions,\n extensions: Array.from(extensionSet),\n topDirectories: Array.from(directorySet),\n suggestedType,\n suggestedScope,\n };\n}\n","import type { CommitType, FileChange } from \"../git/types.js\";\n\ninterface TypePattern {\n type: CommitType;\n patterns: RegExp[];\n priority: number;\n}\n\nconst TYPE_PATTERNS: TypePattern[] = [\n {\n type: \"test\",\n patterns: [\n /\\.test\\.[jt]sx?$/,\n /\\.spec\\.[jt]sx?$/,\n /__tests__\\//,\n /test\\//,\n /tests\\//,\n /\\.cy\\.[jt]sx?$/,\n /cypress\\//,\n ],\n priority: 10,\n },\n {\n type: \"docs\",\n patterns: [\n /\\.md$/i,\n /^docs\\//,\n /^documentation\\//,\n /^README/i,\n /CHANGELOG/i,\n /LICENSE/i,\n /\\.txt$/,\n ],\n priority: 9,\n },\n {\n type: \"ci\",\n patterns: [\n /^\\.github\\/workflows\\//,\n /^\\.github\\/actions\\//,\n /^\\.circleci\\//,\n /^\\.travis\\.yml$/,\n /^\\.gitlab-ci\\.yml$/,\n /^Jenkinsfile$/,\n /^\\.buildkite\\//,\n ],\n priority: 8,\n },\n {\n type: \"build\",\n patterns: [\n /^package\\.json$/,\n /^package-lock\\.json$/,\n /^yarn\\.lock$/,\n /^pnpm-lock\\.yaml$/,\n /^tsconfig.*\\.json$/,\n /^webpack\\./,\n /^vite\\.config\\./,\n /^rollup\\.config\\./,\n /^esbuild\\./,\n /^tsup\\.config\\./,\n /^Makefile$/,\n /^CMakeLists\\.txt$/,\n /^build\\./,\n /^Dockerfile$/,\n /^docker-compose/,\n ],\n priority: 7,\n },\n {\n type: \"style\",\n patterns: [\n /\\.css$/,\n /\\.scss$/,\n /\\.sass$/,\n /\\.less$/,\n /\\.styled\\.[jt]sx?$/,\n /^\\.prettier/,\n /^\\.eslint/,\n /^\\.stylelint/,\n /^\\.editorconfig$/,\n ],\n priority: 6,\n },\n {\n type: \"chore\",\n patterns: [\n /^\\.gitignore$/,\n /^\\.gitattributes$/,\n /^\\.npmignore$/,\n /^\\.nvmrc$/,\n /^\\.node-version$/,\n /^\\.env\\.example$/,\n /\\.lock$/,\n ],\n priority: 5,\n },\n {\n type: \"perf\",\n patterns: [/perf\\//, /benchmark\\//, /\\.bench\\.[jt]sx?$/],\n priority: 4,\n },\n];\n\nfunction matchesPattern(path: string, patterns: RegExp[]): boolean {\n return patterns.some((pattern) => pattern.test(path));\n}\n\nexport function detectType(files: FileChange[]): CommitType | null {\n if (files.length === 0) return null;\n\n // Count matches for each type\n const typeCounts = new Map<CommitType, number>();\n\n for (const file of files) {\n for (const { type, patterns } of TYPE_PATTERNS) {\n if (matchesPattern(file.path, patterns)) {\n typeCounts.set(type, (typeCounts.get(type) || 0) + 1);\n break; // Only match first pattern for each file\n }\n }\n }\n\n // If all files match a single type, use that\n const totalMatchedFiles = Array.from(typeCounts.values()).reduce(\n (a, b) => a + b,\n 0\n );\n\n if (totalMatchedFiles === files.length && typeCounts.size === 1) {\n return typeCounts.keys().next().value ?? null;\n }\n\n // If majority of files match a type, use that\n for (const [type, count] of typeCounts) {\n if (count > files.length / 2) {\n return type;\n }\n }\n\n // Check for new files (feat) vs modifications (fix)\n const newFiles = files.filter((f) => f.status === \"A\");\n const modifiedFiles = files.filter((f) => f.status === \"M\");\n const deletedFiles = files.filter((f) => f.status === \"D\");\n\n // If all files are deleted, it's likely a refactor or chore\n if (deletedFiles.length === files.length) {\n return \"chore\";\n }\n\n // If mostly new files in src/, likely a feature\n const newSrcFiles = newFiles.filter(\n (f) =>\n f.path.startsWith(\"src/\") ||\n f.path.startsWith(\"lib/\") ||\n f.path.startsWith(\"app/\")\n );\n if (newSrcFiles.length > files.length / 2) {\n return \"feat\";\n }\n\n // If mostly modifications, likely a fix\n if (modifiedFiles.length > files.length / 2) {\n return \"fix\";\n }\n\n // Default based on file status\n if (newFiles.length > 0) {\n return \"feat\";\n }\n\n return \"fix\";\n}\n\nexport function detectTypeFromPath(path: string): CommitType | null {\n for (const { type, patterns } of TYPE_PATTERNS) {\n if (matchesPattern(path, patterns)) {\n return type;\n }\n }\n return null;\n}\n","import type { FileChange } from \"../git/types.js\";\n\nfunction extractScope(path: string): string | null {\n const parts = path.split(\"/\");\n\n // Handle monorepo patterns: packages/core/... → core\n if (parts[0] === \"packages\" && parts.length > 2) {\n return parts[1];\n }\n\n // Handle src/module/... → module\n if (parts[0] === \"src\" && parts.length > 2) {\n return parts[1];\n }\n\n // Handle lib/module/... → module\n if (parts[0] === \"lib\" && parts.length > 2) {\n return parts[1];\n }\n\n // Handle app/module/... → module\n if (parts[0] === \"app\" && parts.length > 2) {\n return parts[1];\n }\n\n // Handle components/ComponentName/... → component name in kebab-case\n if (parts[0] === \"components\" && parts.length > 1) {\n return toKebabCase(parts[1]);\n }\n\n // Handle routes or pages: pages/dashboard/... → dashboard\n if ((parts[0] === \"pages\" || parts[0] === \"routes\") && parts.length > 1) {\n return parts[1];\n }\n\n // Handle api routes: api/users/... → users\n if (parts[0] === \"api\" && parts.length > 1) {\n return parts[1];\n }\n\n // Handle features pattern: features/auth/... → auth\n if (parts[0] === \"features\" && parts.length > 1) {\n return parts[1];\n }\n\n // Handle modules pattern: modules/payment/... → payment\n if (parts[0] === \"modules\" && parts.length > 1) {\n return parts[1];\n }\n\n // For root-level config files, use the file name without extension\n if (parts.length === 1) {\n const match = path.match(/^\\.?([a-zA-Z]+)/);\n if (match) {\n return match[1].toLowerCase();\n }\n }\n\n return null;\n}\n\nfunction toKebabCase(str: string): string {\n return str\n .replace(/([a-z])([A-Z])/g, \"$1-$2\")\n .replace(/[\\s_]+/g, \"-\")\n .toLowerCase();\n}\n\nexport function detectScope(files: FileChange[]): string | null {\n if (files.length === 0) return null;\n\n // Extract scopes from all files\n const scopes = files\n .map((f) => extractScope(f.path))\n .filter((s): s is string => s !== null);\n\n if (scopes.length === 0) return null;\n\n // Count scope occurrences\n const scopeCounts = new Map<string, number>();\n for (const scope of scopes) {\n scopeCounts.set(scope, (scopeCounts.get(scope) || 0) + 1);\n }\n\n // If all files have the same scope, return it\n if (scopeCounts.size === 1) {\n return scopes[0];\n }\n\n // If one scope dominates (>70%), use it\n for (const [scope, count] of scopeCounts) {\n if (count > files.length * 0.7) {\n return scope;\n }\n }\n\n // Multiple scopes - return null to indicate no single scope\n return null;\n}\n\nexport function extractScopeFromPath(path: string): string | null {\n return extractScope(path);\n}\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport { analyzeChanges } from \"../git/analyzer.js\";\nimport { generateCommitMessage } from \"../commit/generator.js\";\nimport type { CommitType } from \"../git/types.js\";\n\nconst CommitTypeEnum = z.enum([\n \"feat\",\n \"fix\",\n \"docs\",\n \"style\",\n \"refactor\",\n \"test\",\n \"build\",\n \"ci\",\n \"chore\",\n \"perf\",\n \"revert\",\n]);\n\nconst GenerateMessageSchema = z.object({\n type: CommitTypeEnum.optional().describe(\n \"Override the commit type (feat, fix, docs, etc.)\"\n ),\n scope: z\n .string()\n .nullable()\n .optional()\n .describe(\"Override the scope (e.g., auth, api, ui)\"),\n description: z\n .string()\n .optional()\n .describe(\"Override the description text\"),\n breaking: z\n .boolean()\n .optional()\n .default(false)\n .describe(\"Mark as breaking change\"),\n include_staged: z\n .boolean()\n .optional()\n .default(true)\n .describe(\"Include staged changes\"),\n include_unstaged: z\n .boolean()\n .optional()\n .default(false)\n .describe(\"Include unstaged changes\"),\n cwd: z\n .string()\n .optional()\n .describe(\"Working directory (defaults to current directory)\"),\n});\n\nexport function registerGenerateMessageTool(server: McpServer): void {\n server.tool(\n \"generate_commit_message\",\n \"Generate a Conventional Commits message based on git changes. Can auto-detect type and scope, or accept overrides.\",\n GenerateMessageSchema.shape,\n async (params) => {\n const {\n type,\n scope,\n description,\n breaking,\n include_staged,\n include_unstaged,\n cwd,\n } = GenerateMessageSchema.parse(params);\n\n try {\n // First analyze the changes\n const summary = await analyzeChanges({\n includeStaged: include_staged,\n includeUnstaged: include_unstaged,\n cwd: cwd || process.cwd(),\n });\n\n if (summary.totalFiles === 0) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n error: \"No changes found to generate commit message\",\n suggestion: \"Stage some changes first with 'git add'\",\n }),\n },\n ],\n isError: true,\n };\n }\n\n // Generate the commit message\n const result = generateCommitMessage(summary, {\n type: type as CommitType | undefined,\n scope: scope ?? undefined,\n description,\n breaking,\n });\n\n const output = {\n message: result.message.full,\n components: {\n type: result.message.type,\n scope: result.message.scope,\n description: result.message.description,\n breaking: result.message.breaking,\n },\n confidence: result.confidence,\n analysis: {\n totalFiles: summary.totalFiles,\n totalAdditions: summary.totalAdditions,\n totalDeletions: summary.totalDeletions,\n },\n };\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(output, null, 2),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error generating commit message: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n}\n","import type {\n ChangesSummary,\n CommitMessage,\n CommitType,\n GenerateOptions,\n} from \"../git/types.js\";\n\nfunction generateDescription(summary: ChangesSummary): string {\n const { files, totalAdditions, totalDeletions } = summary;\n\n if (files.length === 0) {\n return \"no changes\";\n }\n\n // Single file - use file name\n if (files.length === 1) {\n const file = files[0];\n const fileName = file.path.split(\"/\").pop() || file.path;\n const action = getActionVerb(file.status);\n return `${action} ${fileName}`;\n }\n\n // Multiple files in same directory\n const directories = new Set(files.map((f) => f.path.split(\"/\").slice(0, -1).join(\"/\")));\n if (directories.size === 1) {\n const dir = directories.values().next().value;\n const dirName = dir ? dir.split(\"/\").pop() || dir : \"root\";\n return `update ${files.length} files in ${dirName}`;\n }\n\n // Describe by primary action\n const added = files.filter((f) => f.status === \"A\").length;\n const modified = files.filter((f) => f.status === \"M\").length;\n const deleted = files.filter((f) => f.status === \"D\").length;\n const renamed = files.filter((f) => f.status === \"R\").length;\n\n const parts: string[] = [];\n if (added > 0) parts.push(`add ${added} file${added > 1 ? \"s\" : \"\"}`);\n if (modified > 0) parts.push(`update ${modified} file${modified > 1 ? \"s\" : \"\"}`);\n if (deleted > 0) parts.push(`remove ${deleted} file${deleted > 1 ? \"s\" : \"\"}`);\n if (renamed > 0) parts.push(`rename ${renamed} file${renamed > 1 ? \"s\" : \"\"}`);\n\n if (parts.length === 0) {\n return `update ${files.length} files`;\n }\n\n return parts.join(\", \");\n}\n\nfunction getActionVerb(status: string): string {\n switch (status) {\n case \"A\":\n return \"add\";\n case \"M\":\n return \"update\";\n case \"D\":\n return \"remove\";\n case \"R\":\n return \"rename\";\n case \"C\":\n return \"copy\";\n default:\n return \"update\";\n }\n}\n\nfunction formatCommitMessage(\n type: CommitType,\n scope: string | null,\n description: string,\n breaking: boolean\n): string {\n const breakingMark = breaking ? \"!\" : \"\";\n const scopePart = scope ? `(${scope})` : \"\";\n return `${type}${scopePart}${breakingMark}: ${description}`;\n}\n\nexport interface GenerateResult {\n message: CommitMessage;\n confidence: \"high\" | \"medium\" | \"low\";\n}\n\nexport function generateCommitMessage(\n summary: ChangesSummary,\n options: GenerateOptions = {}\n): GenerateResult {\n const type = options.type || (summary.suggestedType as CommitType) || \"chore\";\n const scope = options.scope !== undefined ? options.scope : summary.suggestedScope;\n const description = options.description || generateDescription(summary);\n const breaking = options.breaking || false;\n\n const full = formatCommitMessage(type, scope, description, breaking);\n\n // Calculate confidence\n let confidence: \"high\" | \"medium\" | \"low\" = \"medium\";\n\n if (options.type && options.description) {\n // User provided explicit values\n confidence = \"high\";\n } else if (summary.totalFiles === 1) {\n // Single file changes are usually clear\n confidence = \"high\";\n } else if (summary.totalFiles > 10) {\n // Many files - harder to auto-describe\n confidence = \"low\";\n } else if (summary.suggestedType !== null) {\n // Pattern matched a type\n confidence = \"medium\";\n }\n\n return {\n message: {\n type,\n scope,\n description,\n breaking,\n full,\n },\n confidence,\n };\n}\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport { executeCommit } from \"../git/executor.js\";\n\nconst ExecuteCommitSchema = z.object({\n message: z.string().min(1).describe(\"The commit message to use\"),\n confirmed: z\n .boolean()\n .describe(\"Must be true to execute the commit (safety check)\"),\n cwd: z\n .string()\n .optional()\n .describe(\"Working directory (defaults to current directory)\"),\n});\n\nexport function registerExecuteCommitTool(server: McpServer): void {\n server.tool(\n \"execute_commit\",\n \"Execute a git commit with the provided message. Requires confirmed=true as a safety measure.\",\n ExecuteCommitSchema.shape,\n async (params) => {\n const { message, confirmed, cwd } = ExecuteCommitSchema.parse(params);\n\n try {\n const result = await executeCommit(\n message,\n confirmed,\n cwd || process.cwd()\n );\n\n if (result.success) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: true,\n commitHash: result.commitHash,\n message: message,\n }),\n },\n ],\n };\n } else {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: false,\n error: result.error,\n }),\n },\n ],\n isError: true,\n };\n }\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error executing commit: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n}\n","import { spawn } from \"node:child_process\";\nimport type { CommitResult } from \"./types.js\";\n\nfunction runGitCommand(\n args: string[],\n cwd: string\n): Promise<{ stdout: string; stderr: string; code: number }> {\n return new Promise((resolve, reject) => {\n const proc = spawn(\"git\", args, { cwd });\n let stdout = \"\";\n let stderr = \"\";\n\n proc.stdout.on(\"data\", (data) => {\n stdout += data.toString();\n });\n\n proc.stderr.on(\"data\", (data) => {\n stderr += data.toString();\n });\n\n proc.on(\"close\", (code) => {\n resolve({ stdout, stderr, code: code ?? 1 });\n });\n\n proc.on(\"error\", (err) => {\n reject(err);\n });\n });\n}\n\nexport async function executeCommit(\n message: string,\n confirmed: boolean,\n cwd: string = process.cwd()\n): Promise<CommitResult> {\n if (!confirmed) {\n return {\n success: false,\n error: \"Commit not confirmed. Set confirmed=true to execute the commit.\",\n };\n }\n\n if (!message || message.trim().length === 0) {\n return {\n success: false,\n error: \"Commit message cannot be empty.\",\n };\n }\n\n try {\n // Check if there are staged changes\n const statusResult = await runGitCommand(\n [\"diff\", \"--cached\", \"--quiet\"],\n cwd\n );\n\n if (statusResult.code === 0) {\n return {\n success: false,\n error: \"No staged changes to commit.\",\n };\n }\n\n // Execute the commit\n const commitResult = await runGitCommand([\"commit\", \"-m\", message], cwd);\n\n if (commitResult.code !== 0) {\n return {\n success: false,\n error: commitResult.stderr || commitResult.stdout,\n };\n }\n\n // Get the commit hash\n const hashResult = await runGitCommand([\"rev-parse\", \"HEAD\"], cwd);\n const commitHash = hashResult.stdout.trim();\n\n return {\n success: true,\n commitHash,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Unknown error occurred\",\n };\n }\n}\n\nexport async function stageFiles(\n files: string[],\n cwd: string = process.cwd()\n): Promise<{ success: boolean; error?: string }> {\n try {\n const result = await runGitCommand([\"add\", ...files], cwd);\n if (result.code !== 0) {\n return { success: false, error: result.stderr };\n }\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Unknown error occurred\",\n };\n }\n}\n"],"mappings":"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","names":["z","z","z","spawn","runGitCommand","z"]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@theoribbi/claude-code-autocommit",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for generating Conventional Commits messages from git changes",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "claude-code-autocommit": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsup",
15
+ "dev": "tsup --watch",
16
+ "start": "node dist/index.js",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "claude",
22
+ "git",
23
+ "commit",
24
+ "conventional-commits",
25
+ "model-context-protocol"
26
+ ],
27
+ "author": "theoribbi",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/theoribbi/claude-code-autocommit.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/theoribbi/claude-code-autocommit/issues"
35
+ },
36
+ "homepage": "https://github.com/theoribbi/claude-code-autocommit#readme",
37
+ "dependencies": {
38
+ "@modelcontextprotocol/sdk": "^1.0.0",
39
+ "zod": "^3.23.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^20.0.0",
43
+ "tsup": "^8.0.0",
44
+ "typescript": "^5.0.0"
45
+ },
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ }
52
+ }