@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 +157 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +748 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
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
|
package/dist/index.d.ts
ADDED
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
|
+
}
|