From 269fb2ea9dad3bd4824f9e8fad3840d4ab953abc Mon Sep 17 00:00:00 2001 From: Artemonim Date: Tue, 1 Jul 2025 10:47:52 +0300 Subject: [PATCH 1/5] Update version to 1.3.1 and enhance docstring handling ### Changed - Improved handling of manual docstrings in Python files to ensure correct placement and prevent duplication. - Added a new test to verify the idempotency of processing files with existing manual docstrings. - Updated `.gitignore` to include `temporary/` and `git_diff.txt`. ### Fixed - Resolved issues with the generation of Python docstrings to ensure they do not duplicate content on subsequent runs. --- .gitignore | 3 +- CHANGELOG.md | 330 +++++------ agent_docstrings/__init__.py | 2 +- agent_docstrings/core.py | 29 +- pyproject.toml | 4 +- .../fixtures/python_with_manual_docstring.py | 10 + tests/test_common.py | 522 +++++++++--------- tests/test_core.py | 26 +- tests/test_determinism.py | 27 + 9 files changed, 500 insertions(+), 453 deletions(-) create mode 100644 tests/fixtures/python_with_manual_docstring.py diff --git a/.gitignore b/.gitignore index 4cae6eb..ec35129 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,8 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST -/git_diff.txt +git_diff.txt +temporary/ # PyInstaller # Usually these files are written by a small stub bootstrap script and should be diff --git a/CHANGELOG.md b/CHANGELOG.md index 0720fa6..219aa49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,159 +1,171 @@ -# 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] - -### Planned Features - -- Additional language support: Swift, Perl, Curl, Fortran, Visual Basic, R, PHP, Lua, Bash, SQL -- Configuration file format validation -- Switching to the Abstract Syntax Tree (AST) -- [And more...](https://github.com/Artemonim/AgentDocstrings/issues) - -## [1.3.0] - 2025-06-30 - -### Added - -- **Process Individual Files**: The CLI now accepts both directory and individual file paths, allowing for more granular control over which files are processed. -- **Expanded Keywords**: Added a comprehensive list of keywords to `pyproject.toml` to significantly improve package discoverability on PyPI and search engines. -- **Beta Features Flag**: Introduced a `--beta` command-line flag to enable experimental features that are under development. - -### Changed - -- **Header Text Update**: Changed the auto-generated header to "Table of content is automatically generated by Agent Docstrings {version}". -- **Streamlined Header Format**: The format of the generated "Table of Contents" has been improved. Top-level functions and classes are now presented in a single, chronologically sorted list, removing the nested "Functions" section for a cleaner, more intuitive layout. -- **Deterministic Sorting**: All discovered items (classes, methods, functions) are now strictly sorted by their line number in the source file, ensuring a consistent and predictable output every time. -- **CLI Argument Renaming**: The `DIRECTORY` argument in the CLI has been renamed to `PATH` to accurately reflect its new capability to handle both files and directories. -- **Header Version Updates**: The tool will now update the header if the generator version has changed, even if the code structure remains the same, ensuring docstrings always reflect the version of the tool that generated them. -- **Repository URLs**: Updated project URLs in `pyproject.toml` to point to the correct `AgentDocstrings` repository name. - -### Fixed - -- **Error Handling for Inaccessible Directories**: Fixed a crash (`PermissionError`) that occurred when scanning directories with restricted read permissions. The application will now skip such directories and print a warning, preventing unintended modifications to files that might have been excluded by an unreadable `.gitignore` or other configuration files. -- **Deleting empty lines**: Detected and fixed the removal of empty lines at the end of processed files -- **Language-Specific Indentation**: Fixed the indentation in the generated 'Table of Contents' to respect common style conventions for each language (e.g., 4 spaces for Python, 2 for JavaScript). - -### Build - -- **Python Version Support**: Updated the minimum required Python version from 3.8 to 3.10 to align with modern dependencies and language features. Project metadata (`pyproject.toml`) and CI configurations have been updated accordingly. - -### CI/CD - -- **Continuous Integration Workflow**: Added a new GitHub Actions workflow (`ci.yml`) to automatically run tests on all pushes and pull requests to the `master` branch. The workflow tests against multiple Python versions (3.10-3.13) and includes matrix testing for beta features. - -### Documentation - -- **Complete README Overhaul**: The `README.md` has been completely rewritten to be more comprehensive, professional, and user-friendly. It now includes a clear project mission, a detailed table of contents, expanded sections on features and usage, new examples, and platform compatibility information. -- **Pull Request Template**: Added a `PULL_REQUEST_TEMPLATE.md` to standardize contributions. -- **Demo Video**: Included a new video in the `README.md` to demonstrate the tool's functionality. - -## [1.2.1] - 2025-06-30 - -### Added - -- **Python Docstring Merging**: Implemented a feature to merge the auto-generated header with existing manual module-level docstrings in Python files, preserving user-written content. - -### Changed - -- **Test Suite Refactoring**: Significantly refactored the test suite by introducing a `source_processor` fixture. This simplifies test code, removes boilerplate for file creation, and improves readability across all test files. - -### Documentation - -- Updated the repository URL in `README.md`. -- Reorganized `README.md` for better readability by moving the "Supported Languages" section to the top. - -## [1.2.0] - 2025-06-29 - -### Added - -- **Generator Versioning**: The tool's version is now embedded in the generated docstring for easier tracking and debugging. -- **Header Preservation**: Implemented intelligent detection to preserve file headers (e.g., shebangs, encoding declarations, Go package definitions, leading comments/imports) across all supported languages. -- **Expanded Language Support**: Added initial processing support and type mappings for Java, PowerShell, Delphi, and C. -- **Enhanced Testing**: Introduced new test suites for determinism, header preservation, and line number accuracy to ensure core feature reliability. -- **Initial release of `agent-docstrings`** - -### Changed - -- **Python Parser Overhaul**: Replaced the fragile regex-based Python parser with a robust implementation using Python's native Abstract Syntax Tree (`ast`) module. This provides highly accurate parsing of complex function signatures, decorators, type hints, and nested class structures. -- **Line Numbering Accuracy**: Completely reworked the line number calculation to account for preserved file headers and the size of the injected docstring, ensuring the table of contents is always accurate. - -### Fixed - -- **`__future__` Import Placement**: Corrected a critical bug where `from __future__ import` statements were incorrectly moved below the generated docstring, breaking Python file syntax. -- **Docstring Management**: Hardened the logic for identifying and removing agent-generated docstrings by using more specific start/end markers, preventing accidental modification of user-written docstrings. -- **Generic Parser**: Improved the generic parser for C-style languages, resolving a known bug that affected brace counting and failed C# file parsing. - -## [1.1.0] - 2025-06-29 - -### AST-parsing - -- **Go Language**: - - Implemented a new, high-precision AST parser using Go's native `go/parser` and `go/ast` libraries. This significantly improves the accuracy of identifying functions, methods, and types in `.go` files compared to previous methods. - - Added a `build_goparser.ps1` script to automate the compilation of the Go parser into an executable. - - Integrated the new parser into the main Python application, replacing the old logic for Go file analysis. - -## [1.0.1] - 2025-06-27 - -### Fixed - -- **Parser improvements**: - - Correctly identifies functions with `async def`. - - Better handling of functions with decorators. -- **Docstring placement**: - - Ensures auto-generated docstrings are placed after shebang (`#!`) and encoding (`# -*- coding: utf-8 -*-`) lines. -- **Unified docstring handling**: - - Intelligently integrates auto-generated docstrings into existing manual docstrings. - - Replaces content of existing auto-generated docstrings while preserving manual additions. - -## [1.0.0] - 2025-06-27 - -### Added - -- **Multi-language support**: Python, Java, Kotlin, Go, PowerShell, Delphi, C++, C#, JavaScript, TypeScript -- **Smart file filtering system**: - - Automatic `.gitignore` parsing and respect - - Custom blacklist support via `.agent-docstrings-ignore` files - - Custom whitelist support via `.agent-docstrings-include` files -- **Python version compatibility**: Full support for Python 3.8, 3.9, 3.10, 3.11, 3.12, and 3.13 -- **Type annotations**: Complete type hint support using `typing` module for backward compatibility -- **CLI interface**: Easy-to-use command-line tool with verbose output option -- **Programmatic API**: Import and use in other Python projects -- **Safe operation**: Only modifies auto-generated docstring blocks, preserves existing documentation -- **Incremental updates**: Only processes files when changes are detected - -### Technical Features - -- Uses `from __future__ import annotations` for forward compatibility -- Compatible with `typing.Union` and `typing.Tuple` for Python 3.8/3.9 -- No external dependencies - built on Python standard library only -- Comprehensive test suite with pytest -- Full type checking support with mypy -- Code formatting with black -- Proper packaging for PyPI distribution - -### Configuration - -- `.agent-docstrings-ignore`: Specify files and patterns to exclude -- `.agent-docstrings-include`: Specify files and patterns to include (whitelist mode) -- Automatic integration with existing `.gitignore` files -- Support for glob patterns in configuration files - -### Documentation - -- Comprehensive README with usage examples -- Integration guides for pre-commit hooks and CI/CD -- Development setup instructions -- API documentation for programmatic usage - -## Version History - -- **1.0.1** - Parser and docstring handling improvements -- **1.0.0** - Initial stable release with multi-language support and filtering system -- **0.4.0** - (internal) -- **0.3.0** - (internal) -- **0.2.0** - (internal) -- **0.1.0** - Initial development version (internal) +# 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] + +### Planned Features + +- Additional language support: Swift, Perl, Curl, Fortran, Visual Basic, R, PHP, Lua, Bash, SQL +- Configuration file format validation +- Switching to the Abstract Syntax Tree (AST) +- [And more...](https://github.com/Artemonim/AgentDocstrings/issues) + +## [NextRelease] + +## [1.3.1] + +### Added + +- **Python Single-Line Docstrings**: Implemented support for identifying and merging the generated table of contents with existing single-line Python docstrings (`"""docstring"""`). + +### Fixed + +- **Python Docstring Generation**: Fixed a critical bug where repeatedly processing a Python file with a manual docstring would cause content duplication. The logic has been reworked to ensure correct placement of the generated table of contents relative to `from __future__ import` statements and existing docstrings. + +## [1.3.0] - 2025-06-30 + +### Added + +- **Process Individual Files**: The CLI now accepts both directory and individual file paths, allowing for more granular control over which files are processed. +- **Expanded Keywords**: Added a comprehensive list of keywords to `pyproject.toml` to significantly improve package discoverability on PyPI and search engines. +- **Beta Features Flag**: Introduced a `--beta` command-line flag to enable experimental features that are under development. + +### Changed + +- **Header Text Update**: Changed the auto-generated header to "Table of content is automatically generated by Agent Docstrings {version}". +- **Streamlined Header Format**: The format of the generated "Table of Contents" has been improved. Top-level functions and classes are now presented in a single, chronologically sorted list, removing the nested "Functions" section for a cleaner, more intuitive layout. +- **Deterministic Sorting**: All discovered items (classes, methods, functions) are now strictly sorted by their line number in the source file, ensuring a consistent and predictable output every time. +- **CLI Argument Renaming**: The `DIRECTORY` argument in the CLI has been renamed to `PATH` to accurately reflect its new capability to handle both files and directories. +- **Header Version Updates**: The tool will now update the header if the generator version has changed, even if the code structure remains the same, ensuring docstrings always reflect the version of the tool that generated them. +- **Repository URLs**: Updated project URLs in `pyproject.toml` to point to the correct `AgentDocstrings` repository name. + +### Fixed + +- **Error Handling for Inaccessible Directories**: Fixed a crash (`PermissionError`) that occurred when scanning directories with restricted read permissions. The application will now skip such directories and print a warning, preventing unintended modifications to files that might have been excluded by an unreadable `.gitignore` or other configuration files. +- **Deleting empty lines**: Detected and fixed the removal of empty lines at the end of processed files +- **Language-Specific Indentation**: Fixed the indentation in the generated 'Table of Contents' to respect common style conventions for each language (e.g., 4 spaces for Python, 2 for JavaScript). + +### Build + +- **Python Version Support**: Updated the minimum required Python version from 3.8 to 3.10 to align with modern dependencies and language features. Project metadata (`pyproject.toml`) and CI configurations have been updated accordingly. + +### CI/CD + +- **Continuous Integration Workflow**: Added a new GitHub Actions workflow (`ci.yml`) to automatically run tests on all pushes and pull requests to the `master` branch. The workflow tests against multiple Python versions (3.10-3.13) and includes matrix testing for beta features. + +### Documentation + +- **Complete README Overhaul**: The `README.md` has been completely rewritten to be more comprehensive, professional, and user-friendly. It now includes a clear project mission, a detailed table of contents, expanded sections on features and usage, new examples, and platform compatibility information. +- **Pull Request Template**: Added a `PULL_REQUEST_TEMPLATE.md` to standardize contributions. +- **Demo Video**: Included a new video in the `README.md` to demonstrate the tool's functionality. + +## [1.2.1] - 2025-06-30 + +### Added + +- **Python Docstring Merging**: Implemented a feature to merge the auto-generated header with existing manual module-level docstrings in Python files, preserving user-written content. + +### Changed + +- **Test Suite Refactoring**: Significantly refactored the test suite by introducing a `source_processor` fixture. This simplifies test code, removes boilerplate for file creation, and improves readability across all test files. + +### Documentation + +- Updated the repository URL in `README.md`. +- Reorganized `README.md` for better readability by moving the "Supported Languages" section to the top. + +## [1.2.0] - 2025-06-29 + +### Added + +- **Generator Versioning**: The tool's version is now embedded in the generated docstring for easier tracking and debugging. +- **Header Preservation**: Implemented intelligent detection to preserve file headers (e.g., shebangs, encoding declarations, Go package definitions, leading comments/imports) across all supported languages. +- **Expanded Language Support**: Added initial processing support and type mappings for Java, PowerShell, Delphi, and C. +- **Enhanced Testing**: Introduced new test suites for determinism, header preservation, and line number accuracy to ensure core feature reliability. +- **Initial release of `agent-docstrings`** + +### Changed + +- **Python Parser Overhaul**: Replaced the fragile regex-based Python parser with a robust implementation using Python's native Abstract Syntax Tree (`ast`) module. This provides highly accurate parsing of complex function signatures, decorators, type hints, and nested class structures. +- **Line Numbering Accuracy**: Completely reworked the line number calculation to account for preserved file headers and the size of the injected docstring, ensuring the table of contents is always accurate. + +### Fixed + +- **`__future__` Import Placement**: Corrected a critical bug where `from __future__ import` statements were incorrectly moved below the generated docstring, breaking Python file syntax. +- **Docstring Management**: Hardened the logic for identifying and removing agent-generated docstrings by using more specific start/end markers, preventing accidental modification of user-written docstrings. +- **Generic Parser**: Improved the generic parser for C-style languages, resolving a known bug that affected brace counting and failed C# file parsing. + +## [1.1.0] - 2025-06-29 + +### AST-parsing + +- **Go Language**: + - Implemented a new, high-precision AST parser using Go's native `go/parser` and `go/ast` libraries. This significantly improves the accuracy of identifying functions, methods, and types in `.go` files compared to previous methods. + - Added a `build_goparser.ps1` script to automate the compilation of the Go parser into an executable. + - Integrated the new parser into the main Python application, replacing the old logic for Go file analysis. + +## [1.0.1] - 2025-06-27 + +### Fixed + +- **Parser improvements**: + - Correctly identifies functions with `async def`. + - Better handling of functions with decorators. +- **Docstring placement**: + - Ensures auto-generated docstrings are placed after shebang (`#!`) and encoding (`# -*- coding: utf-8 -*-`) lines. +- **Unified docstring handling**: + - Intelligently integrates auto-generated docstrings into existing manual docstrings. + - Replaces content of existing auto-generated docstrings while preserving manual additions. + +## [1.0.0] - 2025-06-27 + +### Added + +- **Multi-language support**: Python, Java, Kotlin, Go, PowerShell, Delphi, C++, C#, JavaScript, TypeScript +- **Smart file filtering system**: + - Automatic `.gitignore` parsing and respect + - Custom blacklist support via `.agent-docstrings-ignore` files + - Custom whitelist support via `.agent-docstrings-include` files +- **Python version compatibility**: Full support for Python 3.8, 3.9, 3.10, 3.11, 3.12, and 3.13 +- **Type annotations**: Complete type hint support using `typing` module for backward compatibility +- **CLI interface**: Easy-to-use command-line tool with verbose output option +- **Programmatic API**: Import and use in other Python projects +- **Safe operation**: Only modifies auto-generated docstring blocks, preserves existing documentation +- **Incremental updates**: Only processes files when changes are detected + +### Technical Features + +- Uses `from __future__ import annotations` for forward compatibility +- Compatible with `typing.Union` and `typing.Tuple` for Python 3.8/3.9 +- No external dependencies - built on Python standard library only +- Comprehensive test suite with pytest +- Full type checking support with mypy +- Code formatting with black +- Proper packaging for PyPI distribution + +### Configuration + +- `.agent-docstrings-ignore`: Specify files and patterns to exclude +- `.agent-docstrings-include`: Specify files and patterns to include (whitelist mode) +- Automatic integration with existing `.gitignore` files +- Support for glob patterns in configuration files + +### Documentation + +- Comprehensive README with usage examples +- Integration guides for pre-commit hooks and CI/CD +- Development setup instructions +- API documentation for programmatic usage + +## Version History + +- **1.0.1** - Parser and docstring handling improvements +- **1.0.0** - Initial stable release with multi-language support and filtering system +- **0.4.0** - (internal) +- **0.3.0** - (internal) +- **0.2.0** - (internal) +- **0.1.0** - Initial development version (internal) diff --git a/agent_docstrings/__init__.py b/agent_docstrings/__init__.py index 5aea6e4..e58120e 100644 --- a/agent_docstrings/__init__.py +++ b/agent_docstrings/__init__.py @@ -7,4 +7,4 @@ Attributes: __version__ (str): Current version of the *agent-docstrings* package. """ -__version__ = "1.3.0" \ No newline at end of file +__version__ = "1.3.1" \ No newline at end of file diff --git a/agent_docstrings/core.py b/agent_docstrings/core.py index 0f57c03..8744083 100644 --- a/agent_docstrings/core.py +++ b/agent_docstrings/core.py @@ -412,7 +412,7 @@ def process_file(path: Path, verbose: bool = False, beta: bool = False) -> None: idx += 1 # Check for manual docstring start if idx < len(body_lines) and body_lines[idx].strip().startswith(('"""', "'''")): - delim = body_lines[idx].strip() + delim_line = body_lines[idx].strip() # Ensure it's not an existing auto-generated docstring marker_present = False for i in range(idx, min(idx + 5, len(body_lines))): @@ -422,12 +422,29 @@ def process_file(path: Path, verbose: bool = False, beta: bool = False) -> None: if not marker_present: # Find end of manual docstring end_idx = None - for j in range(idx + 1, len(body_lines)): - if body_lines[j].strip() == delim: - end_idx = j - break + manual_inner = [] + delim = None + + delim_quotes = '"""' if delim_line.startswith('"""') else "'''" + is_single_line = delim_line.endswith(delim_quotes) and delim_line != delim_quotes + + if is_single_line: + end_idx = idx + content_part = delim_line[len(delim_quotes):-len(delim_quotes)] + if content_part: + manual_inner = [content_part] + delim = delim_quotes + else: + # Multi-line docstring + delim = delim_line + for j in range(idx + 1, len(body_lines)): + if body_lines[j].strip() == delim: + end_idx = j + break + if end_idx is not None: + manual_inner = body_lines[idx + 1:end_idx] + if end_idx is not None: - manual_inner = body_lines[idx + 1:end_idx] # Compute auto header content lines with correct offset for merge # temp_header_lines holds the auto header lines including delimiters # content_lines length is temp_header_lines minus start/end markers diff --git a/pyproject.toml b/pyproject.toml index 4569ca3..a600663 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "agent-docstrings" -version = "1.3.0" +version = "1.3.1" description = "A command-line tool to auto-generate and update file-level docstrings summarizing classes and functions. Useful for maintaining a high-level overview of your files, especially in projects with code generated or modified by AI assistants." readme = { file = "README.md", content-type = "text/markdown" } license = { file = "LICENSE" } @@ -148,7 +148,7 @@ exclude_lines = [ ] [tool.bumpversion] -current_version = "1.3.0" +current_version = "1.3.1" commit = false tag = false diff --git a/tests/fixtures/python_with_manual_docstring.py b/tests/fixtures/python_with_manual_docstring.py new file mode 100644 index 0000000..49d28db --- /dev/null +++ b/tests/fixtures/python_with_manual_docstring.py @@ -0,0 +1,10 @@ +"""This is a manual docstring. +It has multiple lines. +The generator should not break it. +""" + +class MyClass: + """A simple example class.""" + def my_method(self, arg1: int) -> str: + """A simple example method.""" + return f"Hello {arg1}" \ No newline at end of file diff --git a/tests/test_common.py b/tests/test_common.py index 716a221..5945644 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1,262 +1,262 @@ -from __future__ import annotations - -""" - --- AUTO-GENERATED DOCSTRING --- - Table of content is automatically generated by Agent Docstrings v1.3.0 - - Classes/Functions: - - TestDataClasses (line 43): - - test_signature_info_creation() -> None (line 46) - - test_class_info_creation() -> None (line 52) - - test_comment_style_creation() -> None (line 71) - - TestCommentStyles (line 80): - - test_all_supported_languages_have_styles() -> None (line 83) - - test_comment_style_values(language: str, expected_start: str, expected_end: str, expected_prefix: str, expected_indent: str) -> None (line 100) - - TestHeaderStripping (line 116): - - test_strip_python_header() -> None (line 119) - - test_strip_block_comment_header() -> None (line 144) - - test_strip_c_style_comment_header() -> None (line 162) - - test_no_header_to_strip() -> None (line 180) - - test_preserve_shebang_when_stripping() -> None (line 189) - - test_strip_header_with_various_whitespace() -> None (line 202) - - test_strip_only_first_matching_header() -> None (line 210) - - test_strip_header_edge_cases() -> None (line 226) - - test_header_not_at_start() -> None (line 238) - - test_invalid_language_patterns(language: str) -> None (line 252) - --- END AUTO-GENERATED DOCSTRING --- -""" -"""Tests for agent_docstrings.languages.common module.""" - - -import pytest - -from agent_docstrings.languages.common import ( - COMMENT_STYLES, - ClassInfo, - SignatureInfo, - CommentStyle, - remove_agent_docstring, - DOCSTRING_START_MARKER, - DOCSTRING_END_MARKER, -) - - -class TestDataClasses: - """Tests for data classes used in parsing.""" - - def test_signature_info_creation(self) -> None: - """Test SignatureInfo namedtuple creation and access.""" - sig = SignatureInfo(signature="test_function(param: str) -> int", line=42) - assert sig.signature == "test_function(param: str) -> int" - assert sig.line == 42 - - def test_class_info_creation(self) -> None: - """Test ClassInfo namedtuple creation and access.""" - method = SignatureInfo(signature="method()", line=2) - inner_class = ClassInfo(name="Inner", line=3, methods=[], inner_classes=[]) - - cls = ClassInfo( - name="TestClass", - line=1, - methods=[method], - inner_classes=[inner_class] - ) - - assert cls.name == "TestClass" - assert cls.line == 1 - assert len(cls.methods) == 1 - assert cls.methods[0] == method - assert len(cls.inner_classes) == 1 - assert cls.inner_classes[0] == inner_class - - def test_comment_style_creation(self) -> None: - """Test CommentStyle namedtuple creation.""" - style = CommentStyle(start="/*", end="*/", prefix=" * ", indent=" ") - assert style.start == "/*" - assert style.end == "*/" - assert style.prefix == " * " - assert style.indent == " " - - -class TestCommentStyles: - """Tests for comment style definitions.""" - - def test_all_supported_languages_have_styles(self) -> None: - """Ensure all supported languages have comment style definitions.""" - expected_languages = { - "python", "kotlin", "javascript", "typescript", "csharp", "cpp", - "c", "java", "go", "powershell", "delphi" - } - assert set(COMMENT_STYLES.keys()) == expected_languages - - @pytest.mark.parametrize("language,expected_start,expected_end,expected_prefix,expected_indent", [ - ("python", '"""', '"""', " ", " "), - ("kotlin", '/**', ' */', ' * ', " "), - ("javascript", '/**', ' */', ' * ', " "), - ("typescript", '/**', ' */', ' * ', " "), - ("csharp", '/*', ' */', ' * ', " "), - ("cpp", '/*', ' */', ' * ', " "), - ("go", '/*', ' */', ' * ', "\t"), - ]) - def test_comment_style_values( - self, - language: str, - expected_start: str, - expected_end: str, - expected_prefix: str, - expected_indent: str - ) -> None: - """Test specific comment style values for each language.""" - style = COMMENT_STYLES[language] - assert style.start == expected_start - assert style.end == expected_end - assert style.prefix == expected_prefix - assert style.indent == expected_indent - - -class TestHeaderStripping: - """Tests for remove_agent_docstring function.""" - - def test_strip_python_header(self) -> None: - """Test stripping Python docstring headers.""" - content = f'''"""{DOCSTRING_START_MARKER} - - TestClass (line 5): - - method(self) (line 6) - - Functions: - - function() (line 10) -{DOCSTRING_END_MARKER}""" -class TestClass: - def method(self): - pass - -def function(): - pass''' - - expected = '''class TestClass: - def method(self): - pass - -def function(): - pass''' - - result = remove_agent_docstring(content, "python") - assert result.strip() == expected.strip() - - def test_strip_block_comment_header(self) -> None: - """Test stripping block comment headers for C-style languages.""" - content = f'''/**{DOCSTRING_START_MARKER} - * - TestClass (line 8): - * - method() (line 9) - {DOCSTRING_END_MARKER}*/ -class TestClass {{ - void method() {{}} -}}''' - - expected = '''class TestClass { - void method() {} -}''' - - for language in ["kotlin", "javascript", "typescript"]: - result = remove_agent_docstring(content, language) - assert result.strip() == expected.strip() - - def test_strip_c_style_comment_header(self) -> None: - """Test stripping C-style comment headers.""" - content = f'''/*{DOCSTRING_START_MARKER} - * - Calculator (line 6): - * - add(int, int) (line 7) - {DOCSTRING_END_MARKER}*/ -class Calculator {{ - int add(int a, int b) {{ return a + b; }} -}}''' - - expected = '''class Calculator { - int add(int a, int b) { return a + b; } -}''' - - for language in ["csharp", "cpp"]: - result = remove_agent_docstring(content, language) - assert result.strip() == expected.strip() - - def test_no_header_to_strip(self) -> None: - """Test that content without headers remains unchanged.""" - content = '''class TestClass: - def method(self): - pass''' - - result = remove_agent_docstring(content, "python") - assert result == content - - def test_preserve_shebang_when_stripping(self) -> None: - """Test that shebangs are preserved during header stripping.""" - content = f'''#!/usr/bin/env python3 -"""{DOCSTRING_START_MARKER} - - TestClass (line 6): -{DOCSTRING_END_MARKER}""" -class TestClass: - pass''' - - result = remove_agent_docstring(content, "python") - assert result.strip().startswith("#!/usr/bin/env python3") - assert "class TestClass:" in result - - def test_strip_header_with_various_whitespace(self) -> None: - """Test header stripping with different whitespace patterns.""" - base_content = f'"""{DOCSTRING_START_MARKER}\n - Test (line 4):\n{DOCSTRING_END_MARKER}"""\nclass Test: pass' - - result = remove_agent_docstring(base_content, "python") - assert DOCSTRING_START_MARKER not in result - assert "class Test: pass" in result - - def test_strip_only_first_matching_header(self) -> None: - """Test that only the first matching header is stripped.""" - content = f'''"""{DOCSTRING_START_MARKER} - - FirstClass (line 6): -{DOCSTRING_END_MARKER}""" -class FirstClass: - def method(self): - """ - This should not be stripped - """ - pass''' - - result = remove_agent_docstring(content, "python") - assert result.count(DOCSTRING_START_MARKER) == 0 - assert "This should not be stripped" in result - - def test_strip_header_edge_cases(self) -> None: - """Test edge cases in header stripping.""" - assert remove_agent_docstring("", "python") == "" - - header_only = f'"""{DOCSTRING_START_MARKER}\n - Test (line 4):\n{DOCSTRING_END_MARKER}"""' - result = remove_agent_docstring(header_only, "python") - assert result == "" - - no_newline = f'"""{DOCSTRING_START_MARKER}\n{DOCSTRING_END_MARKER}"""class Test: pass' - result = remove_agent_docstring(no_newline, "python") - assert result == "class Test: pass" - - def test_header_not_at_start(self) -> None: - """Test that headers not at the start of file are not stripped.""" - content = f'''class SomeClass: - pass - -"""{DOCSTRING_START_MARKER} - - This should not be stripped -{DOCSTRING_END_MARKER}"""''' - - result = remove_agent_docstring(content, "python") - assert "class SomeClass:" in result - assert DOCSTRING_START_MARKER in result - - @pytest.mark.parametrize("language", ["python", "kotlin", "javascript", "typescript", "csharp", "cpp"]) - def test_invalid_language_patterns(self, language: str) -> None: - invalid_contents = [ - "Classes/Functions: but not in a comment", - "/* Classes/Functions: but not closed properly", - '""" Classes/Functions: but missing closing quotes', - ] - - for content in invalid_contents: - result = remove_agent_docstring(content, language) +from __future__ import annotations + +""" + --- AUTO-GENERATED DOCSTRING --- + Table of content is automatically generated by Agent Docstrings v1.3.1 + + Classes/Functions: + - TestDataClasses (line 40): + - test_signature_info_creation() -> None (line 43) + - test_class_info_creation() -> None (line 49) + - test_comment_style_creation() -> None (line 68) + - TestCommentStyles (line 77): + - test_all_supported_languages_have_styles() -> None (line 80) + - test_comment_style_values(language: str, expected_start: str, expected_end: str, expected_prefix: str, expected_indent: str) -> None (line 97) + - TestHeaderStripping (line 113): + - test_strip_python_header() -> None (line 116) + - test_strip_block_comment_header() -> None (line 141) + - test_strip_c_style_comment_header() -> None (line 159) + - test_no_header_to_strip() -> None (line 177) + - test_preserve_shebang_when_stripping() -> None (line 186) + - test_strip_header_with_various_whitespace() -> None (line 199) + - test_strip_only_first_matching_header() -> None (line 207) + - test_strip_header_edge_cases() -> None (line 223) + - test_header_not_at_start() -> None (line 235) + - test_invalid_language_patterns(language: str) -> None (line 249) + --- END AUTO-GENERATED DOCSTRING --- +Tests for agent_docstrings.languages.common module. +""" + + +import pytest + +from agent_docstrings.languages.common import ( + COMMENT_STYLES, + ClassInfo, + SignatureInfo, + CommentStyle, + remove_agent_docstring, + DOCSTRING_START_MARKER, + DOCSTRING_END_MARKER, +) + + +class TestDataClasses: + """Tests for data classes used in parsing.""" + + def test_signature_info_creation(self) -> None: + """Test SignatureInfo namedtuple creation and access.""" + sig = SignatureInfo(signature="test_function(param: str) -> int", line=42) + assert sig.signature == "test_function(param: str) -> int" + assert sig.line == 42 + + def test_class_info_creation(self) -> None: + """Test ClassInfo namedtuple creation and access.""" + method = SignatureInfo(signature="method()", line=2) + inner_class = ClassInfo(name="Inner", line=3, methods=[], inner_classes=[]) + + cls = ClassInfo( + name="TestClass", + line=1, + methods=[method], + inner_classes=[inner_class] + ) + + assert cls.name == "TestClass" + assert cls.line == 1 + assert len(cls.methods) == 1 + assert cls.methods[0] == method + assert len(cls.inner_classes) == 1 + assert cls.inner_classes[0] == inner_class + + def test_comment_style_creation(self) -> None: + """Test CommentStyle namedtuple creation.""" + style = CommentStyle(start="/*", end="*/", prefix=" * ", indent=" ") + assert style.start == "/*" + assert style.end == "*/" + assert style.prefix == " * " + assert style.indent == " " + + +class TestCommentStyles: + """Tests for comment style definitions.""" + + def test_all_supported_languages_have_styles(self) -> None: + """Ensure all supported languages have comment style definitions.""" + expected_languages = { + "python", "kotlin", "javascript", "typescript", "csharp", "cpp", + "c", "java", "go", "powershell", "delphi" + } + assert set(COMMENT_STYLES.keys()) == expected_languages + + @pytest.mark.parametrize("language,expected_start,expected_end,expected_prefix,expected_indent", [ + ("python", '"""', '"""', " ", " "), + ("kotlin", '/**', ' */', ' * ', " "), + ("javascript", '/**', ' */', ' * ', " "), + ("typescript", '/**', ' */', ' * ', " "), + ("csharp", '/*', ' */', ' * ', " "), + ("cpp", '/*', ' */', ' * ', " "), + ("go", '/*', ' */', ' * ', "\t"), + ]) + def test_comment_style_values( + self, + language: str, + expected_start: str, + expected_end: str, + expected_prefix: str, + expected_indent: str + ) -> None: + """Test specific comment style values for each language.""" + style = COMMENT_STYLES[language] + assert style.start == expected_start + assert style.end == expected_end + assert style.prefix == expected_prefix + assert style.indent == expected_indent + + +class TestHeaderStripping: + """Tests for remove_agent_docstring function.""" + + def test_strip_python_header(self) -> None: + """Test stripping Python docstring headers.""" + content = f'''"""{DOCSTRING_START_MARKER} + - TestClass (line 5): + - method(self) (line 6) + - Functions: + - function() (line 10) +{DOCSTRING_END_MARKER}""" +class TestClass: + def method(self): + pass + +def function(): + pass''' + + expected = '''class TestClass: + def method(self): + pass + +def function(): + pass''' + + result = remove_agent_docstring(content, "python") + assert result.strip() == expected.strip() + + def test_strip_block_comment_header(self) -> None: + """Test stripping block comment headers for C-style languages.""" + content = f'''/**{DOCSTRING_START_MARKER} + * - TestClass (line 8): + * - method() (line 9) + {DOCSTRING_END_MARKER}*/ +class TestClass {{ + void method() {{}} +}}''' + + expected = '''class TestClass { + void method() {} +}''' + + for language in ["kotlin", "javascript", "typescript"]: + result = remove_agent_docstring(content, language) + assert result.strip() == expected.strip() + + def test_strip_c_style_comment_header(self) -> None: + """Test stripping C-style comment headers.""" + content = f'''/*{DOCSTRING_START_MARKER} + * - Calculator (line 6): + * - add(int, int) (line 7) + {DOCSTRING_END_MARKER}*/ +class Calculator {{ + int add(int a, int b) {{ return a + b; }} +}}''' + + expected = '''class Calculator { + int add(int a, int b) { return a + b; } +}''' + + for language in ["csharp", "cpp"]: + result = remove_agent_docstring(content, language) + assert result.strip() == expected.strip() + + def test_no_header_to_strip(self) -> None: + """Test that content without headers remains unchanged.""" + content = '''class TestClass: + def method(self): + pass''' + + result = remove_agent_docstring(content, "python") + assert result == content + + def test_preserve_shebang_when_stripping(self) -> None: + """Test that shebangs are preserved during header stripping.""" + content = f'''#!/usr/bin/env python3 +"""{DOCSTRING_START_MARKER} + - TestClass (line 6): +{DOCSTRING_END_MARKER}""" +class TestClass: + pass''' + + result = remove_agent_docstring(content, "python") + assert result.strip().startswith("#!/usr/bin/env python3") + assert "class TestClass:" in result + + def test_strip_header_with_various_whitespace(self) -> None: + """Test header stripping with different whitespace patterns.""" + base_content = f'"""{DOCSTRING_START_MARKER}\n - Test (line 4):\n{DOCSTRING_END_MARKER}"""\nclass Test: pass' + + result = remove_agent_docstring(base_content, "python") + assert DOCSTRING_START_MARKER not in result + assert "class Test: pass" in result + + def test_strip_only_first_matching_header(self) -> None: + """Test that only the first matching header is stripped.""" + content = f'''"""{DOCSTRING_START_MARKER} + - FirstClass (line 6): +{DOCSTRING_END_MARKER}""" +class FirstClass: + def method(self): + """ + This should not be stripped + """ + pass''' + + result = remove_agent_docstring(content, "python") + assert result.count(DOCSTRING_START_MARKER) == 0 + assert "This should not be stripped" in result + + def test_strip_header_edge_cases(self) -> None: + """Test edge cases in header stripping.""" + assert remove_agent_docstring("", "python") == "" + + header_only = f'"""{DOCSTRING_START_MARKER}\n - Test (line 4):\n{DOCSTRING_END_MARKER}"""' + result = remove_agent_docstring(header_only, "python") + assert result == "" + + no_newline = f'"""{DOCSTRING_START_MARKER}\n{DOCSTRING_END_MARKER}"""class Test: pass' + result = remove_agent_docstring(no_newline, "python") + assert result == "class Test: pass" + + def test_header_not_at_start(self) -> None: + """Test that headers not at the start of file are not stripped.""" + content = f'''class SomeClass: + pass + +"""{DOCSTRING_START_MARKER} + - This should not be stripped +{DOCSTRING_END_MARKER}"""''' + + result = remove_agent_docstring(content, "python") + assert "class SomeClass:" in result + assert DOCSTRING_START_MARKER in result + + @pytest.mark.parametrize("language", ["python", "kotlin", "javascript", "typescript", "csharp", "cpp"]) + def test_invalid_language_patterns(self, language: str) -> None: + invalid_contents = [ + "Classes/Functions: but not in a comment", + "/* Classes/Functions: but not closed properly", + '""" Classes/Functions: but missing closing quotes', + ] + + for content in invalid_contents: + result = remove_agent_docstring(content, language) assert result == content \ No newline at end of file diff --git a/tests/test_core.py b/tests/test_core.py index 7c9e96a..ebb81ab 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -22,7 +22,6 @@ - test_process_unsupported_extension(tmp_path: Path) -> None (line 249) - test_process_python_file_creates_header(source_processor) -> None (line 258) - test_process_file_with_existing_header(source_processor) -> None (line 276) - - test_process_file_merges_manual_docstring(source_processor) -> None (line 292) - test_process_file_preserves_shebang(source_processor) -> None (line 312) - test_process_empty_file(tmp_path: Path) -> None (line 326) - test_process_file_no_classes_or_functions(tmp_path: Path) -> None (line 335) @@ -290,26 +289,6 @@ def new_method(self): assert "NewClass" in processed_content assert "OldClass" not in processed_content - def test_process_file_merges_manual_docstring(self, source_processor) -> None: - """Test merging manual docstring with auto-generated header into a single docstring.""" - content = dedent(''' - """ - Manual header explaining the module. - - """ - def foo(): - return 1 - ''').strip() - processed_content, lines, _ = source_processor("test.py", content) - # Only one docstring should exist - assert processed_content.count('"""') == 2 - # Auto-generated marker should be present - assert '--- AUTO-GENERATED DOCSTRING ---' in processed_content - # Manual header text should still be present - assert 'Manual header explaining the module.' in processed_content - # Code should follow the docstring - assert 'def foo' in processed_content - def test_process_file_preserves_shebang(self, source_processor) -> None: """Test that shebang lines are preserved.""" content = '''#!/usr/bin/env python3 @@ -320,9 +299,10 @@ def main(): main()''' processed_content, _, _ = source_processor("script.py", content) - + assert processed_content.startswith("#!/usr/bin/env python3") - assert "Classes/Functions:" in processed_content + assert "def main" in processed_content + assert "--- AUTO-GENERATED DOCSTRING ---" in processed_content def test_process_empty_file(self, tmp_path: Path) -> None: """Test processing an empty file.""" diff --git a/tests/test_determinism.py b/tests/test_determinism.py index 61b55fb..0b72f15 100644 --- a/tests/test_determinism.py +++ b/tests/test_determinism.py @@ -8,9 +8,36 @@ """ import pytest from agent_docstrings.core import process_file +from pathlib import Path # determinism test ensures that after inserting header once, subsequent runs do not modify the file +def test_process_file_determinism_with_manual_python_docstring(tmp_path): + """ + Tests that a Python file with a pre-existing manual docstring is handled + correctly and idempotently, by placing the agent docstring before the + manual one without duplication on subsequent runs. + """ + # Prepare the test file + fixture_path = Path("tests/fixtures/python_with_manual_docstring.py") + original_content = fixture_path.read_text(encoding="utf-8") + test_file_path = tmp_path / "test.py" + test_file_path.write_text(original_content, encoding="utf-8") + + # First run: should add the agent docstring + process_file(test_file_path) + content_after_first_run = test_file_path.read_text(encoding="utf-8") + + # Second run: should NOT change the file + process_file(test_file_path) + content_after_second_run = test_file_path.read_text(encoding="utf-8") + + # Assert that the first run actually added the docstring + assert original_content != content_after_first_run + # Assert that the second run made no changes + assert content_after_first_run == content_after_second_run + + def test_process_file_determinism(sample_files_by_language): """ Processes each sample file three times and asserts that after the first processing, From 6a357e301da7893a8e2c05ec2259f4f94a8ab11a Mon Sep 17 00:00:00 2001 From: Artemonim Date: Tue, 1 Jul 2025 11:00:07 +0300 Subject: [PATCH 2/5] ci update --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b43d1d..e07969a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [master] + branches: [master, dev] pull_request_target: - branches: [master] + branches: [master, dev] jobs: test: @@ -58,6 +58,7 @@ jobs: report: name: Report Coverage + if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest needs: test steps: @@ -77,3 +78,22 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} directory: ./coverage-artifacts/ fail_ci_if_error: false + + check-version: + name: Check for accidental version bump + if: github.base_ref == 'dev' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Verify that version was not bumped + run: | + if ! git diff --quiet origin/dev HEAD -- pyproject.toml; then + echo "::error::Version in pyproject.toml was changed in a PR to dev." + echo "Version bumping should only happen in a release PR to master." + exit 1 + fi + echo "Version check passed for pyproject.toml" From b70aa9f598267ce71c6a1e31b42eb5ea6b7d669b Mon Sep 17 00:00:00 2001 From: Artemonim Date: Tue, 1 Jul 2025 11:02:46 +0300 Subject: [PATCH 3/5] changelog update --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 219aa49..d604f4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Python Docstring Generation**: Fixed a critical bug where repeatedly processing a Python file with a manual docstring would cause content duplication. The logic has been reworked to ensure correct placement of the generated table of contents relative to `from __future__ import` statements and existing docstrings. +### CI/CD + +- **CI dev**: The CI pipeline now runs on the `dev` branch, with Codecov reports limited to `master`. +- **Version check**: Added a new check to prevent accidental version bumps in feature branches. + ## [1.3.0] - 2025-06-30 ### Added From cce97b3550af479882b8ef9a672a2f23ed3cfc18 Mon Sep 17 00:00:00 2001 From: Artemonim Date: Tue, 1 Jul 2025 11:13:52 +0300 Subject: [PATCH 4/5] Remove redundant tests for version bump without changes in `test_core.py` --- tests/test_core.py | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index ebb81ab..96845db 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -32,8 +32,6 @@ - test_process_file_read_error(mock_read, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None (line 403) - test_process_file_write_error(mock_write, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None (line 414) - test_process_file_parser_error(source_processor) -> None (line 425) - - test_process_file_version_bump_no_changes(source_processor) -> None (line 430) - - test_process_file_version_bump_no_changes_verbose(source_processor, capsys: pytest.CaptureFixture[str]) -> None (line 449) - TestDiscoverAndProcessFiles (line 468): - test_discover_single_directory(tmp_path: Path) -> None (line 471) - test_discover_multiple_directories(tmp_path: Path) -> None (line 486) @@ -408,43 +406,6 @@ def test_process_file_parser_error(self, source_processor) -> None: # It should not crash source_processor("malformed.py", "def func(a,:", verbose=True) - def test_process_file_version_bump_no_changes(self, source_processor) -> None: - """Test that version bump in header without code changes updates the docstring to current version.""" - content = '''""" - --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. - - Classes/Functions: - - foo() (line 1) - --- END AUTO-GENERATED DOCSTRING --- -""" - def foo(): - pass''' - processed_content, _, _ = source_processor("test.py", content, verbose=False) - # The header version should be updated to the current version - assert "generated by Agent Docstrings v" in processed_content - assert "v1.3.0" in processed_content - assert "v1.2.0" not in processed_content - - def test_process_file_version_bump_no_changes_verbose(self, source_processor, capsys: pytest.CaptureFixture[str]) -> None: - """Test that version-only header changes produce a processed message when verbose.""" - content = '''""" - --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. - - Classes/Functions: - - foo() (line 1) - --- END AUTO-GENERATED DOCSTRING --- -""" - def foo(): - pass''' - source_processor("test.py", content, verbose=True) - captured = capsys.readouterr() - # Should indicate that the file was processed - assert "Processed Python:" in captured.out - class TestDiscoverAndProcessFiles: """Tests for discover_and_process_files function.""" From 69a6ae6462e330ace2840be411435e9cde27c809 Mon Sep 17 00:00:00 2001 From: Artemonim Date: Tue, 1 Jul 2025 11:40:06 +0300 Subject: [PATCH 5/5] ci optimization --- .github/workflows/ci.yml | 211 +++++++++++++++++++++------------------ 1 file changed, 112 insertions(+), 99 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e07969a..b976939 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,99 +1,112 @@ -name: CI - -on: - push: - branches: [master, dev] - pull_request_target: - branches: [master, dev] - -jobs: - test: - name: Test on Python ${{ matrix.python-version }} (beta=${{ matrix.beta }}) - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] - beta: [false, true] - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: "1.22" - - - name: Build Go parsers - run: pwsh -File ./build_goparser.ps1 - shell: bash - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install .[dev] - - - name: CLI smoke test - run: | - agent-docstrings --version - if [ "${{ matrix.beta }}" = "true" ]; then - agent-docstrings --beta --version - fi - - - name: Run tests with coverage - run: | - pytest --cov=agent_docstrings --cov-report=xml --cov-report=term-missing - - - name: Upload coverage artifact - uses: actions/upload-artifact@v4 - with: - name: coverage-${{ matrix.python-version }}-${{ matrix.beta }} - path: coverage.xml - - report: - name: Report Coverage - if: github.ref == 'refs/heads/master' - runs-on: ubuntu-latest - needs: test - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Download all coverage artifacts - uses: actions/download-artifact@v4 - with: - path: coverage-artifacts - pattern: coverage-* - merge-multiple: true - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - directory: ./coverage-artifacts/ - fail_ci_if_error: false - - check-version: - name: Check for accidental version bump - if: github.base_ref == 'dev' - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Verify that version was not bumped - run: | - if ! git diff --quiet origin/dev HEAD -- pyproject.toml; then - echo "::error::Version in pyproject.toml was changed in a PR to dev." - echo "Version bumping should only happen in a release PR to master." - exit 1 - fi - echo "Version check passed for pyproject.toml" +name: CI + +on: + # Run on pull requests into dev or master. + pull_request: + branches: [master, dev] + # After a PR is merged, the merge commit is pushed to master; we still want tests + coverage once on the resulting commit. + push: + branches: [master] + +jobs: + test: + # * Runs unit-test matrix: + # - Always on pull_request (dev or master) + # - On push to master (after merge) + if: | + github.event_name == 'pull_request' || + (github.event_name == 'push' && github.ref == 'refs/heads/master') + name: Test on Python ${{ matrix.python-version }} (beta=${{ matrix.beta }}) + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + beta: [false, true] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + # For pull_request we check out the PR commit; for push we stay on the pushed ref (master). + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.22" + + - name: Build Go parsers + run: pwsh -File ./build_goparser.ps1 + shell: bash + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[dev] + + - name: CLI smoke test + run: | + agent-docstrings --version + if [ "${{ matrix.beta }}" = "true" ]; then + agent-docstrings --beta --version + fi + + - name: Run tests with coverage + run: | + pytest --cov=agent_docstrings --cov-report=xml --cov-report=term-missing + + - name: Upload coverage artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-${{ matrix.python-version }}-${{ matrix.beta }} + path: coverage.xml + + report: + # * Only for master: either in PR to master (so reviewers see comment) or after merge push to master. + if: | + (github.event_name == 'pull_request' && github.base_ref == 'master') || + (github.event_name == 'push' && github.ref == 'refs/heads/master') + name: Report Coverage + runs-on: ubuntu-latest + needs: test + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download all coverage artifacts + uses: actions/download-artifact@v4 + with: + path: coverage-artifacts + pattern: coverage-* + merge-multiple: true + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + directory: ./coverage-artifacts/ + fail_ci_if_error: false + + check-version: + # * Only on PRs into dev: prevent accidental version bumps. + if: github.event_name == 'pull_request' && github.base_ref == 'dev' + name: Check for accidental version bump + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Verify that version was not bumped + run: | + if ! git diff --quiet origin/dev HEAD -- pyproject.toml; then + echo "::error::Version in pyproject.toml was changed in a PR to dev." + echo "Version bumping should only happen in a release PR to master." + exit 1 + fi + echo "Version check passed for pyproject.toml"