Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:46:13 +08:00
commit 6005591708
7 changed files with 1125 additions and 0 deletions

View 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()

View 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()

View 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()