@intentsolutionsio/devops-automation-pack 1.0.0 → 1.0.3
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 +49 -13
- package/package.json +1 -1
- package/skills/generating-conventional-commits/SKILL.md +15 -5
- package/skills/generating-conventional-commits/references/README.md +0 -1
- package/skills/generating-conventional-commits/scripts/generate_commit_message.py +42 -90
- package/skills/generating-conventional-commits/scripts/suggest_commit_type.py +104 -140
- package/skills/generating-conventional-commits/scripts/validate_commit_message.py +12 -25
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ Version 1.0.0 | October 2025
|
|
|
11
11
|
The **DevOps Automation Pack** is a collection of 25 professional plugins for Claude Code that automate your DevOps workflow.
|
|
12
12
|
|
|
13
13
|
**Instead of manually:**
|
|
14
|
+
|
|
14
15
|
- Writing Git commit messages
|
|
15
16
|
- Creating CI/CD pipelines from scratch
|
|
16
17
|
- Optimizing Dockerfiles
|
|
@@ -18,6 +19,7 @@ The **DevOps Automation Pack** is a collection of 25 professional plugins for Cl
|
|
|
18
19
|
- Writing Terraform modules
|
|
19
20
|
|
|
20
21
|
**You get:**
|
|
22
|
+
|
|
21
23
|
- AI-generated conventional commits
|
|
22
24
|
- Production-ready pipelines in minutes
|
|
23
25
|
- Automatic Docker optimization
|
|
@@ -33,6 +35,7 @@ The **DevOps Automation Pack** is a collection of 25 professional plugins for Cl
|
|
|
33
35
|
### 25 Professional Plugins
|
|
34
36
|
|
|
35
37
|
**Git Workflow (5 commands)**
|
|
38
|
+
|
|
36
39
|
- `/commit-smart` (gc) - Generate conventional commits with AI
|
|
37
40
|
- `/pr-create` (gpr) - Create pull requests with professional templates
|
|
38
41
|
- `/branch-create` (bc) - Create properly named feature branches
|
|
@@ -40,6 +43,7 @@ The **DevOps Automation Pack** is a collection of 25 professional plugins for Cl
|
|
|
40
43
|
- `/rebase-interactive` (ri) - Interactive rebase workflow
|
|
41
44
|
|
|
42
45
|
**CI/CD Automation (6 plugins: 1 agent + 5 commands)**
|
|
46
|
+
|
|
43
47
|
- CI/CD Expert Agent - Pipeline design specialist
|
|
44
48
|
- `/github-actions-create` (gha) - Generate GitHub Actions workflows
|
|
45
49
|
- `/gitlab-ci-create` (glci) - Generate GitLab CI pipelines
|
|
@@ -48,24 +52,28 @@ The **DevOps Automation Pack** is a collection of 25 professional plugins for Cl
|
|
|
48
52
|
- `/deployment-strategy` (ds) - Recommend deployment approach
|
|
49
53
|
|
|
50
54
|
**Docker (4 plugins: 1 agent + 3 commands)**
|
|
55
|
+
|
|
51
56
|
- Docker Specialist Agent - Container optimization expert
|
|
52
57
|
- `/dockerfile-generate` (dg) - Create optimized Dockerfiles
|
|
53
58
|
- `/docker-compose-create` (dcc) - Generate docker-compose.yml
|
|
54
59
|
- `/docker-optimize` (do) - Reduce image size by 50-80%
|
|
55
60
|
|
|
56
61
|
**Kubernetes (4 plugins: 1 agent + 3 commands)**
|
|
62
|
+
|
|
57
63
|
- Kubernetes Expert Agent - K8s orchestration specialist
|
|
58
64
|
- `/k8s-manifest-generate` (km) - Generate production-ready manifests
|
|
59
65
|
- `/k8s-helm-chart` (kh) - Create Helm charts with best practices
|
|
60
66
|
- `/k8s-troubleshoot` (kt) - Debug pod failures instantly
|
|
61
67
|
|
|
62
68
|
**Terraform (4 plugins: 1 agent + 3 commands)**
|
|
69
|
+
|
|
63
70
|
- Terraform Architect Agent - Infrastructure as code expert
|
|
64
71
|
- `/terraform-module-create` (tm) - Generate reusable modules
|
|
65
72
|
- `/terraform-plan-analyze` (tpa) - Analyze plans for risks
|
|
66
73
|
- `/cloudformation-generate` (cfn) - Create CloudFormation templates
|
|
67
74
|
|
|
68
75
|
**Deployment (2 plugins: 1 agent + 1 command)**
|
|
76
|
+
|
|
69
77
|
- Deployment Specialist Agent - Release management expert
|
|
70
78
|
- `/monitoring-setup` (ms) - Set up Prometheus + Grafana
|
|
71
79
|
|
|
@@ -102,13 +110,15 @@ See [`docs/QUICK_START.md`](docs/QUICK_START.md) for detailed walkthrough.
|
|
|
102
110
|
## Who Is This For?
|
|
103
111
|
|
|
104
112
|
### Perfect For:
|
|
105
|
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
-
|
|
113
|
+
|
|
114
|
+
- **Junior developers** learning DevOps practices
|
|
115
|
+
- **Senior engineers** who want to move faster
|
|
116
|
+
- **DevOps teams** standardizing workflows
|
|
117
|
+
- **Startups** adopting cloud infrastructure
|
|
118
|
+
- **Enterprises** enforcing best practices
|
|
110
119
|
|
|
111
120
|
### You'll Benefit If You:
|
|
121
|
+
|
|
112
122
|
- Write Git commits, PRs, or create branches
|
|
113
123
|
- Build or optimize CI/CD pipelines
|
|
114
124
|
- Work with Docker containers
|
|
@@ -117,6 +127,7 @@ See [`docs/QUICK_START.md`](docs/QUICK_START.md) for detailed walkthrough.
|
|
|
117
127
|
- Set up monitoring and alerting
|
|
118
128
|
|
|
119
129
|
### Required Knowledge:
|
|
130
|
+
|
|
120
131
|
- Basic Git understanding (commit, push, pull)
|
|
121
132
|
- Familiarity with your chosen tools (Docker, K8s, etc.)
|
|
122
133
|
- **No expert-level knowledge needed** - plugins guide you
|
|
@@ -144,15 +155,18 @@ See [`docs/USE_CASES.md`](docs/USE_CASES.md) for 7 detailed real-world scenarios
|
|
|
144
155
|
## Documentation
|
|
145
156
|
|
|
146
157
|
### Getting Started
|
|
158
|
+
|
|
147
159
|
- **[Installation Guide](docs/INSTALLATION.md)** - Step-by-step setup instructions (5 minutes)
|
|
148
160
|
- **[Quick Start](docs/QUICK_START.md)** - Your first workflow in 5 minutes
|
|
149
161
|
- **[Use Cases](docs/USE_CASES.md)** - 7 real-world scenarios with before/after
|
|
150
162
|
|
|
151
163
|
### Reference
|
|
152
|
-
|
|
164
|
+
|
|
165
|
+
- **Troubleshooting** - Solutions to 20 common problems
|
|
153
166
|
- **Plugin Reference** - Complete command documentation (see each plugin's help)
|
|
154
167
|
|
|
155
168
|
### Support
|
|
169
|
+
|
|
156
170
|
- **Email:** mandy@intentsolutions.io
|
|
157
171
|
- **Response time:** Within 24 hours
|
|
158
172
|
- **Include:** Error message, command, OS, Claude Code version
|
|
@@ -162,12 +176,15 @@ See [`docs/USE_CASES.md`](docs/USE_CASES.md) for 7 detailed real-world scenarios
|
|
|
162
176
|
## Requirements
|
|
163
177
|
|
|
164
178
|
### Minimum Requirements
|
|
179
|
+
|
|
165
180
|
- Claude Code version **1.5.0 or higher**
|
|
166
181
|
- 10 MB free disk space
|
|
167
182
|
- Git installed (for Git workflow commands)
|
|
168
183
|
|
|
169
184
|
### Optional Requirements
|
|
185
|
+
|
|
170
186
|
Depending on which plugins you use:
|
|
187
|
+
|
|
171
188
|
- **Docker commands:** Docker installed and running
|
|
172
189
|
- **Kubernetes commands:** kubectl configured
|
|
173
190
|
- **Terraform commands:** Terraform installed
|
|
@@ -182,32 +199,38 @@ Depending on which plugins you use:
|
|
|
182
199
|
The pack supports these technologies:
|
|
183
200
|
|
|
184
201
|
**Version Control:**
|
|
202
|
+
|
|
185
203
|
- Git (GitHub, GitLab, Bitbucket)
|
|
186
204
|
- Conventional Commits standard
|
|
187
205
|
|
|
188
206
|
**CI/CD Platforms:**
|
|
207
|
+
|
|
189
208
|
- GitHub Actions
|
|
190
209
|
- GitLab CI
|
|
191
210
|
- CircleCI
|
|
192
211
|
- Jenkins (via pipeline optimization)
|
|
193
212
|
|
|
194
213
|
**Containers:**
|
|
214
|
+
|
|
195
215
|
- Docker
|
|
196
216
|
- docker-compose
|
|
197
217
|
- Multi-stage builds
|
|
198
218
|
- Alpine/Debian/Ubuntu base images
|
|
199
219
|
|
|
200
220
|
**Orchestration:**
|
|
221
|
+
|
|
201
222
|
- Kubernetes (1.25+)
|
|
202
223
|
- Helm 3
|
|
203
224
|
- kubectl
|
|
204
225
|
|
|
205
226
|
**Infrastructure:**
|
|
227
|
+
|
|
206
228
|
- Terraform (1.0+)
|
|
207
229
|
- AWS CloudFormation
|
|
208
230
|
- Multi-cloud (AWS, GCP, Azure)
|
|
209
231
|
|
|
210
232
|
**Monitoring:**
|
|
233
|
+
|
|
211
234
|
- Prometheus
|
|
212
235
|
- Grafana
|
|
213
236
|
- AlertManager
|
|
@@ -219,6 +242,7 @@ The pack supports these technologies:
|
|
|
219
242
|
**Launch Price:** $39 (regular $49)
|
|
220
243
|
|
|
221
244
|
**What You Get:**
|
|
245
|
+
|
|
222
246
|
- All 25 plugins (19 commands + 6 agents)
|
|
223
247
|
- Lifetime updates for version 1.x
|
|
224
248
|
- Email support
|
|
@@ -226,6 +250,7 @@ The pack supports these technologies:
|
|
|
226
250
|
- No subscription, pay once
|
|
227
251
|
|
|
228
252
|
**Money-Back Guarantee:**
|
|
253
|
+
|
|
229
254
|
- 30-day full refund
|
|
230
255
|
- No questions asked
|
|
231
256
|
- Email mandy@intentsolutions.io
|
|
@@ -239,6 +264,7 @@ The pack supports these technologies:
|
|
|
239
264
|
The pack is tech-agnostic for most commands. It detects your stack and adapts.
|
|
240
265
|
|
|
241
266
|
**Works with:**
|
|
267
|
+
|
|
242
268
|
- Any Git hosting (GitHub, GitLab, Bitbucket, etc.)
|
|
243
269
|
- Any programming language
|
|
244
270
|
- Any cloud provider (AWS, GCP, Azure)
|
|
@@ -247,6 +273,7 @@ The pack is tech-agnostic for most commands. It detects your stack and adapts.
|
|
|
247
273
|
### Do I need to be a DevOps expert?
|
|
248
274
|
|
|
249
275
|
**No.** The pack is designed for all skill levels:
|
|
276
|
+
|
|
250
277
|
- **Beginners:** Step-by-step guidance and explanations
|
|
251
278
|
- **Intermediate:** Fast workflow automation
|
|
252
279
|
- **Experts:** Time-saving tools and best practices enforcement
|
|
@@ -254,6 +281,7 @@ The pack is tech-agnostic for most commands. It detects your stack and adapts.
|
|
|
254
281
|
### Can I use this for commercial projects?
|
|
255
282
|
|
|
256
283
|
**Yes.** Your license covers:
|
|
284
|
+
|
|
257
285
|
- Personal projects
|
|
258
286
|
- Commercial projects
|
|
259
287
|
- Team usage (one license per developer)
|
|
@@ -261,6 +289,7 @@ The pack is tech-agnostic for most commands. It detects your stack and adapts.
|
|
|
261
289
|
### How often is it updated?
|
|
262
290
|
|
|
263
291
|
**Version 1.x updates:** Free for life
|
|
292
|
+
|
|
264
293
|
- Bug fixes: As needed
|
|
265
294
|
- New features: Quarterly
|
|
266
295
|
- Security patches: Immediately
|
|
@@ -270,6 +299,7 @@ The pack is tech-agnostic for most commands. It detects your stack and adapts.
|
|
|
270
299
|
### What if I have issues?
|
|
271
300
|
|
|
272
301
|
**Support channels:**
|
|
302
|
+
|
|
273
303
|
1. **Documentation:** Check `docs/000-docs/157-DR-FAQS-troubleshooting.md` (covers 20 common issues)
|
|
274
304
|
2. **Email:** mandy@intentsolutions.io (response within 24 hours)
|
|
275
305
|
3. **Refund:** 30-day money-back guarantee
|
|
@@ -279,16 +309,20 @@ The pack is tech-agnostic for most commands. It detects your stack and adapts.
|
|
|
279
309
|
## Next Steps
|
|
280
310
|
|
|
281
311
|
### 1. Install the Pack
|
|
312
|
+
|
|
282
313
|
See [`docs/INSTALLATION.md`](docs/INSTALLATION.md) for step-by-step instructions.
|
|
283
314
|
|
|
284
315
|
### 2. Run Your First Workflow
|
|
316
|
+
|
|
285
317
|
Follow [`docs/QUICK_START.md`](docs/QUICK_START.md) - takes 5 minutes.
|
|
286
318
|
|
|
287
319
|
### 3. Explore Use Cases
|
|
320
|
+
|
|
288
321
|
Read [`docs/USE_CASES.md`](docs/USE_CASES.md) to see how others use it.
|
|
289
322
|
|
|
290
323
|
### 4. Get Help If Needed
|
|
291
|
-
|
|
324
|
+
|
|
325
|
+
Check `docs/000-docs/157-DR-FAQS-troubleshooting.md` or email support.
|
|
292
326
|
|
|
293
327
|
---
|
|
294
328
|
|
|
@@ -297,11 +331,12 @@ Check [`docs/000-docs/157-DR-FAQS-troubleshooting.md`](docs/000-docs/157-DR-FAQS
|
|
|
297
331
|
This plugin pack is licensed for individual use. See LICENSE file for details.
|
|
298
332
|
|
|
299
333
|
**Key Points:**
|
|
300
|
-
|
|
301
|
-
-
|
|
302
|
-
-
|
|
303
|
-
-
|
|
304
|
-
-
|
|
334
|
+
|
|
335
|
+
- Use on unlimited personal projects
|
|
336
|
+
- Use on unlimited commercial projects
|
|
337
|
+
- One license per developer
|
|
338
|
+
- No redistribution
|
|
339
|
+
- No reselling
|
|
305
340
|
|
|
306
341
|
---
|
|
307
342
|
|
|
@@ -319,6 +354,7 @@ This plugin pack is licensed for individual use. See LICENSE file for details.
|
|
|
319
354
|
## Version History
|
|
320
355
|
|
|
321
356
|
**v1.0.0** (October 10, 2025)
|
|
357
|
+
|
|
322
358
|
- Initial release
|
|
323
359
|
- 25 plugins (19 commands + 6 agents)
|
|
324
360
|
- Complete documentation
|
|
@@ -332,4 +368,4 @@ Start with the [Quick Start Guide](docs/QUICK_START.md) →
|
|
|
332
368
|
|
|
333
369
|
**Need help?** Email mandy@intentsolutions.io
|
|
334
370
|
|
|
335
|
-
**Have questions?** See
|
|
371
|
+
**Have questions?** See Troubleshooting →
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentsolutionsio/devops-automation-pack",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "25 professional DevOps plugins for CI/CD, deployment, Docker, Kubernetes, and infrastructure automation. Save 20+ hours of manual work.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"devops",
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: generating-conventional-commits
|
|
3
|
-
description:
|
|
4
|
-
|
|
3
|
+
description: 'Execute generates conventional commit messages using AI. It analyzes
|
|
4
|
+
code changes and suggests a commit message adhering to the conventional commits
|
|
5
|
+
specification. Use this skill when you need help writing clear, standardized commit
|
|
6
|
+
messages, especially a... Use when managing version control. Trigger with phrases
|
|
7
|
+
like ''commit'', ''branch'', or ''git''.
|
|
8
|
+
|
|
9
|
+
'
|
|
5
10
|
allowed-tools: Read, Write, Edit, Grep, Glob, Bash(cmd:*)
|
|
6
11
|
version: 1.0.0
|
|
7
12
|
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
8
13
|
license: MIT
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
tags:
|
|
15
|
+
- packages
|
|
16
|
+
- conventional-commits
|
|
17
|
+
compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw
|
|
11
18
|
---
|
|
12
19
|
# Devops Automation Pack
|
|
13
20
|
|
|
@@ -26,6 +33,7 @@ Create well-formatted, informative commit messages that follow the conventional
|
|
|
26
33
|
## When to Use This Skill
|
|
27
34
|
|
|
28
35
|
This skill activates when you need to:
|
|
36
|
+
|
|
29
37
|
- Create a commit message after making code changes.
|
|
30
38
|
- Ensure your commit messages follow the conventional commits standard.
|
|
31
39
|
- Save time writing commit messages manually.
|
|
@@ -37,6 +45,7 @@ This skill activates when you need to:
|
|
|
37
45
|
User request: "Generate a commit message for these changes."
|
|
38
46
|
|
|
39
47
|
The skill will:
|
|
48
|
+
|
|
40
49
|
1. Analyze the staged changes related to a new feature.
|
|
41
50
|
2. Generate a commit message like `feat: Implement user authentication`.
|
|
42
51
|
|
|
@@ -45,6 +54,7 @@ The skill will:
|
|
|
45
54
|
User request: "Create a commit for the bug fix."
|
|
46
55
|
|
|
47
56
|
The skill will:
|
|
57
|
+
|
|
48
58
|
1. Analyze the staged changes related to a bug fix.
|
|
49
59
|
2. Generate a commit message like `fix: Resolve issue with incorrect password reset`.
|
|
50
60
|
|
|
@@ -83,4 +93,4 @@ The skill produces structured output relevant to the task.
|
|
|
83
93
|
## Resources
|
|
84
94
|
|
|
85
95
|
- Project documentation
|
|
86
|
-
- Related skills and commands
|
|
96
|
+
- Related skills and commands
|
|
@@ -26,16 +26,11 @@ def get_git_diff(staged_only: bool = False) -> str:
|
|
|
26
26
|
The git diff output as a string
|
|
27
27
|
"""
|
|
28
28
|
try:
|
|
29
|
-
cmd = [
|
|
29
|
+
cmd = ["git", "diff"]
|
|
30
30
|
if staged_only:
|
|
31
|
-
cmd.append(
|
|
31
|
+
cmd.append("--cached")
|
|
32
32
|
|
|
33
|
-
result = subprocess.run(
|
|
34
|
-
cmd,
|
|
35
|
-
capture_output=True,
|
|
36
|
-
text=True,
|
|
37
|
-
check=False
|
|
38
|
-
)
|
|
33
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
39
34
|
|
|
40
35
|
if result.returncode != 0:
|
|
41
36
|
return ""
|
|
@@ -56,7 +51,7 @@ def get_diff_from_file(filepath: str) -> str:
|
|
|
56
51
|
The diff content as a string
|
|
57
52
|
"""
|
|
58
53
|
try:
|
|
59
|
-
with open(filepath,
|
|
54
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
60
55
|
return f.read()
|
|
61
56
|
except FileNotFoundError:
|
|
62
57
|
print(f"Error: File not found: {filepath}", file=sys.stderr)
|
|
@@ -84,55 +79,55 @@ def analyze_diff(diff_content: str) -> Tuple[str, str, str]:
|
|
|
84
79
|
has_fix = False
|
|
85
80
|
files_changed = []
|
|
86
81
|
|
|
87
|
-
lines = diff_content.split(
|
|
82
|
+
lines = diff_content.split("\n")
|
|
88
83
|
|
|
89
84
|
for line in lines:
|
|
90
85
|
# Track which files are being modified
|
|
91
|
-
if line.startswith(
|
|
86
|
+
if line.startswith("diff --git"):
|
|
92
87
|
# Extract filename
|
|
93
|
-
parts = line.split(
|
|
88
|
+
parts = line.split(" ")
|
|
94
89
|
if len(parts) >= 4:
|
|
95
90
|
filepath = parts[3]
|
|
96
91
|
files_changed.append(filepath)
|
|
97
92
|
|
|
98
93
|
# Detect test files
|
|
99
|
-
if
|
|
94
|
+
if "test" in line.lower() or "spec" in line.lower():
|
|
100
95
|
has_tests = True
|
|
101
96
|
|
|
102
97
|
# Detect documentation changes
|
|
103
|
-
if any(x in line.lower() for x in [
|
|
98
|
+
if any(x in line.lower() for x in [".md", "readme", "docs/", "documentation"]):
|
|
104
99
|
has_docs = True
|
|
105
100
|
|
|
106
101
|
# Detect style changes (formatting, whitespace)
|
|
107
|
-
if line.startswith(
|
|
108
|
-
if len(line.lstrip(
|
|
102
|
+
if line.startswith("-") and line.lstrip("-").strip():
|
|
103
|
+
if len(line.lstrip("-").strip()) < 20: # Short lines likely style
|
|
109
104
|
has_style = True
|
|
110
105
|
|
|
111
106
|
# Detect feature additions (new functions, classes)
|
|
112
|
-
if any(x in line for x in [
|
|
113
|
-
if line.startswith(
|
|
107
|
+
if any(x in line for x in ["def ", "class ", "function ", "const ", "let "]):
|
|
108
|
+
if line.startswith("+"):
|
|
114
109
|
has_feature = True
|
|
115
110
|
|
|
116
111
|
# Detect bug fixes (removing problematic code)
|
|
117
|
-
if
|
|
112
|
+
if "bug" in line.lower() or "fix" in line.lower():
|
|
118
113
|
has_fix = True
|
|
119
114
|
|
|
120
115
|
# Determine commit type
|
|
121
116
|
if has_fix:
|
|
122
|
-
commit_type =
|
|
117
|
+
commit_type = "fix"
|
|
123
118
|
elif has_feature:
|
|
124
|
-
commit_type =
|
|
119
|
+
commit_type = "feat"
|
|
125
120
|
elif has_tests:
|
|
126
|
-
commit_type =
|
|
121
|
+
commit_type = "test"
|
|
127
122
|
elif has_docs:
|
|
128
|
-
commit_type =
|
|
123
|
+
commit_type = "docs"
|
|
129
124
|
elif has_style:
|
|
130
|
-
commit_type =
|
|
125
|
+
commit_type = "style"
|
|
131
126
|
else:
|
|
132
|
-
commit_type =
|
|
127
|
+
commit_type = "refactor"
|
|
133
128
|
|
|
134
129
|
# Determine scope from files changed
|
|
135
|
-
scope =
|
|
130
|
+
scope = ""
|
|
136
131
|
if files_changed:
|
|
137
132
|
# Extract directory or module name from first file
|
|
138
133
|
first_file = files_changed[0]
|
|
@@ -141,17 +136,13 @@ def analyze_diff(diff_content: str) -> Tuple[str, str, str]:
|
|
|
141
136
|
scope = parts[0]
|
|
142
137
|
|
|
143
138
|
# Create description
|
|
144
|
-
description =
|
|
139
|
+
description = "Update code based on diff analysis"
|
|
145
140
|
|
|
146
141
|
return commit_type, scope, description
|
|
147
142
|
|
|
148
143
|
|
|
149
144
|
def generate_message(
|
|
150
|
-
commit_type: str,
|
|
151
|
-
scope: Optional[str],
|
|
152
|
-
subject: str,
|
|
153
|
-
body: Optional[str] = None,
|
|
154
|
-
footer: Optional[str] = None
|
|
145
|
+
commit_type: str, scope: Optional[str], subject: str, body: Optional[str] = None, footer: Optional[str] = None
|
|
155
146
|
) -> str:
|
|
156
147
|
"""
|
|
157
148
|
Generate a conventional commit message.
|
|
@@ -173,7 +164,7 @@ def generate_message(
|
|
|
173
164
|
subject_line = f"{commit_type}: {subject}"
|
|
174
165
|
|
|
175
166
|
# Ensure subject starts with lowercase
|
|
176
|
-
parts = subject_line.split(
|
|
167
|
+
parts = subject_line.split(": ", 1)
|
|
177
168
|
if len(parts) == 2:
|
|
178
169
|
subject_line = f"{parts[0]}: {parts[1][0].lower()}{parts[1][1:] if len(parts[1]) > 1 else ''}"
|
|
179
170
|
|
|
@@ -210,52 +201,19 @@ Examples:
|
|
|
210
201
|
|
|
211
202
|
# Output as JSON
|
|
212
203
|
%(prog)s --staged --format json
|
|
213
|
-
"""
|
|
204
|
+
""",
|
|
214
205
|
)
|
|
215
206
|
|
|
216
207
|
input_group = parser.add_mutually_exclusive_group()
|
|
217
|
-
input_group.add_argument(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
help='Analyze staged changes (default)'
|
|
221
|
-
)
|
|
222
|
-
input_group.add_argument(
|
|
223
|
-
'--unstaged',
|
|
224
|
-
action='store_true',
|
|
225
|
-
help='Analyze unstaged changes'
|
|
226
|
-
)
|
|
227
|
-
input_group.add_argument(
|
|
228
|
-
'--file',
|
|
229
|
-
type=str,
|
|
230
|
-
help='Path to diff file'
|
|
231
|
-
)
|
|
208
|
+
input_group.add_argument("--staged", action="store_true", help="Analyze staged changes (default)")
|
|
209
|
+
input_group.add_argument("--unstaged", action="store_true", help="Analyze unstaged changes")
|
|
210
|
+
input_group.add_argument("--file", type=str, help="Path to diff file")
|
|
232
211
|
|
|
233
|
-
parser.add_argument(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
)
|
|
238
|
-
parser.add_argument(
|
|
239
|
-
'--body',
|
|
240
|
-
type=str,
|
|
241
|
-
help='Optional body text'
|
|
242
|
-
)
|
|
243
|
-
parser.add_argument(
|
|
244
|
-
'--footer',
|
|
245
|
-
type=str,
|
|
246
|
-
help='Optional footer (e.g., Closes #123)'
|
|
247
|
-
)
|
|
248
|
-
parser.add_argument(
|
|
249
|
-
'--format',
|
|
250
|
-
choices=['text', 'json'],
|
|
251
|
-
default='text',
|
|
252
|
-
help='Output format (default: text)'
|
|
253
|
-
)
|
|
254
|
-
parser.add_argument(
|
|
255
|
-
'-v', '--verbose',
|
|
256
|
-
action='store_true',
|
|
257
|
-
help='Enable verbose output'
|
|
258
|
-
)
|
|
212
|
+
parser.add_argument("--subject", type=str, help="Custom subject for the commit message")
|
|
213
|
+
parser.add_argument("--body", type=str, help="Optional body text")
|
|
214
|
+
parser.add_argument("--footer", type=str, help="Optional footer (e.g., Closes #123)")
|
|
215
|
+
parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format (default: text)")
|
|
216
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
|
|
259
217
|
|
|
260
218
|
args = parser.parse_args()
|
|
261
219
|
|
|
@@ -278,23 +236,17 @@ Examples:
|
|
|
278
236
|
subject = args.subject or "update code"
|
|
279
237
|
|
|
280
238
|
# Generate message
|
|
281
|
-
message = generate_message(
|
|
282
|
-
commit_type,
|
|
283
|
-
scope if scope else None,
|
|
284
|
-
subject,
|
|
285
|
-
args.body,
|
|
286
|
-
args.footer
|
|
287
|
-
)
|
|
239
|
+
message = generate_message(commit_type, scope if scope else None, subject, args.body, args.footer)
|
|
288
240
|
|
|
289
241
|
# Output result
|
|
290
|
-
if args.format ==
|
|
242
|
+
if args.format == "json":
|
|
291
243
|
output = {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
244
|
+
"type": commit_type,
|
|
245
|
+
"scope": scope or None,
|
|
246
|
+
"subject": subject,
|
|
247
|
+
"body": args.body,
|
|
248
|
+
"footer": args.footer,
|
|
249
|
+
"message": message,
|
|
298
250
|
}
|
|
299
251
|
print(json.dumps(output, indent=2))
|
|
300
252
|
else:
|
|
@@ -303,5 +255,5 @@ Examples:
|
|
|
303
255
|
return 0
|
|
304
256
|
|
|
305
257
|
|
|
306
|
-
if __name__ ==
|
|
258
|
+
if __name__ == "__main__":
|
|
307
259
|
sys.exit(main())
|
|
@@ -11,7 +11,7 @@ import argparse
|
|
|
11
11
|
import sys
|
|
12
12
|
import subprocess
|
|
13
13
|
import json
|
|
14
|
-
from typing import Tuple, Dict,
|
|
14
|
+
from typing import Tuple, Dict, Optional
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def get_git_diff(staged_only: bool = False, ref: Optional[str] = None) -> str:
|
|
@@ -26,18 +26,13 @@ def get_git_diff(staged_only: bool = False, ref: Optional[str] = None) -> str:
|
|
|
26
26
|
The git diff output
|
|
27
27
|
"""
|
|
28
28
|
try:
|
|
29
|
-
cmd = [
|
|
29
|
+
cmd = ["git", "diff"]
|
|
30
30
|
if staged_only:
|
|
31
|
-
cmd.append(
|
|
31
|
+
cmd.append("--cached")
|
|
32
32
|
if ref:
|
|
33
33
|
cmd.append(ref)
|
|
34
34
|
|
|
35
|
-
result = subprocess.run(
|
|
36
|
-
cmd,
|
|
37
|
-
capture_output=True,
|
|
38
|
-
text=True,
|
|
39
|
-
check=False
|
|
40
|
-
)
|
|
35
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
41
36
|
|
|
42
37
|
return result.stdout if result.returncode == 0 else ""
|
|
43
38
|
except FileNotFoundError:
|
|
@@ -47,7 +42,7 @@ def get_git_diff(staged_only: bool = False, ref: Optional[str] = None) -> str:
|
|
|
47
42
|
def get_diff_from_file(filepath: str) -> str:
|
|
48
43
|
"""Read diff from a file."""
|
|
49
44
|
try:
|
|
50
|
-
with open(filepath,
|
|
45
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
51
46
|
return f.read()
|
|
52
47
|
except FileNotFoundError:
|
|
53
48
|
print(f"Error: File not found: {filepath}", file=sys.stderr)
|
|
@@ -68,77 +63,77 @@ def analyze_changes(diff_content: str) -> Dict[str, int]:
|
|
|
68
63
|
Dictionary of change type counts
|
|
69
64
|
"""
|
|
70
65
|
counts = {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
66
|
+
"test_additions": 0,
|
|
67
|
+
"test_removals": 0,
|
|
68
|
+
"doc_changes": 0,
|
|
69
|
+
"style_changes": 0,
|
|
70
|
+
"feature_additions": 0,
|
|
71
|
+
"feature_removals": 0,
|
|
72
|
+
"performance_changes": 0,
|
|
73
|
+
"bug_references": 0,
|
|
74
|
+
"refactor_changes": 0,
|
|
75
|
+
"ci_changes": 0,
|
|
76
|
+
"dependency_changes": 0,
|
|
77
|
+
"total_additions": 0,
|
|
78
|
+
"total_removals": 0,
|
|
84
79
|
}
|
|
85
80
|
|
|
86
|
-
lines = diff_content.split(
|
|
87
|
-
current_file =
|
|
81
|
+
lines = diff_content.split("\n")
|
|
82
|
+
current_file = ""
|
|
88
83
|
|
|
89
84
|
for i, line in enumerate(lines):
|
|
90
85
|
# Track current file
|
|
91
|
-
if line.startswith(
|
|
92
|
-
parts = line.split(
|
|
86
|
+
if line.startswith("diff --git"):
|
|
87
|
+
parts = line.split(" ")
|
|
93
88
|
if len(parts) >= 4:
|
|
94
89
|
current_file = parts[3]
|
|
95
90
|
|
|
96
91
|
# Count additions and removals
|
|
97
|
-
if line.startswith(
|
|
98
|
-
counts[
|
|
92
|
+
if line.startswith("+") and not line.startswith("+++"):
|
|
93
|
+
counts["total_additions"] += 1
|
|
99
94
|
|
|
100
95
|
# Analyze added lines
|
|
101
96
|
content = line[1:]
|
|
102
|
-
if
|
|
103
|
-
counts[
|
|
104
|
-
elif
|
|
105
|
-
counts[
|
|
106
|
-
elif any(x in content.lower() for x in [
|
|
107
|
-
counts[
|
|
108
|
-
elif any(x in content.lower() for x in [
|
|
109
|
-
counts[
|
|
110
|
-
|
|
111
|
-
elif line.startswith(
|
|
112
|
-
counts[
|
|
97
|
+
if "test" in current_file.lower() or "spec" in current_file.lower():
|
|
98
|
+
counts["test_additions"] += 1
|
|
99
|
+
elif "package.json" in current_file or "requirements.txt" in current_file:
|
|
100
|
+
counts["dependency_changes"] += 1
|
|
101
|
+
elif any(x in content.lower() for x in ["def ", "class ", "function ", "const ", "async "]):
|
|
102
|
+
counts["feature_additions"] += 1
|
|
103
|
+
elif any(x in content.lower() for x in ["optimize", "performance", "cache", "lazy"]):
|
|
104
|
+
counts["performance_changes"] += 1
|
|
105
|
+
|
|
106
|
+
elif line.startswith("-") and not line.startswith("---"):
|
|
107
|
+
counts["total_removals"] += 1
|
|
113
108
|
|
|
114
109
|
content = line[1:]
|
|
115
|
-
if
|
|
116
|
-
counts[
|
|
117
|
-
elif any(x in content.lower() for x in [
|
|
118
|
-
counts[
|
|
110
|
+
if "test" in current_file.lower() or "spec" in current_file.lower():
|
|
111
|
+
counts["test_removals"] += 1
|
|
112
|
+
elif any(x in content.lower() for x in ["def ", "class ", "function ", "const "]):
|
|
113
|
+
counts["feature_removals"] += 1
|
|
119
114
|
|
|
120
115
|
# Detect specific change patterns
|
|
121
|
-
if any(x in line.lower() for x in [
|
|
122
|
-
counts[
|
|
116
|
+
if any(x in line.lower() for x in [".md", "readme", "docs/", "documentation"]):
|
|
117
|
+
counts["doc_changes"] += 1
|
|
123
118
|
|
|
124
119
|
# Style changes (mostly whitespace/formatting)
|
|
125
|
-
if line.startswith(
|
|
126
|
-
if len(line.lstrip(
|
|
127
|
-
counts[
|
|
120
|
+
if line.startswith("+") or line.startswith("-"):
|
|
121
|
+
if len(line.lstrip("+-").strip()) < 10: # Short lines
|
|
122
|
+
counts["style_changes"] += 1
|
|
128
123
|
|
|
129
124
|
# Bug/fix references
|
|
130
|
-
if any(x in line.lower() for x in [
|
|
131
|
-
counts[
|
|
125
|
+
if any(x in line.lower() for x in ["fix", "bug", "issue", "closes #", "fixes #"]):
|
|
126
|
+
counts["bug_references"] += 1
|
|
132
127
|
|
|
133
128
|
# CI/CD changes
|
|
134
|
-
if
|
|
135
|
-
counts[
|
|
136
|
-
if
|
|
137
|
-
counts[
|
|
129
|
+
if ".github/workflows" in current_file or ".gitlab-ci" in current_file or "Jenkinsfile" in current_file:
|
|
130
|
+
counts["ci_changes"] += 1
|
|
131
|
+
if "pipeline" in line.lower() or "workflow" in line.lower():
|
|
132
|
+
counts["ci_changes"] += 1
|
|
138
133
|
|
|
139
134
|
# Refactoring (renaming, restructuring without functional change)
|
|
140
|
-
if any(x in line.lower() for x in [
|
|
141
|
-
counts[
|
|
135
|
+
if any(x in line.lower() for x in ["rename", "reorganize", "refactor", "restructure"]):
|
|
136
|
+
counts["refactor_changes"] += 1
|
|
142
137
|
|
|
143
138
|
return counts
|
|
144
139
|
|
|
@@ -157,74 +152,74 @@ def suggest_commit_type(diff_content: str) -> Tuple[str, float, str]:
|
|
|
157
152
|
|
|
158
153
|
# Scoring system: higher score = more confident
|
|
159
154
|
scores = {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
155
|
+
"feat": 0.0,
|
|
156
|
+
"fix": 0.0,
|
|
157
|
+
"docs": 0.0,
|
|
158
|
+
"style": 0.0,
|
|
159
|
+
"refactor": 0.0,
|
|
160
|
+
"perf": 0.0,
|
|
161
|
+
"test": 0.0,
|
|
162
|
+
"chore": 0.0,
|
|
163
|
+
"ci": 0.0,
|
|
164
|
+
"build": 0.0,
|
|
170
165
|
}
|
|
171
166
|
|
|
172
167
|
reasoning = []
|
|
173
168
|
|
|
174
169
|
# Test changes
|
|
175
|
-
if changes[
|
|
176
|
-
scores[
|
|
170
|
+
if changes["test_additions"] > 0:
|
|
171
|
+
scores["test"] += changes["test_additions"] * 2
|
|
177
172
|
reasoning.append(f"Added {changes['test_additions']} test lines")
|
|
178
173
|
|
|
179
174
|
# Documentation changes
|
|
180
|
-
if changes[
|
|
181
|
-
scores[
|
|
175
|
+
if changes["doc_changes"] > 0:
|
|
176
|
+
scores["docs"] += changes["doc_changes"] * 1.5
|
|
182
177
|
reasoning.append(f"Modified {changes['doc_changes']} documentation lines")
|
|
183
178
|
|
|
184
179
|
# Bug fixes
|
|
185
|
-
if changes[
|
|
186
|
-
scores[
|
|
180
|
+
if changes["bug_references"] > 0:
|
|
181
|
+
scores["fix"] += changes["bug_references"] * 3
|
|
187
182
|
reasoning.append(f"Found {changes['bug_references']} bug/fix references")
|
|
188
183
|
|
|
189
184
|
# Performance improvements
|
|
190
|
-
if changes[
|
|
191
|
-
scores[
|
|
185
|
+
if changes["performance_changes"] > 0:
|
|
186
|
+
scores["perf"] += changes["performance_changes"] * 2
|
|
192
187
|
reasoning.append(f"Found {changes['performance_changes']} performance improvements")
|
|
193
188
|
|
|
194
189
|
# CI/CD changes
|
|
195
|
-
if changes[
|
|
196
|
-
scores[
|
|
190
|
+
if changes["ci_changes"] > 0:
|
|
191
|
+
scores["ci"] += changes["ci_changes"] * 2
|
|
197
192
|
reasoning.append(f"Modified CI/CD files ({changes['ci_changes']} lines)")
|
|
198
193
|
|
|
199
194
|
# Dependency changes
|
|
200
|
-
if changes[
|
|
201
|
-
scores[
|
|
195
|
+
if changes["dependency_changes"] > 0:
|
|
196
|
+
scores["build"] += changes["dependency_changes"] * 1.5
|
|
202
197
|
reasoning.append(f"Modified dependencies ({changes['dependency_changes']} changes)")
|
|
203
198
|
|
|
204
199
|
# Feature additions
|
|
205
|
-
if changes[
|
|
206
|
-
feature_ratio = changes[
|
|
207
|
-
scores[
|
|
200
|
+
if changes["feature_additions"] > changes["feature_removals"]:
|
|
201
|
+
feature_ratio = changes["feature_additions"] / max(1, changes["feature_removals"])
|
|
202
|
+
scores["feat"] += feature_ratio * 2
|
|
208
203
|
reasoning.append(f"Added {changes['feature_additions']} feature lines")
|
|
209
204
|
|
|
210
205
|
# Refactoring
|
|
211
|
-
if changes[
|
|
212
|
-
scores[
|
|
206
|
+
if changes["refactor_changes"] > 0:
|
|
207
|
+
scores["refactor"] += changes["refactor_changes"] * 1.5
|
|
213
208
|
reasoning.append(f"Found {changes['refactor_changes']} refactoring indicators")
|
|
214
209
|
|
|
215
210
|
# Style changes
|
|
216
|
-
if changes[
|
|
217
|
-
scores[
|
|
211
|
+
if changes["style_changes"] > changes["total_additions"] * 0.3:
|
|
212
|
+
scores["style"] += changes["style_changes"]
|
|
218
213
|
reasoning.append(f"Mostly style changes ({changes['style_changes']} lines)")
|
|
219
214
|
|
|
220
215
|
# Fallback: determine based on change volume
|
|
221
|
-
total_changes = changes[
|
|
216
|
+
total_changes = changes["total_additions"] + changes["total_removals"]
|
|
222
217
|
if total_changes == 0:
|
|
223
|
-
return
|
|
218
|
+
return "chore", 0.5, "No meaningful changes detected"
|
|
224
219
|
|
|
225
220
|
# If nothing scored highly, use refactor as catch-all
|
|
226
221
|
if max(scores.values()) == 0:
|
|
227
|
-
scores[
|
|
222
|
+
scores["refactor"] = 1.0
|
|
228
223
|
reasoning.append("General code changes without specific category")
|
|
229
224
|
|
|
230
225
|
# Find the highest scoring type
|
|
@@ -237,27 +232,21 @@ def suggest_commit_type(diff_content: str) -> Tuple[str, float, str]:
|
|
|
237
232
|
return suggested, confidence, reasoning_str
|
|
238
233
|
|
|
239
234
|
|
|
240
|
-
def format_output(
|
|
241
|
-
suggested_type: str,
|
|
242
|
-
confidence: float,
|
|
243
|
-
reasoning: str,
|
|
244
|
-
format_type: str = 'text'
|
|
245
|
-
) -> str:
|
|
235
|
+
def format_output(suggested_type: str, confidence: float, reasoning: str, format_type: str = "text") -> str:
|
|
246
236
|
"""Format the output based on requested format."""
|
|
247
|
-
if format_type ==
|
|
248
|
-
return json.dumps(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
237
|
+
if format_type == "json":
|
|
238
|
+
return json.dumps(
|
|
239
|
+
{
|
|
240
|
+
"type": suggested_type,
|
|
241
|
+
"confidence": round(confidence, 2),
|
|
242
|
+
"confidence_percent": f"{confidence * 100:.1f}%",
|
|
243
|
+
"reasoning": reasoning,
|
|
244
|
+
},
|
|
245
|
+
indent=2,
|
|
246
|
+
)
|
|
254
247
|
else:
|
|
255
248
|
confidence_percent = f"{confidence * 100:.1f}%"
|
|
256
|
-
return
|
|
257
|
-
f"Suggested commit type: {suggested_type}\n"
|
|
258
|
-
f"Confidence: {confidence_percent}\n"
|
|
259
|
-
f"Reasoning: {reasoning}"
|
|
260
|
-
)
|
|
249
|
+
return f"Suggested commit type: {suggested_type}\nConfidence: {confidence_percent}\nReasoning: {reasoning}"
|
|
261
250
|
|
|
262
251
|
|
|
263
252
|
def main():
|
|
@@ -284,42 +273,17 @@ Examples:
|
|
|
284
273
|
|
|
285
274
|
# Verbose output with detailed analysis
|
|
286
275
|
%(prog)s --staged --verbose
|
|
287
|
-
"""
|
|
276
|
+
""",
|
|
288
277
|
)
|
|
289
278
|
|
|
290
279
|
input_group = parser.add_mutually_exclusive_group()
|
|
291
|
-
input_group.add_argument(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
help='Analyze staged changes (default)'
|
|
295
|
-
)
|
|
296
|
-
input_group.add_argument(
|
|
297
|
-
'--unstaged',
|
|
298
|
-
action='store_true',
|
|
299
|
-
help='Analyze unstaged changes'
|
|
300
|
-
)
|
|
301
|
-
input_group.add_argument(
|
|
302
|
-
'--file',
|
|
303
|
-
type=str,
|
|
304
|
-
help='Path to diff file to analyze'
|
|
305
|
-
)
|
|
280
|
+
input_group.add_argument("--staged", action="store_true", help="Analyze staged changes (default)")
|
|
281
|
+
input_group.add_argument("--unstaged", action="store_true", help="Analyze unstaged changes")
|
|
282
|
+
input_group.add_argument("--file", type=str, help="Path to diff file to analyze")
|
|
306
283
|
|
|
307
|
-
parser.add_argument(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
help='Git reference to compare against (e.g., main, HEAD~1)'
|
|
311
|
-
)
|
|
312
|
-
parser.add_argument(
|
|
313
|
-
'--format',
|
|
314
|
-
choices=['text', 'json'],
|
|
315
|
-
default='text',
|
|
316
|
-
help='Output format (default: text)'
|
|
317
|
-
)
|
|
318
|
-
parser.add_argument(
|
|
319
|
-
'-v', '--verbose',
|
|
320
|
-
action='store_true',
|
|
321
|
-
help='Enable verbose output with detailed analysis'
|
|
322
|
-
)
|
|
284
|
+
parser.add_argument("--ref", type=str, help="Git reference to compare against (e.g., main, HEAD~1)")
|
|
285
|
+
parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format (default: text)")
|
|
286
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output with detailed analysis")
|
|
323
287
|
|
|
324
288
|
args = parser.parse_args()
|
|
325
289
|
|
|
@@ -345,5 +309,5 @@ Examples:
|
|
|
345
309
|
return 0
|
|
346
310
|
|
|
347
311
|
|
|
348
|
-
if __name__ ==
|
|
312
|
+
if __name__ == "__main__":
|
|
349
313
|
sys.exit(main())
|
|
@@ -33,13 +33,13 @@ def validate_conventional_commit(message: str) -> Tuple[bool, str]:
|
|
|
33
33
|
return False, "Commit message cannot be empty"
|
|
34
34
|
|
|
35
35
|
message = message.strip()
|
|
36
|
-
lines = message.split(
|
|
36
|
+
lines = message.split("\n")
|
|
37
37
|
subject = lines[0]
|
|
38
38
|
|
|
39
39
|
# Validate subject line format
|
|
40
40
|
# Pattern: type(scope)?: subject or type: subject
|
|
41
41
|
# Where type is required, scope is optional, and subject is required
|
|
42
|
-
pattern = r
|
|
42
|
+
pattern = r"^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)(\(.+\))?!?: .{1,}$"
|
|
43
43
|
|
|
44
44
|
if not re.match(pattern, subject):
|
|
45
45
|
return False, (
|
|
@@ -52,17 +52,16 @@ def validate_conventional_commit(message: str) -> Tuple[bool, str]:
|
|
|
52
52
|
# Validate subject line length (recommended max 50 chars)
|
|
53
53
|
if len(subject) > 72:
|
|
54
54
|
return False, (
|
|
55
|
-
f"Subject line too long ({len(subject)} chars). "
|
|
56
|
-
"Recommended maximum is 50 characters, hard limit is 72"
|
|
55
|
+
f"Subject line too long ({len(subject)} chars). Recommended maximum is 50 characters, hard limit is 72"
|
|
57
56
|
)
|
|
58
57
|
|
|
59
58
|
# Validate subject doesn't end with period
|
|
60
|
-
if subject.endswith(
|
|
59
|
+
if subject.endswith("."):
|
|
61
60
|
return False, "Subject line should not end with a period"
|
|
62
61
|
|
|
63
62
|
# If there are multiple lines, validate blank line after subject
|
|
64
63
|
if len(lines) > 1:
|
|
65
|
-
if lines[1].strip() !=
|
|
64
|
+
if lines[1].strip() != "":
|
|
66
65
|
return False, "Expected blank line between subject and body"
|
|
67
66
|
|
|
68
67
|
return True, ""
|
|
@@ -83,34 +82,22 @@ Examples:
|
|
|
83
82
|
|
|
84
83
|
# Validate with verbose output
|
|
85
84
|
%(prog)s --message "fix: resolve bug" --verbose
|
|
86
|
-
"""
|
|
85
|
+
""",
|
|
87
86
|
)
|
|
88
87
|
|
|
89
88
|
# Create mutually exclusive group for input source
|
|
90
89
|
input_group = parser.add_mutually_exclusive_group(required=True)
|
|
91
|
-
input_group.add_argument(
|
|
92
|
-
|
|
93
|
-
type=str,
|
|
94
|
-
help='Commit message to validate'
|
|
95
|
-
)
|
|
96
|
-
input_group.add_argument(
|
|
97
|
-
'-f', '--file',
|
|
98
|
-
type=str,
|
|
99
|
-
help='Path to file containing commit message'
|
|
100
|
-
)
|
|
90
|
+
input_group.add_argument("-m", "--message", type=str, help="Commit message to validate")
|
|
91
|
+
input_group.add_argument("-f", "--file", type=str, help="Path to file containing commit message")
|
|
101
92
|
|
|
102
|
-
parser.add_argument(
|
|
103
|
-
'-v', '--verbose',
|
|
104
|
-
action='store_true',
|
|
105
|
-
help='Enable verbose output'
|
|
106
|
-
)
|
|
93
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
|
|
107
94
|
|
|
108
95
|
args = parser.parse_args()
|
|
109
96
|
|
|
110
97
|
# Get the commit message
|
|
111
98
|
if args.file:
|
|
112
99
|
try:
|
|
113
|
-
with open(args.file,
|
|
100
|
+
with open(args.file, "r", encoding="utf-8") as f:
|
|
114
101
|
message = f.read()
|
|
115
102
|
except FileNotFoundError:
|
|
116
103
|
print(f"Error: File not found: {args.file}", file=sys.stderr)
|
|
@@ -129,10 +116,10 @@ Examples:
|
|
|
129
116
|
print("✓ Commit message is valid")
|
|
130
117
|
return 0
|
|
131
118
|
else:
|
|
132
|
-
print(
|
|
119
|
+
print("✗ Validation failed:", file=sys.stderr)
|
|
133
120
|
print(error_msg, file=sys.stderr)
|
|
134
121
|
return 1
|
|
135
122
|
|
|
136
123
|
|
|
137
|
-
if __name__ ==
|
|
124
|
+
if __name__ == "__main__":
|
|
138
125
|
sys.exit(main())
|