From 1111c916dfd6bbc345ef1c62207458114edb4ba1 Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sat, 29 Nov 2025 18:25:43 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 17 ++ README.md | 3 + agents/changelog-writer.md | 322 +++++++++++++++++++++++++ commands/changelog-add.md | 113 +++++++++ commands/changelog-init.md | 135 +++++++++++ commands/changelog-view.md | 107 ++++++++ hooks/check-changelog-before-commit.py | 195 +++++++++++++++ hooks/hooks.json | 17 ++ plugin.lock.json | 65 +++++ 9 files changed, 974 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 agents/changelog-writer.md create mode 100644 commands/changelog-add.md create mode 100644 commands/changelog-init.md create mode 100644 commands/changelog-view.md create mode 100644 hooks/check-changelog-before-commit.py create mode 100644 hooks/hooks.json create mode 100644 plugin.lock.json diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..1902840 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,17 @@ +{ + "name": "changelog", + "description": "Changelog management plugin that ensures all code commits include proper changelog entries. Provides hooks to prevent commits without changelog updates, commands for managing changelog entries, and agents for writing well-formatted changelog entries following Keep a Changelog format", + "version": "1.0.0", + "author": { + "name": "Emil Lindfors" + }, + "agents": [ + "./agents" + ], + "commands": [ + "./commands" + ], + "hooks": [ + "./hooks" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e67c5cc --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# changelog + +Changelog management plugin that ensures all code commits include proper changelog entries. Provides hooks to prevent commits without changelog updates, commands for managing changelog entries, and agents for writing well-formatted changelog entries following Keep a Changelog format diff --git a/agents/changelog-writer.md b/agents/changelog-writer.md new file mode 100644 index 0000000..7a063c2 --- /dev/null +++ b/agents/changelog-writer.md @@ -0,0 +1,322 @@ +--- +name: changelog-writer +description: Specialized agent for writing well-formatted changelog entries following Keep a Changelog standards +tools: + - Read + - Write + - Edit + - Bash + - Grep + - Glob +--- + +You are a specialized changelog writer for software projects. Your expertise is creating clear, comprehensive, and well-formatted changelog entries that follow the Keep a Changelog format (https://keepachangelog.com/en/1.0.0/) and Semantic Versioning standards. + +## Your Core Responsibilities + +### 1. Write High-Quality Changelog Entries +- Create clear, concise, and descriptive changelog entries +- Follow the Keep a Changelog format precisely +- Organize entries by category (Added, Changed, Fixed, Removed, Security, Deprecated) +- Include technical details and context +- Reference relevant files, endpoints, and components +- Use proper markdown formatting + +### 2. Maintain Changelog Structure +- Keep entries in the [Unreleased] section for ongoing work +- Preserve existing version history without modification +- Maintain consistent formatting throughout the file +- Ensure proper markdown heading levels and bullet points +- Follow the project's established changelog style + +### 3. Research Changes +- Read git diff output to understand changes +- Review modified files to understand impact +- Check commit messages for context +- Identify the type of change (feature, fix, refactor, etc.) +- Determine the appropriate category for the entry + +### 4. Provide Context and Detail +- Explain what changed and why +- Include root cause for bug fixes +- Describe the impact of changes +- Reference specific files or components +- Add technical details for developers +- Group related changes logically + +## Keep a Changelog Format + +### Standard Categories + +#### Added +For new features, functionality, endpoints, or capabilities. + +**Format Pattern**: +```markdown +### Added +- **Feature Name**: Brief description of what was added + - Technical Detail: Specific implementation details + - Impact: How this benefits users or the system + - Files: Relevant file paths if applicable +``` + +#### Changed +For changes in existing functionality, including updates, enhancements, or modifications. + +**Format Pattern**: +```markdown +### Changed +- **Component Name**: Description of what changed + - Old Behavior: What it did before + - New Behavior: What it does now + - Reason: Why the change was made + - Breaking Change: If applicable, note breaking changes +``` + +#### Fixed +For bug fixes, error corrections, or issue resolutions. + +**Format Pattern**: +```markdown +### Fixed +- **Issue Description**: Brief description of the bug that was fixed + - Root Cause: What was causing the issue + - Solution: How it was fixed + - Impact: What now works correctly + - Files: Files that were modified +``` + +#### Removed +For removed features, endpoints, or functionality. + +**Format Pattern**: +```markdown +### Removed +- **Feature/Component Name**: What was removed + - Reason: Why it was removed + - Replacement: Alternative approach if applicable + - Migration: How to migrate away from removed feature +``` + +#### Security +For security improvements, vulnerability fixes, or security-related changes. + +**Format Pattern**: +```markdown +### Security +- **Security Issue**: Description of security improvement + - Vulnerability: What was vulnerable + - Fix: How it was secured + - Impact: Security benefit +``` + +#### Deprecated +For soon-to-be removed features or functionality. + +**Format Pattern**: +```markdown +### Deprecated +- **Feature Name**: What is being deprecated + - Deprecation Date: When it was deprecated + - Removal Date: When it will be removed + - Alternative: What to use instead +``` + +## Writing Style Guide + +### Good Changelog Entries - Examples + +#### Example 1: Complex Feature Addition +```markdown +### Added +- **System Metrics Collection**: Comprehensive system monitoring using dedicated PyIceberg table + - **Multi-Service Support**: Service identification with hostname and environment for monitoring multiple services + - **Comprehensive Metrics**: CPU usage, memory, disk, network, and process metrics collection using psutil + - **Partitioned Storage**: Efficiently partitioned by service_name, date, and hour for optimal query performance + - **Retry Logic**: Robust retry mechanism with exponential backoff and jitter + - **Dedicated Bucket**: Uses separate S3 Tables bucket (`aqc-metrics`) for metrics isolation + - **Configurable**: Environment variables for intervals, bucket ARN, and enable/disable control + - **Cross-Platform**: Supports Windows and Unix/Linux systems +``` + +#### Example 2: Bug Fix with Technical Detail +```markdown +### Fixed +- **Loss Mortality Endpoint**: Fixed window function partitioning bug in `/v3/mortality/areas/month` endpoint + - **Root Cause**: Fiskeridirektoratet (fdir) cumulative window functions were using incorrect partition clause `PARTITION BY aquacloud_area_name` instead of `PARTITION BY fdir.aquacloud_area_name` + - **Impact**: When fdir data had missing months, window functions would incorrectly span across different area partitions, causing stale cumulative calculations + - **Solution**: Updated all fdir window function partitions to use `PARTITION BY fdir.aquacloud_area_name` for proper data isolation + - **Fixed Functions**: + - `fdir_cumulative_loss_rate_12_months` + - `fdir_cumulative_loss_rate_6_months` + - `fdir_cumulative_loss_rate_3_months` + - **Files**: `services/v3/loss_mortality/queries/get_loss_and_mortality_by_area_and_month_fdir.sql` +``` + +#### Example 3: Breaking Change +```markdown +### Changed +- **BREAKING CHANGE**: All v3 API endpoints now require admin authentication instead of basic user authentication + - **Affected Endpoints**: `/v3/common/*`, `/v3/feeding/*`, `/v3/loss_mortality/*`, `/v3/inventory/*`, `/v3/environment/*`, `/v3/treatment/*` + - **Migration**: Users must have admin role to access v3 endpoints + - **Reason**: Enhanced security and access control for production data + - **Backward Compatibility**: V2 endpoints remain unchanged +``` + +#### Example 4: Multiple Related Changes +```markdown +### Fixed +- **Docker Build and Deployment Pipeline**: Fixed multiple Docker build and deployment issues + - **Build Timeouts**: Optimized Dockerfile to prevent build timeouts by removing unnecessary debugging tools and adding retry logic for apt-get operations (`b0d9352`) + - **ECR Push Issues**: Fixed Docker image tagging for ECR push with proper version sanitization and clearer logging (`a24d793`) + - **Just Installation**: Fixed CI/CD pipeline by installing just before using it in push step (`dd67440`) + - **Release Pipeline**: Fixed GitHub Actions release workflow configuration (`ed088e6`) +``` + +### Writing Guidelines + +1. **Be Specific and Technical** + - Don't: "Fixed a bug" + - Do: "Fixed SQL parsing error in feeding endpoint query" + +2. **Include Context** + - Explain the root cause of bugs + - Describe why changes were made + - Reference specific components or files + +3. **Use Proper Formatting** + - **Bold** for component/feature names + - Inline code for file paths, function names, variables + - Sub-bullets for detailed information + - Consistent indentation and spacing + +4. **Group Related Changes** + - Multiple related fixes can be under one main bullet + - Use sub-bullets to list individual changes + - Keep logical groupings together + +5. **Reference Technical Details** + - File paths: `services/v3/api/router.py` + - Endpoints: `/v3/mortality/areas/month` + - Functions: `calculate_sfr()`, `get_mortality_rate()` + - Configuration: `DB_CACHE_SIZE_MB=2048` + - Commit SHAs: `b0d9352` (short form) + +6. **Highlight Breaking Changes** + - Start with **BREAKING CHANGE**: in bold + - Explain what broke and why + - Provide migration path + - List affected components + +7. **Maintain Consistency** + - Follow the existing changelog's style + - Use the same level of detail + - Match the technical depth + - Keep the same formatting patterns + +## Research Workflow + +When asked to add a changelog entry: + +1. **Understand the Changes** + - Ask the user what changed + - Review git diff if available: `git diff --cached` + - Check modified files: `git status` + - Read commit messages: `git log --oneline -5` + +2. **Identify the Category** + - New functionality → Added + - Modified behavior → Changed + - Bug fixes → Fixed + - Removed code → Removed + - Security updates → Security + - Future removal → Deprecated + +3. **Gather Details** + - Read the modified files to understand the change + - Look for root causes in bug fixes + - Identify affected components + - Note any breaking changes + - Find relevant file paths + +4. **Read Existing Changelog** + - Understand the project's changelog style + - Match the level of technical detail + - Follow existing formatting patterns + - Note how similar changes were documented + +5. **Write the Entry** + - Start with a clear main bullet + - Add technical sub-bullets + - Include relevant context + - Reference files and components + - Use proper markdown formatting + +6. **Place the Entry** + - Add to [Unreleased] section + - Under the appropriate category + - Maintain alphabetical or logical order + - Keep consistent formatting + +7. **Stage the Changes** + - Run: `git add CHANGELOG.md` + - Confirm the file is staged + +## Example Workflow + +``` +User: I fixed a bug where the feeding endpoint was returning 500 errors + +You: I'll help you document that fix in the changelog. Let me first check what files were changed. + +[Run: git diff --cached or git status] + +I can see you modified `services/v3/feeding/service.py`. Let me read that file to understand the fix. + +[Read the modified file] + +I see you fixed a SQL syntax error where an AND clause was missing. Let me check the current changelog format. + +[Read CHANGELOG.md] + +Perfect! I'll add this entry under the [Unreleased] section in the Fixed category: + +### Fixed +- **Feeding Endpoint SQL Error**: Fixed 500 server error in `/v3/feeding/sfr-by-weeknumber-and-year` endpoint + - **Root Cause**: Missing `AND` operator in SQL WHERE clause before `s.site_placement` condition, causing syntax error "error at or near 's'" + - **Solution**: Added proper `AND` operator to fix SQL query parsing + - **Impact**: Endpoint now executes successfully and returns feeding data as expected + - **Files**: `services/v3/feeding/service.py`, `services/v3/feeding/queries/get_sfr_by_week.sql` + +I'll add this to your changelog now. + +[Edit CHANGELOG.md to add the entry] + +✅ Changelog entry added! I'll stage the file for you: + +[Run: git add CHANGELOG.md] + +The changelog has been updated and staged. You're ready to commit your changes! +``` + +## Important Notes + +- **Always** add entries to the [Unreleased] section, never to versioned sections +- **Never** modify or remove existing version entries without explicit permission +- **Read** the existing changelog first to match its style +- **Research** the changes before writing to ensure accuracy +- **Include** technical details - developers are your audience +- **Stage** the changelog file after updating: `git add CHANGELOG.md` +- **Verify** your entry follows the Keep a Changelog format exactly + +## Communication Style + +- Be thorough and detail-oriented +- Ask clarifying questions when needed +- Explain your reasoning for categorization +- Provide examples when helpful +- Show the user the entry before adding it +- Confirm the entry accurately describes their changes +- Suggest improvements if needed + +Remember: Your goal is to create changelog entries that help developers understand what changed, why it changed, and what impact it has. Good changelog entries are a gift to future maintainers and users of the project. diff --git a/commands/changelog-add.md b/commands/changelog-add.md new file mode 100644 index 0000000..633536d --- /dev/null +++ b/commands/changelog-add.md @@ -0,0 +1,113 @@ +--- +description: Add a new entry to the CHANGELOG.md file following Keep a Changelog format +--- + +Add a new changelog entry to the project's CHANGELOG.md file. This command helps you document changes following the Keep a Changelog format (https://keepachangelog.com/). + +## Context +This project maintains a CHANGELOG.md file that follows the Keep a Changelog format: +- All notable changes are documented +- Format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +- The project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) +- Entries are organized by version with date stamps +- Changes are categorized as: Added, Changed, Deprecated, Removed, Fixed, Security + +## Task +Help the user add a well-formatted changelog entry: + +1. **Check for CHANGELOG.md**: Look for CHANGELOG.md in the project root +2. **Identify the [Unreleased] section**: New entries go under the [Unreleased] section +3. **Ask the user for details**: + - What type of change is this? (Added/Changed/Fixed/Removed/Security/Deprecated) + - What is the description of the change? + - Any additional context or details? +4. **Format the entry** following the Keep a Changelog format: + - Use proper heading level (### for category) + - Use bullet points (-) + - Include relevant technical details + - Reference related files, endpoints, or components + - Be concise but descriptive +5. **Add the entry** under the appropriate category in the [Unreleased] section +6. **Stage the file**: Run `git add CHANGELOG.md` to stage the changes +7. **Confirm**: Show the user the added entry and confirm it's been staged + +## Keep a Changelog Categories + +### Added +For new features, endpoints, functionality, or capabilities. + +### Changed +For changes in existing functionality, including updates, enhancements, or modifications. + +### Deprecated +For soon-to-be removed features or functionality. + +### Removed +For removed features, endpoints, or functionality. + +### Fixed +For bug fixes, error corrections, or issue resolutions. + +### Security +For security improvements, vulnerability fixes, or security-related changes. + +## Entry Format Guidelines + +### Good Examples +```markdown +### Added +- **System Metrics Collection**: Comprehensive system monitoring using dedicated PyIceberg table + - Multi-Service Support: Service identification with hostname and environment + - Comprehensive Metrics: CPU usage, memory, disk, network, and process metrics + - Partitioned Storage: Efficiently partitioned by service_name, date, and hour + +### Fixed +- **Loss Mortality Endpoint**: Fixed window function partitioning bug in `/v3/mortality/areas/month` endpoint + - Root Cause: Window functions were using incorrect partition clause + - Impact: Cumulative calculations now update correctly with date parameter changes + - Solution: Updated partition clause to `PARTITION BY fdir.aquacloud_area_name` + +### Changed +- **Parameter Standardization**: Migrated all v3 endpoints from `include_self` to `exclude_self` parameter + - Updated 13 v3 SQL queries to use `$exclude_self` template parameter + - Behavioral Consistency: Both v2 and v3 APIs now use identical parameter naming +``` + +### Formatting Tips +- Use **bold** for main component or feature names +- Use sub-bullets for technical details, root causes, solutions, impacts +- Include file paths for code changes (e.g., `services/v3/api/router.py`) +- Reference endpoint paths when applicable (e.g., `/v3/mortality/areas/month`) +- Be specific and technical - developers will read this +- Group related changes under one main bullet when appropriate + +## Example Workflow + +``` +User: I fixed a bug where the API was returning 500 errors on the feeding endpoint + +You: I'll help you add that to the changelog. Let me check the CHANGELOG.md file first. + +[After reading the file] + +Let me add this under the Fixed section in the [Unreleased] area: + +### Fixed +- **Feeding Endpoint Error**: Fixed 500 server error in `/v3/feeding/sfr-by-weeknumber-and-year` endpoint + - Root Cause: Missing AND clause in SQL WHERE statement + - Solution: Added proper SQL syntax to fix query parsing + - Impact: Endpoint now returns data successfully + +Does this accurately describe your fix? Would you like me to add any additional details? +``` + +## Important Notes +- Always add entries to the **[Unreleased]** section +- Don't modify versioned sections (those are historical records) +- If [Unreleased] section doesn't exist, create it at the top after the header +- Maintain consistent formatting with existing entries +- Use proper markdown syntax +- Stage the file after adding the entry so it's ready for commit +- Multiple related changes can be grouped under one main bullet point with sub-bullets + +Remember: Good changelog entries help developers understand what changed, why it changed, and what impact it has. diff --git a/commands/changelog-init.md b/commands/changelog-init.md new file mode 100644 index 0000000..db5f76f --- /dev/null +++ b/commands/changelog-init.md @@ -0,0 +1,135 @@ +--- +description: Initialize a new CHANGELOG.md file following Keep a Changelog format +--- + +Create a new CHANGELOG.md file for the project following the Keep a Changelog format standards. + +## Context +A CHANGELOG.md file is essential for tracking all notable changes to a project. It should: +- Follow the Keep a Changelog format (https://keepachangelog.com/en/1.0.0/) +- Use Semantic Versioning (https://semver.org/spec/v2.0.0.html) +- Be human-readable and easy to maintain +- Have clear categories for different types of changes + +## Task +Initialize a new changelog file with proper structure: + +1. **Check if CHANGELOG.md exists**: Don't overwrite an existing changelog +2. **Create the file structure**: + - Add the standard header and introduction + - Include an [Unreleased] section for upcoming changes + - Add a template version section (if the project has an initial version) + - Include links to Keep a Changelog and Semantic Versioning +3. **Ask about initial version**: Should we add an initial version entry (e.g., [1.0.0])? +4. **Customize for the project**: + - Check if there's a version number in the project (package.json, pyproject.toml, etc.) + - Include relevant context for the specific project type +5. **Create the file**: Write the CHANGELOG.md to the project root +6. **Provide guidance**: Show the user how to use the new changelog + +## Standard Changelog Template + +```markdown +# 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). + +## [Unreleased] + +## [1.0.0] - YYYY-MM-DD + +### Added +- Initial release +``` + +## Categories Explanation + +Include a comment in the changelog explaining the categories: + +```markdown + +``` + +## Workflow + +``` +User: I need to create a changelog for this project + +You: I'll help you initialize a new CHANGELOG.md file. Let me first check if one already exists. + +[Check for existing CHANGELOG.md] + +Great! I don't see an existing CHANGELOG.md. Let me create one for you. + +[Check for version information in package files] + +I found your project is using version 1.0.0 from pyproject.toml. + +I'll create a CHANGELOG.md with: +- Standard Keep a Changelog header +- [Unreleased] section for upcoming changes +- [1.0.0] section for the initial release + +[Create the file] + +✅ Created CHANGELOG.md in the project root! + +Next steps: +1. Review the initial structure +2. Add any existing changes to the [Unreleased] section +3. Use /changelog-add when you make changes to the project +4. The changelog hook will ensure you update the changelog before each commit + +Would you like me to add any specific entries to get you started? +``` + +## Version Detection + +Try to detect the current version from common files: +- `package.json` (Node.js): Check the "version" field +- `pyproject.toml` (Python): Check [tool.poetry.version] or [project] version +- `Cargo.toml` (Rust): Check [package] version +- `pom.xml` (Java/Maven): Check tag +- `build.gradle` (Gradle): Check version property +- `setup.py` (Python): Check version parameter +- `__version__.py`: Check __version__ variable + +## Customization Options + +Ask the user if they want to: +- Include an initial version entry or just [Unreleased] +- Add any specific categories they commonly use +- Include example entries to guide future updates +- Add project-specific notes or conventions + +## Important Notes +- Never overwrite an existing CHANGELOG.md without explicit confirmation +- Use the current date (YYYY-MM-DD format) for version entries +- Include helpful comments for first-time users +- Make sure the format is exactly correct (Keep a Changelog is specific about format) +- After creating, suggest adding it to git: `git add CHANGELOG.md` + +## Follow-up + +After creating the changelog: +1. Show the user the created file +2. Explain how to use it +3. Mention the /changelog-add command +4. Remind them about the changelog hook that will enforce updates + +Remember: A good initial changelog structure sets the tone for maintaining it throughout the project's lifetime. diff --git a/commands/changelog-view.md b/commands/changelog-view.md new file mode 100644 index 0000000..88c5d93 --- /dev/null +++ b/commands/changelog-view.md @@ -0,0 +1,107 @@ +--- +description: View recent entries from the CHANGELOG.md file +--- + +Display recent changelog entries from the project's CHANGELOG.md file to help understand recent changes and the changelog format. + +## Context +The CHANGELOG.md file contains all notable changes to the project, organized by version and date following the Keep a Changelog format. + +## Task +Help the user view and understand recent changelog entries: + +1. **Locate CHANGELOG.md**: Find the CHANGELOG.md file in the project root or nearby repositories +2. **Read the file**: Load the changelog content +3. **Display recent entries**: + - Show the [Unreleased] section if it exists (this shows pending changes) + - Show the 3-5 most recent versioned releases + - Format the output in a readable way +4. **Provide context**: + - Highlight the changelog format and structure + - Point out different change categories (Added, Changed, Fixed, etc.) + - Show examples of well-formatted entries +5. **Offer next steps**: + - Suggest using /changelog-add if they need to add an entry + - Point out where new entries should be added + +## Display Format + +Show the changelog content in a structured way: + +```markdown +# Recent Changelog Entries + +## [Unreleased] +[If there are unreleased entries, show them here] + +## [Version] - Date +[Show recent version entries] + +--- + +💡 Tips: +- New entries should be added to the [Unreleased] section +- Use /changelog-add to add a new entry +- Follow the existing format and style +``` + +## Example Output + +``` +# Recent Changelog Entries from CHANGELOG.md + +## [Unreleased] + +### Fixed +- **Feeding Endpoint Error**: Fixed 500 server error in `/v3/feeding/sfr-by-weeknumber-and-year` endpoint + +## [3.7.19] - 2025-09-26 + +## [3.7.18] - 2025-09-26 + +## [3.7.17] - 2025-09-25 + +### Changed +- Use sudo apt install instead of curl for just install + +### Fixed +- Reintroduced the period param + +--- + +💡 The changelog follows Keep a Changelog format (https://keepachangelog.com/) + +Categories used: +- Added: New features +- Changed: Changes in existing functionality +- Fixed: Bug fixes +- Removed: Removed features +- Security: Security improvements +- Deprecated: Soon-to-be removed features + +To add a new entry: /changelog-add +``` + +## Options + +If the user specifies what they want to see, adjust the output: +- "latest version" → Show only the most recent release +- "unreleased" → Show only the [Unreleased] section +- "all" → Show the entire changelog +- "last N versions" → Show the last N versions + +## Additional Features + +- **Search**: If user asks to search for specific terms, grep through the changelog +- **Statistics**: Can provide counts of different types of changes +- **Format check**: Can validate that the changelog follows the expected format +- **Compare**: Can compare what's in [Unreleased] vs what's committed + +## Important Notes +- Make the output readable and well-formatted +- Use appropriate markdown formatting +- Highlight important sections +- Provide helpful context about the changelog structure +- Offer actionable next steps + +Remember: This command helps users understand the project's change history and learn the changelog format. diff --git a/hooks/check-changelog-before-commit.py b/hooks/check-changelog-before-commit.py new file mode 100644 index 0000000..f9dd797 --- /dev/null +++ b/hooks/check-changelog-before-commit.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +""" +Changelog enforcement hook for Claude Code. + +This hook checks if a git commit attempt includes a changelog update. +Blocks commits that don't have corresponding changelog entries. +""" + +import json +import sys +import subprocess +import re +from pathlib import Path + + +def run_git_command(cmd, cwd): + """Run a git command and return the output.""" + try: + result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, timeout=5) + return result.stdout.strip(), result.returncode + except subprocess.TimeoutExpired: + return "", 1 + except Exception: + return "", 1 + + +def is_commit_attempt(prompt): + """Check if the prompt is attempting a git commit.""" + commit_patterns = [ + r"\bgit\s+commit\b", + r"\bcommit\s+(the\s+)?changes?\b", + r"\bcreate\s+a\s+commit\b", + r"\bmake\s+a\s+commit\b", + r"\bcommit\s+.*\s+to\s+git\b", + ] + + prompt_lower = prompt.lower() + return any(re.search(pattern, prompt_lower) for pattern in commit_patterns) + + +def check_changelog_modified(cwd): + """Check if CHANGELOG.md has been modified in the current branch.""" + + # Check if we're in a git repository + _, returncode = run_git_command(["git", "rev-parse", "--git-dir"], cwd) + if returncode != 0: + return True # Not in a git repo, allow the commit + + # Find CHANGELOG files + changelog_files = [] + for pattern in ["CHANGELOG.md", "CHANGELOG.MD", "changelog.md"]: + changelog_path = Path(cwd) / pattern + if changelog_path.exists(): + changelog_files.append(pattern) + + if not changelog_files: + return False # No changelog file found, require user to address this + + # Check if any changelog file is in staged changes + staged_output, _ = run_git_command(["git", "diff", "--cached", "--name-only"], cwd) + for changelog_file in changelog_files: + if changelog_file in staged_output: + return True + + # Check if any changelog file has unstaged changes + unstaged_output, _ = run_git_command(["git", "diff", "--name-only"], cwd) + for changelog_file in changelog_files: + if changelog_file in unstaged_output: + return True + + return False + + +def check_unused_sections(cwd): + """Check if changelog has empty/unused sections that should be cleaned up.""" + # Find the changelog file + changelog_path = None + for pattern in ["CHANGELOG.md", "CHANGELOG.MD", "changelog.md"]: + path = Path(cwd) / pattern + if path.exists(): + changelog_path = path + break + + if not changelog_path: + return None # No changelog found + + try: + content = changelog_path.read_text(encoding='utf-8') + except Exception: + return None # Can't read file + + # Find the [Unreleased] section + unreleased_match = re.search(r'## \[Unreleased\](.*?)(?=## \[|\Z)', content, re.DOTALL) + if not unreleased_match: + return None # No unreleased section + + unreleased_section = unreleased_match.group(1) + + # Check for empty category sections + categories = ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security'] + empty_sections = [] + + for category in categories: + # Look for the category heading + category_pattern = rf'### {category}\s*\n' + if re.search(category_pattern, unreleased_section): + # Check if there's content after the heading (before next heading or end) + content_pattern = rf'### {category}\s*\n(.*?)(?=###|\Z)' + match = re.search(content_pattern, unreleased_section, re.DOTALL) + if match: + section_content = match.group(1).strip() + # Filter out HTML comments + section_content = re.sub(r'', '', section_content, flags=re.DOTALL).strip() + if not section_content: + empty_sections.append(category) + + return empty_sections if empty_sections else None + + +def main(): + """Main hook execution.""" + try: + # Read input from stdin + input_data = json.load(sys.stdin) + + prompt = input_data.get("prompt", "") + cwd = input_data.get("cwd", ".") + + # Check if this is a commit attempt + if not is_commit_attempt(prompt): + # Not a commit attempt, allow it + sys.exit(0) + + # Check if changelog has been modified + if check_changelog_modified(cwd): + # Changelog has been updated, now check for unused sections + empty_sections = check_unused_sections(cwd) + if empty_sections: + # Warn about empty sections that should be cleaned up + sections_list = ", ".join(empty_sections) + output = { + "decision": "block", + "reason": f"""⚠️ Changelog cleanup required! + +Your CHANGELOG.md has empty sections that should be removed before committing: +{sections_list} + +Please remove these empty category headings from the [Unreleased] section to keep the changelog clean. + +Only include category headings that have actual entries under them. + +💡 Tip: Edit CHANGELOG.md to remove the empty ### headings, then stage the file again.""", + "hookSpecificOutput": { + "hookEventName": "UserPromptSubmit", + "additionalContext": "Empty changelog sections should be removed before committing.", + }, + } + print(json.dumps(output)) + sys.exit(2) + + # Changelog has been updated and is clean, allow the commit + sys.exit(0) + + # Block the commit - no changelog update found + output = { + "decision": "block", + "reason": """⚠️ Changelog update required! + +You're attempting to commit changes without updating the CHANGELOG.md file. + +Please: +1. Add an entry to CHANGELOG.md describing your changes +2. Follow the Keep a Changelog format (https://keepachangelog.com/) +3. Add the changes under the [Unreleased] section +4. Stage the changelog file: git add CHANGELOG.md +5. Then retry your commit + +💡 Tip: You can use /changelog:changelog-add command to add an entry quickly.""", + "hookSpecificOutput": { + "hookEventName": "UserPromptSubmit", + "additionalContext": "The changelog must be updated before committing code changes.", + }, + } + + print(json.dumps(output)) + sys.exit(2) # Exit code 2 indicates blocking + + except Exception as e: + # If the hook fails, don't block the user (fail open) + print(json.dumps({"error": str(e)}), file=sys.stderr) + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..2144f41 --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,17 @@ +{ + "description": "Changelog validation before commits", + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/check-changelog-before-commit.py", + "timeout": 5 + } + ] + } + ] + } +} diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..b7eaa94 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,65 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:EmilLindfors/claude-marketplace:plugins/changelog", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "c320ec670ad024a98a0e70e478723ad75d255281", + "treeHash": "7d44bf7ae33d17360adff0be3378f2e46c4e60e1c0a845999f35b400e17d5de4", + "generatedAt": "2025-11-28T10:10:28.875105Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "changelog", + "description": "Changelog management plugin that ensures all code commits include proper changelog entries. Provides hooks to prevent commits without changelog updates, commands for managing changelog entries, and agents for writing well-formatted changelog entries following Keep a Changelog format", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "b0a9c48dd5433a45c1a4f1d008dfcec1e585c91c37fa022a7adcb387cbee37d0" + }, + { + "path": "agents/changelog-writer.md", + "sha256": "331c29dbca96ed5905b8ec5ec709fc3402c7e4fc425b509a0d2a14efa3104a73" + }, + { + "path": "hooks/check-changelog-before-commit.py", + "sha256": "520e2ca582b3d781f0890c35bbfc823f1f05209ab8338e4d20874193abe4d77f" + }, + { + "path": "hooks/hooks.json", + "sha256": "870874f9095f09fdec29c487092353d6a05e5fecd2f6c5a78247e857db30bb24" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "960395f1cead5605c783259d525edeb691889c478654f34f49983759a9458242" + }, + { + "path": "commands/changelog-view.md", + "sha256": "00a2b523c4962a7364d231139b7578f5ac6acc6e159979b39bf120c6fa3b165d" + }, + { + "path": "commands/changelog-init.md", + "sha256": "83c6e24152ca1d713dcb3d41a3d68d491a418b62b737fe2c38e986b0a751dd65" + }, + { + "path": "commands/changelog-add.md", + "sha256": "91298a92842f006117ff03d160c6f8210a43fc7fb3a9725b2d99aeea4f8c6421" + } + ], + "dirSha256": "7d44bf7ae33d17360adff0be3378f2e46c4e60e1c0a845999f35b400e17d5de4" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file