diff --git a/agents/clean-code-agent/README.md b/agents/clean-code-agent/README.md new file mode 100644 index 0000000..b0977ce --- /dev/null +++ b/agents/clean-code-agent/README.md @@ -0,0 +1,44 @@ +# Clean Code Agent + +The Clean Code Agent is an AI-powered tool designed to help developers write more readable, maintainable, and well-structured code. It analyzes your source code and provides actionable feedback based on established clean code principles. + +## Key Features + +- **Automated Code Refactoring:** Goes beyond simple analysis by providing `diff`-style previews for fixing issues related to naming, complexity, and code smells. +- **Automatic Docstring Generation:** Identifies public functions missing documentation and generates complete docstrings, including parameters and return values. +- **Unit Test Scaffolding:** Creates basic unit test files for your modules, encouraging a test-driven development culture. +- **Linter Integration:** Can run a command-line linter (e.g., ESLint, Pylint) and use its output as a baseline for its own, more context-aware analysis. +- **Customizable Rule Sets:** Allows teams to enforce their own coding standards by providing a custom rules file. +- **CI/CD Integration:** Includes ready-to-use configuration examples for GitHub Actions, GitLab CI, Jenkins, and Azure DevOps to automate code quality checks in your pipeline. +- **Structured Output:** Produces machine-readable JSON output, perfect for integrations and quality gating. + +## How to Use + +You can run the Clean Code Agent using the `qodo` command-line tool. + +### Basic Analysis + +```bash +qodo clean_code --set files="path/to/your/code" --set language="your_language" +``` + +### Generating Docstrings and Tests + +```bash +qodo clean_code --set files="src/my_module.py" --set language="python" --set generate_docstrings=true --set generate_tests=true +``` + +### CI/CD Integration + +For examples of how to integrate the agent into your CI/CD pipeline, see the configuration files in the `examples/ci-configs` directory. + +## Arguments + +- `files` (required): A comma-separated list of file paths or directories to analyze. +- `language` (required): The programming language of the files (e.g., 'python', 'javascript'). +- `report_format` (optional): The format for the output report ('markdown' or 'json'). Defaults to 'markdown'. +- `linter_command` (optional): A linter command to run before analysis. +- `rules_file` (optional): A path to a file containing custom analysis rules. +- `complexity_analysis` (optional): Whether to perform and report on cyclomatic complexity. Defaults to `false`. +- `generate_docstrings` (optional): If true, generates missing docstrings for public functions. Defaults to `false`. +- `generate_tests` (optional): If true, generates a scaffold for unit tests. Defaults to `false`. diff --git a/agents/clean-code-agent/agent.toml b/agents/clean-code-agent/agent.toml new file mode 100644 index 0000000..03f7bef --- /dev/null +++ b/agents/clean-code-agent/agent.toml @@ -0,0 +1,100 @@ +version = "1.0" + +[commands.clean_code] +description = "An agent that analyzes code for cleanliness, readability, and maintainability, providing suggestions for improvement." + +instructions = """ +You are an expert AI assistant specializing in writing clean, maintainable, and well-documented code. Your primary goal is to improve code quality by not only identifying issues but also by actively generating missing documentation and tests. + +**Core Analysis Task:** +Analyze the provided source code files for readability, maintainability, and structure. For each issue, provide the file path, line number, a clear description, and a 'diff-style' refactoring preview. + +You should look for: +- **Meaningful Naming:** Unclear variable, function, or class names. +- **Function Complexity:** Functions that are too long or have too many responsibilities. +- **Code Smells:** Duplicated code, dead code, long parameter lists. +- **Documentation Quality:** Unclear, outdated, or redundant comments. + +**Advanced Generation Tasks:** + +1. **Docstring Generation (`generate_docstrings` = true):** + - Identify any public function or method that is missing a docstring. + - For each one, generate a complete and accurate docstring that describes the function's purpose, its arguments (`Args:`), and what it returns (`Returns:`). + - The generated docstring should be included in the `suggestion` part of the output. + +2. **Unit Test Scaffolding (`generate_tests` = true):** + - For each source file, create a corresponding test file (e.g., `test_my_module.py` for `my_module.py`). + - In the test file, generate a basic unit test class with placeholder test methods for each public function in the source file. + - The test methods should include a simple `self.fail("Test not implemented")` or equivalent. + - The path to the newly created test file should be included in the output. + +**Execution Flow:** +- If a `linter_command` is provided, run it first and use its output to inform your analysis. +- If a `rules_file` is provided, prioritize those rules. +- Perform the core analysis and the advanced generation tasks as requested by the input arguments. +""" + +arguments = [ + { name = "files", type = "string", required = true, description = "A comma-separated list of file paths or directories to analyze." }, + { name = "language", type = "string", required = true, description = "The programming language of the files (e.g., 'python', 'javascript')." }, + { name = "report_format", type = "string", required = false, default = "markdown", description = "The format for the output report ('markdown' or 'json')." }, + { name = "linter_command", type = "string", required = false, description = "An optional linter command to run before analysis (e.g., 'eslint .')." }, + { name = "rules_file", type = "string", required = false, description = "An optional path to a file containing custom analysis rules." }, + { name = "complexity_analysis", type = "boolean", required = false, default = false, description = "Whether to perform and report on cyclomatic complexity." }, + { name = "generate_docstrings", type = "boolean", required = false, default = false, description = "If true, generates missing docstrings for public functions." }, + { name = "generate_tests", type = "boolean", required = false, default = false, description = "If true, generates a scaffold for unit tests." } +] + +tools = ["filesystem", "shell"] +execution_strategy = "plan" + +output_schema = """ +{ + "type": "object", + "properties": { + "summary": { + "type": "object", + "properties": { + "files_analyzed": {"type": "number"}, + "issues_found": {"type": "number"}, + "docstrings_generated": {"type": "number"}, + "test_files_created": {"type": "number"} + } + }, + "issues": { + "type": "array", + "description": "A list of clean code issues found and refactoring suggestions.", + "items": { + "type": "object", + "properties": { + "file": {"type": "string"}, + "line": {"type": "number"}, + "description": {"type": "string"}, + "suggestion": { + "type": "object", + "properties": { + "code_to_remove": {"type": "string"}, + "code_to_add": {"type": "string"} + } + }, + "cyclomatic_complexity": { + "type": "number", + "description": "The calculated cyclomatic complexity of the function or method." + } + }, + "required": ["file", "line", "description", "suggestion"] + } + }, + "generated_test_files": { + "type": "array", + "description": "A list of paths to the newly created unit test scaffold files.", + "items": { + "type": "string" + } + } + }, + "required": ["summary", "issues"] +} +""" + +exit_expression = "summary.issues_found == 0" diff --git a/agents/clean-code-agent/agent.yaml b/agents/clean-code-agent/agent.yaml new file mode 100644 index 0000000..334c40c --- /dev/null +++ b/agents/clean-code-agent/agent.yaml @@ -0,0 +1,120 @@ +version: "1.0" +commands: + clean_code: + description: "An agent that analyzes code for cleanliness, readability, and maintainability, providing suggestions for improvement." + instructions: | + You are an expert AI assistant specializing in writing clean, maintainable, and well-documented code. Your primary goal is to improve code quality by not only identifying issues but also by actively generating missing documentation and tests. + + **Core Analysis Task:** + Analyze the provided source code files for readability, maintainability, and structure. For each issue, provide the file path, line number, a clear description, and a 'diff-style' refactoring preview. + + You should look for: + - **Meaningful Naming:** Unclear variable, function, or class names. + - **Function Complexity:** Functions that are too long or have too many responsibilities. + - **Code Smells:** Duplicated code, dead code, long parameter lists. + - **Documentation Quality:** Unclear, outdated, or redundant comments. + + **Advanced Generation Tasks:** + + 1. **Docstring Generation (`generate_docstrings` = true):** + - Identify any public function or method that is missing a docstring. + - For each one, generate a complete and accurate docstring that describes the function's purpose, its arguments (`Args:`), and what it returns (`Returns:`). + - The generated docstring should be included in the `suggestion` part of the output. + + 2. **Unit Test Scaffolding (`generate_tests` = true):** + - For each source file, create a corresponding test file (e.g., `test_my_module.py` for `my_module.py`). + - In the test file, generate a basic unit test class with placeholder test methods for each public function in the source file. + - The test methods should include a simple `self.fail("Test not implemented")` or equivalent. + - The path to the newly created test file should be included in the output. + + **Execution Flow:** + - If a `linter_command` is provided, run it first and use its output to inform your analysis. + - If a `rules_file` is provided, prioritize those rules. + - Perform the core analysis and the advanced generation tasks as requested by the input arguments. + arguments: + - name: "files" + type: "string" + required: true + description: "A comma-separated list of file paths or directories to analyze." + - name: "language" + type: "string" + required: true + description: "The programming language of the files (e.g., 'python', 'javascript')." + - name: "report_format" + type: "string" + required: false + default: "markdown" + description: "The format for the output report ('markdown' or 'json')." + - name: "linter_command" + type: "string" + required: false + description: "An optional linter command to run before analysis (e.g., 'eslint .')." + - name: "rules_file" + type: "string" + required: false + description: "An optional path to a file containing custom analysis rules." + - name: "complexity_analysis" + type: "boolean" + required: false + default: false + description: "Whether to perform and report on cyclomatic complexity." + - name: "generate_docstrings" + type: "boolean" + required: false + default: false + description: "If true, generates missing docstrings for public functions." + - name: "generate_tests" + type: "boolean" + required: false + default: false + description: "If true, generates a scaffold for unit tests." + tools: ["filesystem", "shell"] + execution_strategy: "plan" + output_schema: | + { + "type": "object", + "properties": { + "summary": { + "type": "object", + "properties": { + "files_analyzed": {"type": "number"}, + "issues_found": {"type": "number"}, + "docstrings_generated": {"type": "number"}, + "test_files_created": {"type": "number"} + } + }, + "issues": { + "type": "array", + "description": "A list of clean code issues found and refactoring suggestions.", + "items": { + "type": "object", + "properties": { + "file": {"type": "string"}, + "line": {"type": "number"}, + "description": {"type": "string"}, + "suggestion": { + "type": "object", + "properties": { + "code_to_remove": {"type": "string"}, + "code_to_add": {"type": "string"} + } + }, + "cyclomatic_complexity": { + "type": "number", + "description": "The calculated cyclomatic complexity of the function or method." + } + }, + "required": ["file", "line", "description", "suggestion"] + } + }, + "generated_test_files": { + "type": "array", + "description": "A list of paths to the newly created unit test scaffold files.", + "items": { + "type": "string" + } + } + }, + "required": ["summary", "issues"] + } + exit_expression: "summary.issues_found == 0" diff --git a/agents/clean-code-agent/examples/ci-configs/azure-devops.yml b/agents/clean-code-agent/examples/ci-configs/azure-devops.yml new file mode 100644 index 0000000..d2043fc --- /dev/null +++ b/agents/clean-code-agent/examples/ci-configs/azure-devops.yml @@ -0,0 +1,21 @@ +trigger: +- main + +pool: + vmImage: 'ubuntu-latest' + +steps: +- task: NodeTool@0 + inputs: + versionSpec: '18.x' + displayName: 'Install Node.js' + +- script: | + npm install -g @qodo-ai/command + displayName: 'Install Qodo' + +- script: | + qodo clean_code --set files="." --set language="python" --set report_format="json" + displayName: 'Run Clean Code Agent' + env: + QODO_API_KEY: $(QODO_API_KEY) diff --git a/agents/clean-code-agent/examples/ci-configs/github-actions.yml b/agents/clean-code-agent/examples/ci-configs/github-actions.yml new file mode 100644 index 0000000..d695161 --- /dev/null +++ b/agents/clean-code-agent/examples/ci-configs/github-actions.yml @@ -0,0 +1,25 @@ +name: Clean Code Agent Analysis + +on: + pull_request: + branches: [ main ] + +jobs: + clean-code-check: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install Qodo + run: npm install -g @qodo-ai/command + + - name: Run Clean Code Agent + run: | + qodo clean_code \ + --set files="." \ + --set language="python" \ + --set report_format="json" \ + --set generate_docstrings=true + env: + QODO_API_KEY: ${{ secrets.QODO_API_KEY }} diff --git a/agents/clean-code-agent/examples/ci-configs/gitlab-ci.yml b/agents/clean-code-agent/examples/ci-configs/gitlab-ci.yml new file mode 100644 index 0000000..3c60851 --- /dev/null +++ b/agents/clean-code-agent/examples/ci-configs/gitlab-ci.yml @@ -0,0 +1,13 @@ +stages: + - test + +clean_code_check: + stage: test + image: node:18 + script: + - npm install -g @qodo-ai/command + - qodo clean_code --set files="." --set language="python" --set report_format="json" + variables: + QODO_API_KEY: $QODO_API_KEY + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' diff --git a/agents/clean-code-agent/examples/ci-configs/jenkins-pipeline.groovy b/agents/clean-code-agent/examples/ci-configs/jenkins-pipeline.groovy new file mode 100644 index 0000000..e33c8c9 --- /dev/null +++ b/agents/clean-code-agent/examples/ci-configs/jenkins-pipeline.groovy @@ -0,0 +1,16 @@ +pipeline { + agent any + environment { + QODO_API_KEY = credentials('qodo-api-key') + } + stages { + stage('Clean Code Check') { + steps { + script { + sh 'npm install -g @qodo-ai/command' + sh 'qodo clean_code --set files="." --set language="python" --set report_format="json"' + } + } + } + } +} diff --git a/agents/clean-code-agent/examples/sample_code.py b/agents/clean-code-agent/examples/sample_code.py new file mode 100644 index 0000000..a7020e8 --- /dev/null +++ b/agents/clean-code-agent/examples/sample_code.py @@ -0,0 +1,121 @@ +""" +Sample Python module demonstrating clean-code improvements. + +Changes from the original: +- Removed unused imports and variables +- Eliminated duplicated logic by extracting helper function +- Introduced named constants for magic numbers +- Added type hints and docstrings +- Improved function and variable names +- Wrapped executable example in a main guard +- Added a dataclass to avoid long parameter list and kept a thin wrapper + for backward compatibility with the original function signature +""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Iterable, List + +# Named constants (avoid magic numbers) +THRESHOLD: int = 10 +MULTIPLIER: int = 2 +BONUS: int = 5 + + +def _transform_item(value: int) -> int: + """Transform a single integer according to business rules. + + - If value > THRESHOLD, return value * MULTIPLIER + BONUS + - Otherwise, return value * MULTIPLIER + """ + if value > THRESHOLD: + return value * MULTIPLIER + BONUS + return value * MULTIPLIER + + +def transform_values(values: Iterable[int]) -> List[int]: + """Apply the transformation to an iterable of integers. + + Args: + values: Iterable of integers to transform. + + Returns: + List of transformed integers. + """ + return [_transform_item(v) for v in values] + + +# Backward-compatible wrapper with improved name kept alongside +# the original name for external callers. + +def process_data(data_list: Iterable[int], flag: bool) -> List[int]: # noqa: D401 + """Process a list of integers. + + Note: The `flag` parameter is retained for backward compatibility but is + currently unused because the same transformation applies regardless of + the flag value. It may be removed in a future version. + """ + # The original implementation duplicated the same logic for True/False. + # We unify the behavior and ignore `flag`. + del flag # explicitly mark as intentionally unused + return transform_values(data_list) + + +# Dataclass to avoid a long parameter list for user creation +@dataclass(frozen=True) +class UserProfile: + name: str + email: str + password: str + age: int + address: str + phone_number: str + country: str + + +def create_user_profile(profile: UserProfile) -> dict: + """Create a user dictionary from a UserProfile. + + In a real application, this is where validation and persistence would occur. + """ + print(f"Creating user {profile.name} from {profile.country}") + return {"name": profile.name, "email": profile.email} + + +# Backward-compatible thin wrapper that preserves the original signature +# while delegating to the cleaner API based on the dataclass. + +def create_user( + name: str, + email: str, + password: str, + age: int, + address: str, + phone_number: str, + country: str, +) -> dict: + """Backward-compatible wrapper for user creation. + + Prefer using `create_user_profile(UserProfile(...))` to avoid long + parameter lists. + """ + profile = UserProfile( + name=name, + email=email, + password=password, + age=age, + address=address, + phone_number=phone_number, + country=country, + ) + return create_user_profile(profile) + + +def _example_usage() -> None: + values = [5, 15, 3, 25] + processed = process_data(values, True) + print(processed) + + +if __name__ == "__main__": + _example_usage() diff --git a/agents/clean-code-agent/examples/tests/test_utils.py b/agents/clean-code-agent/examples/tests/test_utils.py new file mode 100644 index 0000000..0621991 --- /dev/null +++ b/agents/clean-code-agent/examples/tests/test_utils.py @@ -0,0 +1,28 @@ +import math +import pytest + +from agents.clean-code-agent.examples.utils import add_numbers, subtract_numbers + + +def test_add_numbers_integers(): + assert add_numbers(2, 3) == 5 + + +def test_add_numbers_floats(): + assert math.isclose(add_numbers(2.5, 3.1), 5.6) + + +def test_subtract_numbers_integers(): + assert subtract_numbers(5, 3) == 2 + + +def test_subtract_numbers_floats(): + assert math.isclose(subtract_numbers(5.5, 3.2), 2.3) + + +def test_add_numbers_negative(): + assert add_numbers(-2, -3) == -5 + + +def test_subtract_numbers_negative(): + assert subtract_numbers(-2, -3) == 1 diff --git a/agents/clean-code-agent/examples/usage.md b/agents/clean-code-agent/examples/usage.md new file mode 100644 index 0000000..98c7f92 --- /dev/null +++ b/agents/clean-code-agent/examples/usage.md @@ -0,0 +1,75 @@ +# Clean Code Agent Usage Examples + +This document provides a set of examples for using the Clean Code Agent. + +## Example 1: Basic Analysis of a Single File + +This example shows how to run the agent on a single Python file and get the report in Markdown format. + +**Command:** + +```bash +qodo clean_code --set files="src/my_module.py" --set language="python" +``` + +**Expected Output:** + +A Markdown report printed to the console, listing any clean code issues found in `src/my_module.py`. + +## Example 2: Analyzing an Entire Directory with JSON Output + +This example analyzes all JavaScript files in the `src` directory and outputs the results in JSON format, which can be piped to other tools. + +**Command:** + +```bash +qodo clean_code --set files="src/" --set language="javascript" --set report_format="json" > report.json +``` + +**Expected Output:** + +A `report.json` file containing a structured list of all the issues found. + +## Example 3: Using a Linter and Complexity Analysis + +This example demonstrates how to use the agent with a linter and enable cyclomatic complexity analysis. + +**Command:** + +```bash +qodo clean_code --set files="." --set language="javascript" --set linter_command="eslint ." --set complexity_analysis=true +``` + +**Expected Output:** + +A report that includes both the AI's analysis and the linter's findings, with cyclomatic complexity scores for relevant functions. + +## Example 4: Applying Custom Rules + +This example shows how to use a custom rules file to guide the agent's analysis. + +**Command:** + +```bash +qodo clean_code --set files="app/" --set language="ruby" --set rules_file=".codify/clean_code_rules.txt" +``` + +**Expected Output:** + +A report where the issues are prioritized and analyzed based on the rules defined in `.codify/clean_code_rules.txt`. + +## Example 5: Generating Docstrings and Unit Tests + +This example demonstrates how to use the agent's code generation capabilities. It will analyze the specified file, generate a missing docstring, and create a new unit test file. + +**Command:** + +```bash +qodo clean_code --set files="src/utils.py" --set language="python" --set generate_docstrings=true --set generate_tests=true +``` + +**Expected Output:** + +- A `diff` suggestion in the output to add the new docstring to `src/utils.py`. +- A new file created at `src/test_utils.py` with a basic test structure. +- A summary in the output indicating that 1 docstring was generated and 1 test file was created. diff --git a/agents/clean-code-agent/examples/utils.py b/agents/clean-code-agent/examples/utils.py new file mode 100644 index 0000000..15d51ab --- /dev/null +++ b/agents/clean-code-agent/examples/utils.py @@ -0,0 +1,32 @@ +"""Utility math functions. + +This module provides basic arithmetic operations with type hints and docstrings. +""" +from typing import Union + +Number = Union[int, float] + +def add_numbers(a: Number, b: Number) -> Number: + """Return the sum of two numbers. + + Args: + a: First addend (int or float). + b: Second addend (int or float). + + Returns: + The sum of a and b. + """ + return a + b + + +def subtract_numbers(a: Number, b: Number) -> Number: + """Return the difference of two numbers (a - b). + + Args: + a: Minuend (int or float). + b: Subtrahend (int or float). + + Returns: + The result of a - b. + """ + return a - b