{"id":34086619,"url":"https://github.com/hesreallyhim/antipasta","last_synced_at":"2026-04-01T22:33:14.226Z","repository":{"id":313546160,"uuid":"1051777415","full_name":"hesreallyhim/antipasta","owner":"hesreallyhim","description":"A library for monitoring a variety of complexity metrics in source code and enforcing limitations in your CI/CD processes.","archived":false,"fork":false,"pushed_at":"2026-03-22T01:26:00.000Z","size":865,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-22T14:46:04.538Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hesreallyhim.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["hesreallyhim"],"patreon":["hesreallyhim"],"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":null}},"created_at":"2025-09-06T17:44:40.000Z","updated_at":"2026-03-22T01:26:03.000Z","dependencies_parsed_at":"2025-09-06T20:37:52.730Z","dependency_job_id":"fd0ff808-4b34-49f3-a891-be0eb7c7edd3","html_url":"https://github.com/hesreallyhim/antipasta","commit_stats":null,"previous_names":["hesreallyhim/antipasta"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/hesreallyhim/antipasta","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hesreallyhim%2Fantipasta","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hesreallyhim%2Fantipasta/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hesreallyhim%2Fantipasta/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hesreallyhim%2Fantipasta/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hesreallyhim","download_url":"https://codeload.github.com/hesreallyhim/antipasta/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hesreallyhim%2Fantipasta/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31292648,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T21:15:39.731Z","status":"ssl_error","status_checked_at":"2026-04-01T21:15:34.046Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-14T13:31:09.660Z","updated_at":"2026-04-01T22:33:14.216Z","avatar_url":"https://github.com/hesreallyhim.png","language":"Python","funding_links":["https://github.com/sponsors/hesreallyhim","https://patreon.com/[\"hesreallyhim\"]"],"categories":[],"sub_categories":[],"readme":"# antipasta\n\nA code quality enforcement tool that analyzes code complexity across a number of metrics and helps maintain readable, maintainable code.\n\n## What is antipasta?\n\nantipasta analyzes your source code files and measures various complexity metrics, comparing them against configurable thresholds. If any metrics exceed their thresholds, antipasta reports violations and exits with a non-zero status code, making it suitable for CI/CD pipelines.\n\nCurrently, antipasta supports Python code analysis with plans to add JavaScript and TypeScript support.\n\n## Why use antipasta?\n\nComplex code is harder to understand, test, and maintain. By enforcing limits on complexity metrics, you can:\n\n-   Catch overly complex functions before they're merged\n-   Maintain consistent code quality standards across your team\n-   Identify refactoring opportunities\n-   Reduce technical debt over time\n\n### What makes antipasta different than other code complexity tools like `radon`?\n\nI have no idea, I've never used them - this library is mostly a combination of [radon](https://github.com/rubik/radon) and [complexipy](https://github.com/rohaquinlop/complexipy) with an amazing CLI.\n\n## Installation\n\n### From PyPI (Recommended)\n\n```bash\npip install antipasta\n```\n\n### Development Installation\n\n```bash\n# Clone the repository\ngit clone https://github.com/hesreallyhim/antipasta.git\ncd antipasta\n\n# Install with development dependencies (automatically creates venv)\nmake install-dev\n```\n\n### Requirements\n\n-   Python 3.11 or higher\n\n## Quick Start\n\n### Generate Configuration\n\nIf you don't have a configuration file yet, generate one with sensible defaults:\n\n```bash\n# Interactive mode - prompts for your preferences\nantipasta config generate\n\n# Non-interactive mode - uses defaults\nantipasta config generate --non-interactive\n\n# Generate to a custom location\nantipasta config generate --output my-config.yaml\n```\n\nThe interactive mode will guide you through setting up:\n- Complexity thresholds (cyclomatic, cognitive, maintainability)\n- Advanced Halstead metrics (optional)\n- Language support (currently Python only, JavaScript/TypeScript coming soon)\n- Ignore patterns (enter one at a time, with default test patterns optional)\n- Gitignore integration settings\n\nThe interactive mode validates all inputs and shows valid ranges:\n- **Cyclomatic Complexity**: 1-50 (recommended: 10)\n- **Cognitive Complexity**: 1-100 (recommended: 15)\n- **Maintainability Index**: 0-100 (recommended: 50)\n- **Halstead Volume**: 1-100000 (recommended: 1000)\n- **Halstead Difficulty**: 0.1-100 (recommended: 10)\n- **Halstead Effort**: 1-1000000 (recommended: 10000)\n\nIf you enter an invalid value, you'll be prompted to try again with helpful error messages.\n\n#### Interactive Configuration Example\n\nWhen running `antipasta config generate`, you'll see prompts like:\n\n```\nWelcome to antipasta configuration generator!\n==================================================\n\nLet's set up your code quality thresholds:\n----------------------------------------\n  ℹ️  Range: 1-50 (lower is stricter). Recommended: 10\nMaximum cyclomatic complexity per function [10]:\n\nFile patterns to ignore during analysis:\n----------------------------------------\nInclude default test file patterns? (**/test_*.py, **/*_test.py, **/tests/**) [Y/n]: y\n  ✓ Added default test patterns\n\nEnter additional patterns to ignore (one per line, press Enter with no input to finish):\nPattern (or press Enter to continue): *.pyc\n  ✓ Added: *.pyc\nPattern (or press Enter to continue):\n  Total patterns to ignore: 4\n```\n\n## Available Commands\n\nantipasta provides the following commands:\n\n| Command | Description | Default Behavior |\n|---------|-------------|------------------|\n| `config` | Manage configuration files (subcommands: generate, validate, view) | Group command for configuration management |\n| `config generate` | Generate a configuration file interactively or with defaults | Creates `.antipasta.yaml` in current directory |\n| `config validate` | Validate a configuration file | Validates `.antipasta.yaml` if no file specified |\n| `config view` | View configuration in various formats | Shows configuration summary |\n| `metrics` | Analyze code metrics for specified files | Uses `.antipasta.yaml` config, shows helpful message if missing |\n| `stats` | Collect and display code metrics statistics | Analyzes files matching the specified pattern |\n| `tui` | Launch terminal UI dashboard (interactive) | Shows real-time metrics analysis |\n\n\u003e **Note**: The old commands `generate-config` and `validate-config` are deprecated but still work for backward compatibility. They will show a deprecation warning. Please use `config generate` and `config validate` instead.\n\n## Basic Usage\n\n### Analyze Files\n\n```bash\n# Analyze specific files (uses .antipasta.yaml by default)\nantipasta metrics --files src/main.py src/utils.py\n\n# Analyze all Python files in a directory\nantipasta metrics --directory src/\n\n# Use a custom configuration file\nantipasta metrics --config my-config.yaml --directory .\n\n# Quiet mode (only show violations)\nantipasta metrics --quiet --directory src/\n\n# Output results in JSON format\nantipasta metrics --format json --directory src/\n```\n\n**Note**: If `.antipasta.yaml` doesn't exist, antipasta will:\n1. Show a helpful message: \"Configuration file '.antipasta.yaml' not found. Run 'antipasta config generate' to create a configuration file.\"\n2. Continue with default settings for the analysis\n\n### Validate Configuration\n\n```bash\n# Check if your configuration file is valid (defaults to .antipasta.yaml)\nantipasta config validate\n\n# Validate a specific configuration file\nantipasta config validate my-config.yaml\n```\n\n### View Configuration\n\n```bash\n# View configuration summary (default)\nantipasta config view\n\n# View raw YAML content\nantipasta config view --format raw\n\n# View as JSON\nantipasta config view --format json\n\n# View as a formatted table\nantipasta config view --format table\n\n# View specific config file\nantipasta config view --path custom-config.yaml\n```\n\n### Collect Statistics\n\n```bash\n# Get overall statistics for Python files\nantipasta stats --pattern \"**/*.py\"\n\n# Statistics grouped by directory\nantipasta stats --pattern \"**/*.py\" --by-directory\n\n# Statistics grouped by module (Python packages)\nantipasta stats --pattern \"**/*.py\" --by-module\n\n# Include additional metrics in statistics\nantipasta stats --pattern \"**/*.py\" --metric cyclomatic_complexity --metric cognitive_complexity\n\n# Save results to a file\nantipasta stats --pattern \"**/*.py\" --output report.txt\nantipasta stats --pattern \"**/*.py\" --format json --output report.json\nantipasta stats --pattern \"**/*.py\" --format csv --output report.csv\n\n# Generate ALL report formats at once (9 files from 1 analysis!)\nantipasta stats --pattern \"**/*.py\" --format all --output ./reports/\n# This creates:\n#   - stats_overall.{json,csv,txt}\n#   - stats_by_directory.{json,csv,txt}\n#   - stats_by_module.{json,csv,txt}\n```\n\n## Configuration\n\nantipasta uses YAML configuration files. By default, it looks for `.antipasta.yaml` in the current directory.\n\n### Example Configuration\n\n```yaml\n# antipasta configuration file\n# Generated by: antipasta config generate\n\n# Default thresholds for all languages\ndefaults:\n  max_cyclomatic_complexity: 10.0\n  max_cognitive_complexity: 15.0\n  min_maintainability_index: 50.0\n  # Halstead metrics (advanced)\n  max_halstead_volume: 1000.0\n  max_halstead_difficulty: 10.0\n  max_halstead_effort: 10000.0\n\n# Language-specific configurations\nlanguages:\n  - name: python\n    extensions:\n      - .py\n    metrics:\n      - type: cyclomatic_complexity\n        threshold: 10.0\n        comparison: \"\u003c=\"\n\n      - type: cognitive_complexity\n        threshold: 15.0\n        comparison: \"\u003c=\"\n\n      - type: maintainability_index\n        threshold: 50.0\n        comparison: \"\u003e=\"\n\n      - type: halstead_volume\n        threshold: 1000.0\n        comparison: \"\u003c=\"\n\n      - type: halstead_difficulty\n        threshold: 10.0\n        comparison: \"\u003c=\"\n\n      - type: halstead_effort\n        threshold: 10000.0\n        comparison: \"\u003c=\"\n\n# Files and patterns to ignore during analysis\nignore_patterns:\n  - \"**/test_*.py\"\n  - \"**/*_test.py\"\n  - \"**/tests/**\"\n  - \"**/__pycache__/**\"\n\n# Whether to use .gitignore file for excluding files\nuse_gitignore: true\n```\n\n### Configuration Structure\n\n-   **use_gitignore**: Whether to automatically use patterns from `.gitignore` (default: true)\n-   **defaults**: Default thresholds used when language-specific configuration is not provided\n-   **languages**: Language-specific configurations\n    -   **name**: Language identifier (currently only \"python\" is supported)\n    -   **extensions**: File extensions to associate with this language\n    -   **metrics**: List of metrics to check\n        -   **type**: The metric type (see Metrics section below)\n        -   **threshold**: The threshold value\n        -   **comparison**: How to compare the metric value with the threshold\n        -   **enabled**: (optional) Whether to check this metric (default: true, can be omitted)\n-   **ignore_patterns**: Additional gitignore-style patterns for files to skip (combined with .gitignore if `use_gitignore` is true)\n\n### Comparison Operators\n\n-   `\u003c=` - Metric value must be less than or equal to threshold\n-   `\u003c` - Metric value must be less than threshold\n-   `\u003e=` - Metric value must be greater than or equal to threshold\n-   `\u003e` - Metric value must be greater than threshold\n-   `==` - Metric value must equal threshold\n-   `!=` - Metric value must not equal threshold\n\n## Metrics Explained\n\n### Lines of Code Metrics\n\n-   **LOC**: Total lines of code\n-   **SLOC**: Source lines of code (excluding comments and blanks)\n-   **LLOC**: Logical lines of code\n-   **Comment Lines**: Number of comment lines\n-   **Blank Lines**: Number of blank lines\n\n### Cyclomatic Complexity\n\nMeasures the number of linearly independent paths through a function. Higher values indicate more complex control flow.\n\n-   **Good**: 1-10 (simple, easy to test)\n-   **Moderate**: 11-20 (more complex, harder to test)\n-   **High**: 21+ (very complex, consider refactoring)\n\nExample of high complexity:\n\n```python\ndef process_data(data, mode, validate, transform):\n    if validate:\n        if not data:\n            return None\n        if mode == \"strict\":\n            if not isinstance(data, dict):\n                raise ValueError(\"Invalid data\")\n\n    if transform:\n        if mode == \"simple\":\n            return data.lower()\n        elif mode == \"complex\":\n            if validate:\n                return data.upper()\n            else:\n                return data.title()\n\n    return data\n```\n\n### Halstead Metrics\n\nBased on the number of operators and operands in code:\n\n-   **Volume**: Program size based on the number of operations\n-   **Difficulty**: How hard the code is to understand\n-   **Effort**: Mental effort required to understand the code\n-   **Time**: Estimated time to implement (in seconds)\n-   **Bugs**: Estimated number of bugs (Volume / 3000)\n\n### Maintainability Index\n\nA composite metric (0-100) that considers cyclomatic complexity, lines of code, and Halstead volume. Higher values indicate more maintainable code.\n\n-   **Good**: 50-100 (maintainable)\n-   **Moderate**: 20-49 (moderately maintainable)\n-   **Low**: 0-19 (difficult to maintain)\n\n### Cognitive Complexity\n\nMeasures how difficult code is to understand, focusing on human comprehension rather than execution paths. Unlike cyclomatic complexity, it penalizes nested structures more heavily.\n\n-   **Good**: 1-15 (easy to understand)\n-   **Moderate**: 16-30 (requires careful reading)\n-   **High**: 31+ (difficult to understand, consider refactoring)\n\nKey differences from cyclomatic complexity:\n-   Heavily penalizes nested control structures\n-   Considers break/continue statements in loops\n-   Better represents actual cognitive load\n\n## Exit Codes\n\n-   **0**: Success - all metrics pass their thresholds (for `metrics`), or configuration is valid (for `validate-config`)\n-   **1**: Error - invalid configuration, processing errors, or validation failures\n-   **2**: Violations found (for `metrics` command) or invalid command arguments (for all commands)\n\nThis makes antipasta suitable for CI/CD pipelines:\n\n```bash\n# Basic CI pipeline usage\nantipasta metrics --directory src/ --quiet || exit 1\n\n# First-time setup in CI\nif [ ! -f .antipasta.yaml ]; then\n    antipasta config generate --non-interactive\nfi\nantipasta metrics --directory src/\n\n# Validate configuration before running metrics\nantipasta config validate \u0026\u0026 antipasta metrics --directory src/\n```\n\n## Example Output\n\n### Standard Output\n\n```\nUsing configuration: .antipasta.yaml\nAnalyzing 3 files...\n\n======================================================================\nMETRICS ANALYSIS SUMMARY\n======================================================================\nTotal files analyzed: 3\nFiles with violations: 1\nTotal violations: 2\n\nViolations by type:\n  - cyclomatic_complexity: 1\n  - maintainability_index: 1\n\n----------------------------------------------------------------------\nVIOLATIONS FOUND:\n----------------------------------------------------------------------\n❌ src/complex.py:15 (process_data): Cyclomatic Complexity is 12.00 (threshold: \u003c= 10.0)\n❌ src/complex.py: Maintainability Index is 45.23 (threshold: \u003e= 50.0)\n\n✗ Code quality check FAILED\n```\n\n### Quiet Mode Output\n\n```\n----------------------------------------------------------------------\nVIOLATIONS FOUND:\n----------------------------------------------------------------------\n❌ src/complex.py:15 (process_data): Cyclomatic Complexity is 12.00 (threshold: \u003c= 10.0)\n❌ src/complex.py: Maintainability Index is 45.23 (threshold: \u003e= 50.0)\n\n✗ Code quality check FAILED\n```\n\n\n## Learning Resources\n\n### Code Complexity Reduction Tutorial\n\nIf you're new to code complexity metrics or want to learn how to reduce complexity in your code, check out our comprehensive tutorial:\n\n📚 **[Code Complexity Reduction Tutorial](DEMOS/TUTORIAL/README.md)**\n\nThis hands-on tutorial walks you through:\n- Understanding what makes code complex\n- Step-by-step techniques to reduce complexity\n- Real examples showing a 90% reduction in cognitive complexity\n- When to stop refactoring (perfect metrics vs practical code)\n\nThe tutorial includes 5 progressive versions of the same code, demonstrating techniques like:\n1. **Early returns** - The simplest and most effective technique\n2. **Function extraction** - Breaking down large functions\n3. **Data classes** - Reducing parameter lists\n4. **Configuration objects** - Eliminating magic numbers\n5. **Enterprise patterns** - When and how to use advanced patterns\n\nPerfect for developers who want to write more maintainable code or teams establishing code quality standards.\n\n## Statistics Collection\n\nThe `antipasta stats` command provides comprehensive statistical analysis of your codebase:\n\n### Features\n\n- **File-level statistics**: Average, min, max lines of code per file\n- **Directory grouping**: See metrics broken down by folder\n- **Module grouping**: Group by Python packages\n- **Multiple metrics**: Include any supported metric in the analysis\n- **Export formats**: Table (human-readable), JSON, CSV\n\n### Examples\n\n```bash\n# Basic statistics for all Python files\nantipasta stats --pattern \"**/*.py\"\n\n# Group by directory to find large folders\nantipasta stats --pattern \"**/*.py\" --by-directory\n\n# Group by Python module\nantipasta stats --pattern \"**/*.py\" --by-module\n\n# Include complexity metrics\nantipasta stats --pattern \"**/*.py\" \\\n    --metric cyclomatic_complexity \\\n    --metric cognitive_complexity \\\n    --metric maintainability_index\n\n# Multiple file patterns\nantipasta stats --pattern \"src/**/*.py\" --pattern \"tests/**/*.py\"\n\n# Export for further analysis\nantipasta stats --pattern \"**/*.py\" --format csv \u003e metrics.csv\nantipasta stats --pattern \"**/*.py\" --format json | jq '.files.avg_loc'\n```\n\n### Output Example\n\n```\n============================================================\nCODE METRICS STATISTICS\n============================================================\n\nFILE STATISTICS:\n  Total files: 23\n  Total LOC: 1,947\n  Average LOC per file: 114.5\n  Min LOC: 4\n  Max LOC: 456\n  Standard deviation: 123.3\n\nCYCLOMATIC COMPLEXITY STATISTICS:\n  Count: 81\n  Average: 4.21\n  Min: 1.00\n  Max: 22.00\n```\n\n### Use Cases\n\n1. **Track codebase growth**: Monitor total LOC over time\n2. **Identify large files**: Find files that exceed size thresholds\n3. **Compare modules**: See which parts of your code are most complex\n4. **Team metrics**: Compare complexity across different team areas\n5. **Refactoring targets**: Find directories with high average complexity\n\n## Development\n\n### Running Tests\n\n```bash\n# Run all tests\nmake test\n\n# Run only tests affected by your recent changes (requires initial baseline run)\nmake test-fast\n\n# Clear pytest-testmon cache to force a full recalculation\nmake test-fast-clean\n\n# Run with coverage\nmake test-coverage\n\n# Run specific test file\npytest tests/unit/test_config.py -v\n```\n\nThe `make test-fast` target automatically stores its SQLite cache under `.cache/testmon.sqlite` via the `TESTMON_DATAFILE` environment variable so the repository root stays clean. If the cache ever gets out of date (for example after switching branches), run `make test-fast-clean` to remove the `.cache/testmon.sqlite*` files and trigger a fresh baseline on the next `make test-fast`.\n\n### Code Quality\n\n```bash\n# Format code\nmake format\n\n# Run linters\nmake lint\n\n# Type checking\nmake type-check\n```\n\n### Project Structure\n\n```\nantipasta/\n├── cli/              # Command-line interface\n├── core/             # Core functionality\n│   ├── config.py     # Configuration models\n│   ├── detector.py   # Language detection\n│   ├── metrics.py    # Metric definitions\n│   ├── violations.py # Violation tracking\n│   └── aggregator.py # Analysis coordination\n├── runners/          # Language-specific analyzers\n│   └── python/       # Python analysis (Radon)\n└── utils/            # Utilities\n```\n\n## Current Limitations\n\n1. **Python Only**: Currently only Python is supported. JavaScript and TypeScript support is coming soon.\n2. **Function-Level Only**: Some metrics are only available at the function level, not class or module level.\n3. **Cognitive Complexity Requires Complexipy**: The cognitive complexity metric requires the optional `complexipy` package to be installed.\n\n## Future Enhancements\n\n-   JavaScript/TypeScript support via ts-complex\n-   Pre-commit hook integration\n-   Git hook support\n-   HTML report generation\n-   Baseline file support (ignore existing violations)\n-   Trend analysis over time\n-   GitHub Actions integration\n-   VS Code extension\n\n## Contributing\n\nContributions are welcome! Please:\n\n1. Fork the repository\n2. Create a feature branch\n3. Add tests for new functionality\n4. Ensure all tests pass\n5. Submit a pull request\n\n## Release Process\n\nantipasta supports two release workflows:\n\n### Automated Release (Recommended)\n\nUsing GitHub Actions for automated PyPI deployment:\n\n```bash\n# Pre-flight check\nmake release-doctor      # Check system health before releasing\n\n# Safe one-command release (recommended)\nmake release-patch-safe  # Bug fixes with safety checks\nmake release-minor-safe  # New features with safety checks\nmake release-major-safe  # Breaking changes with safety checks\n\n# Test before releasing\nmake release-dry-patch   # Simulate what will happen\nmake gh-release-dry      # Test GitHub release creation\n\n# Standard releases (without safety checks)\nmake release-patch       # For bug fixes\nmake release-minor       # For new features\nmake release-major       # For breaking changes\n\n# Or step-by-step for more control:\nmake version-bump-patch  # Bump version\nmake gh-release-safe     # Create release with safety checks\n\n# Create a draft release to review first\nmake gh-release-draft    # Creates draft, publish manually on GitHub\n```\n\n### Manual Release\n\nFor direct PyPI uploads from your machine:\n\n```bash\n# 1. Pre-release checks\nmake release-doctor      # Comprehensive health check\nmake release-safety-check # Validate repository state\n\n# 2. Test first\nmake release-dry-patch   # Simulate the release\n\n# 3. Full release workflow\nmake check               # Run all tests\nmake version-bump-patch  # Bump version\nmake release            # Upload to PyPI\n```\n\n### Testing Releases\n\nTest on TestPyPI before production:\n\n```bash\n# Using Makefile (recommended)\nmake gh-release-test  # Triggers TestPyPI workflow\n\n# Or manually via GitHub UI\n# Go to Actions → \"Publish to PyPI\" → Run workflow → Choose 'testpypi'\n```\n\nSee [RELEASE.md](RELEASE.md) for detailed release instructions.\n\n## License\n\n[Add your license here]\n\n## Acknowledgments\n\n-   [Radon](https://github.com/rubik/radon) for Python code metrics\n-   Inspired by various code quality tools like ESLint, Pylint, and SonarQube\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhesreallyhim%2Fantipasta","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhesreallyhim%2Fantipasta","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhesreallyhim%2Fantipasta/lists"}