Initial commit
This commit is contained in:
172
skills/docs-and-changelogs/scripts/create_adr.py
Normal file
172
skills/docs-and-changelogs/scripts/create_adr.py
Normal file
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Create a new Architectural Decision Record (ADR).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def slugify(text):
|
||||
"""Convert text to lowercase slug format."""
|
||||
text = text.lower()
|
||||
text = re.sub(r'[^\w\s-]', '', text)
|
||||
text = re.sub(r'[-\s]+', '-', text)
|
||||
return text.strip('-')
|
||||
|
||||
|
||||
def get_next_adr_number(adr_dir):
|
||||
"""Get the next ADR number in sequence."""
|
||||
if not adr_dir.exists():
|
||||
return 1
|
||||
|
||||
existing = list(adr_dir.glob('*.md'))
|
||||
if not existing:
|
||||
return 1
|
||||
|
||||
numbers = []
|
||||
for path in existing:
|
||||
match = re.match(r'^(\d+)', path.name)
|
||||
if match:
|
||||
numbers.append(int(match.group(1)))
|
||||
|
||||
return max(numbers) + 1 if numbers else 1
|
||||
|
||||
|
||||
def create_adr(title, adr_dir='docs/ADR', status='Proposed'):
|
||||
"""Create a new ADR file."""
|
||||
adr_dir = Path(adr_dir)
|
||||
adr_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
number = get_next_adr_number(adr_dir)
|
||||
slug = slugify(title)
|
||||
filename = f"{number:04d}-{slug}.md"
|
||||
filepath = adr_dir / filename
|
||||
|
||||
date = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
content = f"""# ADR {number:04d}: {title}
|
||||
|
||||
**Status:** {status}
|
||||
|
||||
**Date:** {date}
|
||||
|
||||
**Deciders:** [List key decision makers]
|
||||
|
||||
## Context
|
||||
|
||||
[Describe the context and problem statement. What forces are at play? What are the constraints?]
|
||||
|
||||
### Problem
|
||||
|
||||
[What specific problem are we trying to solve?]
|
||||
|
||||
### Constraints
|
||||
|
||||
- [Constraint 1]
|
||||
- [Constraint 2]
|
||||
|
||||
## Decision
|
||||
|
||||
[Describe the decision we made and why we made it.]
|
||||
|
||||
### Rationale
|
||||
|
||||
[Explain the reasoning behind the decision. What factors influenced this choice?]
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- [Positive consequence 1]
|
||||
- [Positive consequence 2]
|
||||
|
||||
### Negative
|
||||
|
||||
- [Negative consequence 1]
|
||||
- [Negative consequence 2]
|
||||
|
||||
### Neutral
|
||||
|
||||
- [Neutral consequence 1]
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### Alternative 1: [Name]
|
||||
|
||||
**Description:** [Brief description]
|
||||
|
||||
**Pros:**
|
||||
- [Pro 1]
|
||||
|
||||
**Cons:**
|
||||
- [Con 1]
|
||||
|
||||
**Decision:** Rejected because [reason]
|
||||
|
||||
### Alternative 2: [Name]
|
||||
|
||||
**Description:** [Brief description]
|
||||
|
||||
**Pros:**
|
||||
- [Pro 1]
|
||||
|
||||
**Cons:**
|
||||
- [Con 1]
|
||||
|
||||
**Decision:** Rejected because [reason]
|
||||
|
||||
## Implementation
|
||||
|
||||
[How will this decision be implemented? What are the next steps?]
|
||||
|
||||
- [ ] Task 1
|
||||
- [ ] Task 2
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [Link to related ADR if applicable]
|
||||
|
||||
## References
|
||||
|
||||
- [Link to relevant documentation]
|
||||
- [Link to discussions or RFCs]
|
||||
|
||||
## Notes
|
||||
|
||||
[Any additional notes or context]
|
||||
"""
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"Created ADR: {filepath}")
|
||||
print(f"Number: {number:04d}")
|
||||
print(f"Status: {status}")
|
||||
|
||||
return filepath
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Create a new Architectural Decision Record')
|
||||
parser.add_argument('title', help='Title of the ADR')
|
||||
parser.add_argument('--dir', default='docs/ADR', help='ADR directory path')
|
||||
parser.add_argument('--status', default='Proposed',
|
||||
choices=['Proposed', 'Accepted', 'Rejected', 'Deprecated', 'Superseded'],
|
||||
help='Initial status of the ADR')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
filepath = create_adr(args.title, args.dir, args.status)
|
||||
|
||||
print("\nNext steps:")
|
||||
print(f"1. Edit {filepath} to complete all sections")
|
||||
print("2. Review with the team")
|
||||
print("3. Update status to 'Accepted' when finalized")
|
||||
print("4. Link from architecture documentation")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
353
skills/docs-and-changelogs/scripts/create_prd.py
Normal file
353
skills/docs-and-changelogs/scripts/create_prd.py
Normal file
@@ -0,0 +1,353 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Create a new Product Requirement Document (PRD).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def slugify(text):
|
||||
"""Convert text to lowercase slug format."""
|
||||
text = text.lower()
|
||||
text = re.sub(r'[^\w\s-]', '', text)
|
||||
text = re.sub(r'[-\s]+', '-', text)
|
||||
return text.strip('-')
|
||||
|
||||
|
||||
def create_prd(title, prd_dir='docs/PRD', author=''):
|
||||
"""Create a new PRD file."""
|
||||
prd_dir = Path(prd_dir)
|
||||
prd_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
slug = slugify(title)
|
||||
filename = f"{slug}.md"
|
||||
filepath = prd_dir / filename
|
||||
|
||||
date = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
content = f"""# Product Requirement Document: {title}
|
||||
|
||||
**Author:** {author or '[Your Name]'}
|
||||
|
||||
**Date:** {date}
|
||||
|
||||
**Status:** Draft
|
||||
|
||||
**Last Updated:** {date}
|
||||
|
||||
## Executive Summary
|
||||
|
||||
[Brief 2-3 sentence overview of what this feature is and why it matters]
|
||||
|
||||
## Problem Statement
|
||||
|
||||
### Current Situation
|
||||
|
||||
[Describe the current state and what pain points exist]
|
||||
|
||||
### User Impact
|
||||
|
||||
[Who is affected by this problem and how?]
|
||||
|
||||
### Business Impact
|
||||
|
||||
[What is the business cost of not solving this problem?]
|
||||
|
||||
## Goals and Objectives
|
||||
|
||||
### Primary Goals
|
||||
|
||||
1. [Goal 1]
|
||||
2. [Goal 2]
|
||||
3. [Goal 3]
|
||||
|
||||
### Success Metrics
|
||||
|
||||
- [Metric 1]: [Target]
|
||||
- [Metric 2]: [Target]
|
||||
- [Metric 3]: [Target]
|
||||
|
||||
### Non-Goals
|
||||
|
||||
[What is explicitly out of scope for this feature?]
|
||||
|
||||
- [Non-goal 1]
|
||||
- [Non-goal 2]
|
||||
|
||||
## User Stories
|
||||
|
||||
### Story 1: [User Type]
|
||||
|
||||
**As a** [user type]
|
||||
**I want to** [action]
|
||||
**So that** [benefit]
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] [Criterion 1]
|
||||
- [ ] [Criterion 2]
|
||||
- [ ] [Criterion 3]
|
||||
|
||||
### Story 2: [User Type]
|
||||
|
||||
**As a** [user type]
|
||||
**I want to** [action]
|
||||
**So that** [benefit]
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] [Criterion 1]
|
||||
- [ ] [Criterion 2]
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
#### Must Have (P0)
|
||||
|
||||
1. **[Requirement 1]**
|
||||
- Description: [Details]
|
||||
- User Impact: [High/Medium/Low]
|
||||
|
||||
2. **[Requirement 2]**
|
||||
- Description: [Details]
|
||||
- User Impact: [High/Medium/Low]
|
||||
|
||||
#### Should Have (P1)
|
||||
|
||||
1. **[Requirement 3]**
|
||||
- Description: [Details]
|
||||
- User Impact: [High/Medium/Low]
|
||||
|
||||
#### Nice to Have (P2)
|
||||
|
||||
1. **[Requirement 4]**
|
||||
- Description: [Details]
|
||||
- User Impact: [High/Medium/Low]
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
#### Performance
|
||||
|
||||
- [Performance requirement 1]
|
||||
- [Performance requirement 2]
|
||||
|
||||
#### Security
|
||||
|
||||
- [Security requirement 1]
|
||||
- [Security requirement 2]
|
||||
|
||||
#### Accessibility
|
||||
|
||||
- [Accessibility requirement 1]
|
||||
- [Accessibility requirement 2]
|
||||
|
||||
#### Scalability
|
||||
|
||||
- [Scalability requirement 1]
|
||||
- [Scalability requirement 2]
|
||||
|
||||
## Design Considerations
|
||||
|
||||
### User Experience
|
||||
|
||||
[Describe the intended user experience]
|
||||
|
||||
#### User Flow
|
||||
|
||||
1. [Step 1]
|
||||
2. [Step 2]
|
||||
3. [Step 3]
|
||||
|
||||
#### UI Components
|
||||
|
||||
- [Component 1]: [Description]
|
||||
- [Component 2]: [Description]
|
||||
|
||||
### Technical Architecture
|
||||
|
||||
[High-level technical approach]
|
||||
|
||||
#### Components
|
||||
|
||||
- **[Component 1]**: [Purpose and responsibilities]
|
||||
- **[Component 2]**: [Purpose and responsibilities]
|
||||
|
||||
#### Data Model
|
||||
|
||||
[Overview of data structures and relationships]
|
||||
|
||||
#### APIs
|
||||
|
||||
- **[Endpoint 1]**: [Purpose]
|
||||
- **[Endpoint 2]**: [Purpose]
|
||||
|
||||
### Integration Points
|
||||
|
||||
- [System/Service 1]: [How it integrates]
|
||||
- [System/Service 2]: [How it integrates]
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Internal Dependencies
|
||||
|
||||
- [Dependency 1]: [Why needed]
|
||||
- [Dependency 2]: [Why needed]
|
||||
|
||||
### External Dependencies
|
||||
|
||||
- [Third-party service 1]: [Purpose]
|
||||
- [Third-party service 2]: [Purpose]
|
||||
|
||||
### Blocking Issues
|
||||
|
||||
- [Issue 1]: [Resolution plan]
|
||||
- [Issue 2]: [Resolution plan]
|
||||
|
||||
## Risks and Mitigation
|
||||
|
||||
| Risk | Impact | Likelihood | Mitigation Strategy |
|
||||
|------|--------|------------|---------------------|
|
||||
| [Risk 1] | High/Medium/Low | High/Medium/Low | [Strategy] |
|
||||
| [Risk 2] | High/Medium/Low | High/Medium/Low | [Strategy] |
|
||||
|
||||
## Timeline and Milestones
|
||||
|
||||
### Phase 1: [Name] ([Duration])
|
||||
|
||||
- [ ] [Milestone 1]
|
||||
- [ ] [Milestone 2]
|
||||
|
||||
### Phase 2: [Name] ([Duration])
|
||||
|
||||
- [ ] [Milestone 3]
|
||||
- [ ] [Milestone 4]
|
||||
|
||||
### Phase 3: [Name] ([Duration])
|
||||
|
||||
- [ ] [Milestone 5]
|
||||
- [ ] [Milestone 6]
|
||||
|
||||
### Target Launch Date
|
||||
|
||||
[Date or timeframe]
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Testing
|
||||
|
||||
[Approach to unit testing]
|
||||
|
||||
### Integration Testing
|
||||
|
||||
[Approach to integration testing]
|
||||
|
||||
### User Acceptance Testing
|
||||
|
||||
[UAT plan and criteria]
|
||||
|
||||
### Performance Testing
|
||||
|
||||
[Performance testing approach]
|
||||
|
||||
## Launch Plan
|
||||
|
||||
### Pre-Launch Checklist
|
||||
|
||||
- [ ] [Item 1]
|
||||
- [ ] [Item 2]
|
||||
- [ ] [Item 3]
|
||||
|
||||
### Rollout Strategy
|
||||
|
||||
[Describe how the feature will be rolled out]
|
||||
|
||||
- **Audience**: [Who gets access first]
|
||||
- **Timeline**: [Phased rollout schedule]
|
||||
- **Monitoring**: [What metrics to watch]
|
||||
|
||||
### Communication Plan
|
||||
|
||||
- **Internal**: [How to communicate to team]
|
||||
- **External**: [How to communicate to users]
|
||||
|
||||
### Rollback Plan
|
||||
|
||||
[How to rollback if issues arise]
|
||||
|
||||
## Post-Launch
|
||||
|
||||
### Monitoring
|
||||
|
||||
- [Metric to monitor 1]
|
||||
- [Metric to monitor 2]
|
||||
|
||||
### Iteration Plan
|
||||
|
||||
[How we'll iterate based on feedback]
|
||||
|
||||
### Success Evaluation
|
||||
|
||||
[How and when we'll evaluate if we met our goals]
|
||||
|
||||
## Open Questions
|
||||
|
||||
- [ ] [Question 1]
|
||||
- [ ] [Question 2]
|
||||
- [ ] [Question 3]
|
||||
|
||||
## Stakeholders
|
||||
|
||||
| Name | Role | Responsibility |
|
||||
|------|------|----------------|
|
||||
| [Name] | [Role] | [What they're responsible for] |
|
||||
| [Name] | [Role] | [What they're responsible for] |
|
||||
|
||||
## References
|
||||
|
||||
- [Link to related PRDs]
|
||||
- [Link to design mockups]
|
||||
- [Link to technical specs]
|
||||
- [Link to user research]
|
||||
|
||||
## Appendix
|
||||
|
||||
### Terminology
|
||||
|
||||
- **[Term 1]**: [Definition]
|
||||
- **[Term 2]**: [Definition]
|
||||
|
||||
### Additional Resources
|
||||
|
||||
- [Resource 1]
|
||||
- [Resource 2]
|
||||
"""
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"Created PRD: {filepath}")
|
||||
|
||||
return filepath
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Create a new Product Requirement Document')
|
||||
parser.add_argument('title', help='Title of the PRD')
|
||||
parser.add_argument('--dir', default='docs/PRD', help='PRD directory path')
|
||||
parser.add_argument('--author', help='Document author name')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
filepath = create_prd(args.title, args.dir, args.author or '')
|
||||
|
||||
print("\nNext steps:")
|
||||
print(f"1. Edit {filepath} to complete all sections")
|
||||
print("2. Share with stakeholders for review")
|
||||
print("3. Refine based on feedback")
|
||||
print("4. Get final approval before implementation")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
243
skills/docs-and-changelogs/scripts/generate_changelog.py
Normal file
243
skills/docs-and-changelogs/scripts/generate_changelog.py
Normal file
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate CHANGELOG.md from Conventional Commits in git history.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
COMMIT_PATTERN = re.compile(
|
||||
r'^(?P<type>\w+)(?:\((?P<scope>[\w-]+)\))?(?P<breaking>!)?: (?P<description>.+)$'
|
||||
)
|
||||
|
||||
TYPE_HEADERS = {
|
||||
'feat': 'Added',
|
||||
'fix': 'Fixed',
|
||||
'docs': 'Documentation',
|
||||
'style': 'Style',
|
||||
'refactor': 'Changed',
|
||||
'perf': 'Performance',
|
||||
'test': 'Tests',
|
||||
'chore': 'Maintenance',
|
||||
'ci': 'CI/CD',
|
||||
'build': 'Build',
|
||||
'revert': 'Reverted',
|
||||
}
|
||||
|
||||
|
||||
def get_commits(since=None, until='HEAD'):
|
||||
"""Get git commits in range."""
|
||||
cmd = ['git', 'log', '--pretty=format:%H|%s|%b|%an|%ae|%ad', '--date=short']
|
||||
|
||||
if since:
|
||||
cmd.append(f'{since}..{until}')
|
||||
else:
|
||||
cmd.append(until)
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
commits = []
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split('|')
|
||||
if len(parts) >= 6:
|
||||
commits.append({
|
||||
'hash': parts[0],
|
||||
'subject': parts[1],
|
||||
'body': parts[2],
|
||||
'author': parts[3],
|
||||
'email': parts[4],
|
||||
'date': parts[5],
|
||||
})
|
||||
|
||||
return commits
|
||||
|
||||
|
||||
def parse_commit(commit):
|
||||
"""Parse conventional commit message."""
|
||||
match = COMMIT_PATTERN.match(commit['subject'])
|
||||
|
||||
if not match:
|
||||
return None
|
||||
|
||||
parsed = {
|
||||
'type': match.group('type'),
|
||||
'scope': match.group('scope'),
|
||||
'breaking': bool(match.group('breaking')),
|
||||
'description': match.group('description'),
|
||||
'hash': commit['hash'][:7],
|
||||
'full_hash': commit['hash'],
|
||||
'body': commit['body'],
|
||||
}
|
||||
|
||||
# Check for BREAKING CHANGE in body
|
||||
if 'BREAKING CHANGE' in commit['body']:
|
||||
parsed['breaking'] = True
|
||||
# Extract breaking change description
|
||||
breaking_match = re.search(r'BREAKING CHANGE:\s*(.+)', commit['body'])
|
||||
if breaking_match:
|
||||
parsed['breaking_description'] = breaking_match.group(1)
|
||||
|
||||
return parsed
|
||||
|
||||
|
||||
def group_commits(commits):
|
||||
"""Group commits by type and breaking changes."""
|
||||
groups = defaultdict(list)
|
||||
breaking = []
|
||||
|
||||
for commit in commits:
|
||||
parsed = parse_commit(commit)
|
||||
if not parsed:
|
||||
continue
|
||||
|
||||
if parsed['breaking']:
|
||||
breaking.append(parsed)
|
||||
|
||||
commit_type = parsed['type']
|
||||
groups[commit_type].append(parsed)
|
||||
|
||||
return groups, breaking
|
||||
|
||||
|
||||
def format_commit(commit):
|
||||
"""Format a single commit for changelog."""
|
||||
scope = f"**{commit['scope']}**: " if commit['scope'] else ""
|
||||
return f"- {scope}{commit['description']} ([{commit['hash']}](../../commit/{commit['full_hash']}))"
|
||||
|
||||
|
||||
def generate_changelog_section(version, date, groups, breaking):
|
||||
"""Generate a changelog section for a version."""
|
||||
lines = [
|
||||
f"## [{version}] - {date}",
|
||||
""
|
||||
]
|
||||
|
||||
# Breaking changes first
|
||||
if breaking:
|
||||
lines.append("### BREAKING CHANGES")
|
||||
lines.append("")
|
||||
for commit in breaking:
|
||||
if 'breaking_description' in commit:
|
||||
lines.append(f"- {commit['breaking_description']}")
|
||||
else:
|
||||
lines.append(format_commit(commit))
|
||||
lines.append("")
|
||||
|
||||
# Regular changes by type
|
||||
for commit_type, header in TYPE_HEADERS.items():
|
||||
if commit_type in groups and groups[commit_type]:
|
||||
lines.append(f"### {header}")
|
||||
lines.append("")
|
||||
for commit in groups[commit_type]:
|
||||
lines.append(format_commit(commit))
|
||||
lines.append("")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def get_current_version():
|
||||
"""Get current version from package.json."""
|
||||
try:
|
||||
import json
|
||||
with open('package.json') as f:
|
||||
data = json.load(f)
|
||||
return data.get('version', '0.0.0')
|
||||
except:
|
||||
return '0.0.0'
|
||||
|
||||
|
||||
def get_last_tag():
|
||||
"""Get the most recent git tag."""
|
||||
result = subprocess.run(
|
||||
['git', 'describe', '--tags', '--abbrev=0'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
return None
|
||||
|
||||
|
||||
def update_changelog(content, output_path):
|
||||
"""Update or create CHANGELOG.md file."""
|
||||
changelog_path = Path(output_path)
|
||||
|
||||
if changelog_path.exists():
|
||||
with open(changelog_path) as f:
|
||||
existing = f.read()
|
||||
|
||||
# Insert new content after header
|
||||
if '## [' in existing:
|
||||
parts = existing.split('## [', 1)
|
||||
updated = parts[0] + content + '\n## [' + parts[1]
|
||||
else:
|
||||
updated = existing + '\n\n' + content
|
||||
else:
|
||||
# Create new changelog with header
|
||||
header = """# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
"""
|
||||
updated = header + content
|
||||
|
||||
with open(changelog_path, 'w') as f:
|
||||
f.write(updated)
|
||||
|
||||
print(f"Updated: {changelog_path}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Generate changelog from git commits')
|
||||
parser.add_argument('--since', help='Start from this tag/commit')
|
||||
parser.add_argument('--until', default='HEAD', help='End at this tag/commit')
|
||||
parser.add_argument('--version', help='Version number for this release')
|
||||
parser.add_argument('--output', default='CHANGELOG.md', help='Output file path')
|
||||
parser.add_argument('--date', help='Release date (YYYY-MM-DD)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Determine version and since
|
||||
version = args.version or get_current_version()
|
||||
since = args.since or get_last_tag()
|
||||
|
||||
if not since:
|
||||
print("Warning: No previous tag found. Generating changelog for all commits.")
|
||||
|
||||
# Get and parse commits
|
||||
commits = get_commits(since=since, until=args.until)
|
||||
|
||||
if not commits:
|
||||
print("No commits found in range.")
|
||||
return
|
||||
|
||||
groups, breaking = group_commits(commits)
|
||||
|
||||
# Generate changelog section
|
||||
date = args.date or datetime.now().strftime('%Y-%m-%d')
|
||||
changelog_content = generate_changelog_section(version, date, groups, breaking)
|
||||
|
||||
# Update changelog file
|
||||
update_changelog(changelog_content, args.output)
|
||||
|
||||
# Print summary
|
||||
print(f"\nGenerated changelog for version {version}")
|
||||
print(f" Commits processed: {len(commits)}")
|
||||
print(f" Breaking changes: {len(breaking)}")
|
||||
print(f" Features: {len(groups.get('feat', []))}")
|
||||
print(f" Bug fixes: {len(groups.get('fix', []))}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user