diff --git a/.github/workflows/close-stale-ci-prs.yml b/.github/workflows/close-stale-ci-prs.yml new file mode 100644 index 000000000..54f8dbf7a --- /dev/null +++ b/.github/workflows/close-stale-ci-prs.yml @@ -0,0 +1,67 @@ +name: Close Stale CI PRs + +on: + schedule: + # Run every hour + - cron: "0 * * * *" + workflow_dispatch: + +jobs: + close-stale-prs: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Close stale PRs + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Get all open PRs with patchwork-ci- branches + prs=$(gh pr list --state open --json number,headRefName,createdAt --jq '.[] | select(.headRefName | startswith("patchwork-ci-")) | "\(.number) \(.createdAt)"') + + while IFS= read -r line; do + if [ -z "$line" ]; then + continue + fi + + pr_number=$(echo "$line" | awk '{print $1}') + created_at=$(echo "$line" | awk '{print $2}') + + # Convert to Unix timestamp + created_ts=$(date -d "$created_at" +%s) + current_ts=$(date +%s) + age_hours=$(( (current_ts - created_ts) / 3600 )) + + if [ $age_hours -gt 1 ]; then + echo "Closing PR #$pr_number (age: $age_hours hours)" + gh pr comment $pr_number --body "This PR has been automatically closed because it's been open for more than 1 hour. This is a CI-generated PR and should be reviewed and merged promptly if valid." + gh pr close $pr_number + fi + done <<< "$prs" + + - name: Delete stale branches + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Get all branches with patchwork-ci- prefix + branches=$(gh api repos/${{ github.repository }}/branches --jq '.[] | select(.name | startswith("patchwork-ci-")) | "\(.name) \(.commit.commit.committer.date)"') + + while IFS= read -r line; do + if [ -z "$line" ]; then + continue + fi + + branch_name=$(echo "$line" | awk '{print $1}') + created_at=$(echo "$line" | awk '{print $2}') + + # Convert to Unix timestamp + created_ts=$(date -d "$created_at" +%s) + current_ts=$(date +%s) + age_days=$(( (current_ts - created_ts) / 86400 )) + + if [ $age_days -gt 7 ]; then + echo "Deleting branch $branch_name (age: $age_days days)" + gh api -X DELETE repos/${{ github.repository }}/git/refs/heads/$branch_name + fi + done <<< "$branches" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 595d5b9db..61b499156 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,10 +4,12 @@ on: push: branches-ignore: - main + - patchwork-ci-* - autofix-* - dependencyupgrade-* - generatereadme-* - generatedocstring-* + - generatediagram-* - generateunittests-* - generatecodeusageexample-* - resolveissue-* @@ -104,8 +106,10 @@ jobs: patchwork AutoFix --log debug \ --patched_api_key=${{ secrets.PATCHED_API_KEY }} \ --github_api_key=${{ secrets.SCM_GITHUB_KEY }} \ + --branch_prefix=patchwork-ci-autofix- \ --force_pr_creation \ - --disable_telemetry + --disable_telemetry \ + --plain - name: Dependency Upgrade run: | @@ -114,9 +118,11 @@ jobs: --libraries_api_key=${{ secrets.LIBRARIES_KEY }} \ --patched_api_key=${{ secrets.PATCHED_API_KEY }} \ --github_api_key=${{ secrets.SCM_GITHUB_KEY }} \ + --branch_prefix=patchwork-ci-dependencyupgrade- \ --language=python \ --force_pr_creation \ - --disable_telemetry + --disable_telemetry \ + --plain main-test: runs-on: ubuntu-latest @@ -160,7 +166,8 @@ jobs: --anthropic_api_key=${{ secrets.ANTHROPIC_API_KEY }} \ --github_api_key=${{ secrets.SCM_GITHUB_KEY }} \ --pr_url=https://github.com/patched-codes/patchwork/pull/${{ steps.findPr.outputs.number }} \ - --disable_telemetry + --disable_telemetry \ + --plain - name: Generate Docstring run: | @@ -169,7 +176,9 @@ jobs: --patched_api_key=${{ secrets.PATCHED_API_KEY }} \ --github_api_key=${{ secrets.SCM_GITHUB_KEY }} \ --base_path=tests/cicd/generate_docstring \ - --disable_telemetry + --branch_prefix=patchwork-ci-generatedocstring- \ + --disable_telemetry \ + --plain - name: Generate Diagram run: | @@ -178,7 +187,9 @@ jobs: --patched_api_key=${{ secrets.PATCHED_API_KEY }} \ --github_api_key=${{ secrets.SCM_GITHUB_KEY }} \ --folder_path=patchwork/steps \ - --disable_telemetry + --branch_prefix=patchwork-ci-generatediagram- \ + --disable_telemetry \ + --plain - name: Generate UnitTests run: | @@ -187,7 +198,9 @@ jobs: --patched_api_key=${{ secrets.PATCHED_API_KEY }} \ --github_api_key=${{ secrets.SCM_GITHUB_KEY }} \ --folder_path=tests/cicd/generate_docstring \ - --disable_telemetry + --branch_prefix=patchwork-ci-generateunittests- \ + --disable_telemetry \ + --plain - name: Generate Code Usage Example run: | @@ -196,7 +209,9 @@ jobs: --patched_api_key=${{ secrets.PATCHED_API_KEY }} \ --github_api_key=${{ secrets.SCM_GITHUB_KEY }} \ --folder_path=tests/cicd/generate_docstring \ - --disable_telemetry + --branch_prefix=patchwork-ci-generatecodeusageexample- \ + --disable_telemetry \ + --plain - name: Generate README run: | @@ -218,8 +233,9 @@ jobs: --github_api_key=${{ secrets.SCM_GITHUB_KEY }} \ --filter=*.py \ --folder_path=$dir \ - --branch_prefix=generatereadme-$branch_name \ - --disable_telemetry + --branch_prefix=patchwork-ci-generatereadme \ + --disable_telemetry \ + --plain else echo "Found README.md in $dir" fi diff --git a/patchwork/patchflows/AutoFix/AutoFix.py b/patchwork/patchflows/AutoFix/AutoFix.py index b548629c8..8c98d6ba9 100644 --- a/patchwork/patchflows/AutoFix/AutoFix.py +++ b/patchwork/patchflows/AutoFix/AutoFix.py @@ -72,8 +72,11 @@ def __init__(self, inputs: dict): "compatibility": ["C. Compatibility Risk:", "D. Fixed Code:"], "patch": ["D. Fixed Code:", "```", "\n", "```"], } - final_inputs["pr_title"] = f"PatchWork {self.__class__.__name__}" - final_inputs["branch_prefix"] = f"{self.__class__.__name__.lower()}-" + if "pr_title" not in final_inputs.keys(): + final_inputs["pr_title"] = f"PatchWork {self.__class__.__name__}" + + if "branch_prefix" not in final_inputs.keys(): + final_inputs["branch_prefix"] = f"{self.__class__.__name__.lower()}-" validate_steps_with_inputs( set(final_inputs.keys()).union({"prompt_values"}), ScanSemgrep, ExtractCode, LLM, ModifyCode, PR diff --git a/patchwork/patchflows/GenerateCodeUsageExample/GenerateCodeUsageExample.py b/patchwork/patchflows/GenerateCodeUsageExample/GenerateCodeUsageExample.py index 5afac3709..14de647bd 100644 --- a/patchwork/patchflows/GenerateCodeUsageExample/GenerateCodeUsageExample.py +++ b/patchwork/patchflows/GenerateCodeUsageExample/GenerateCodeUsageExample.py @@ -29,8 +29,11 @@ def __init__(self, inputs): if "prompt_template_file" not in final_inputs: final_inputs["prompt_template_file"] = _DEFAULT_PROMPT_JSON - final_inputs["pr_title"] = f"PatchWork Usage Example generated" - final_inputs["branch_prefix"] = f"{self.__class__.__name__.lower()}-" + if "pr_title" not in final_inputs.keys(): + final_inputs["pr_title"] = "PatchWork Usage Example generated" + + if "branch_prefix" not in final_inputs.keys(): + final_inputs["branch_prefix"] = f"{self.__class__.__name__.lower()}-" validate_steps_with_inputs( set(final_inputs.keys()).union({"prompt_values", "files_to_patch"}), LLM, CallCode2Prompt, ModifyCode, PR @@ -50,7 +53,7 @@ def run(self): outputs = ModifyCode(self.inputs).run() self.inputs.update(outputs) number = len(self.inputs["modified_code_files"]) - self.inputs["pr_header"] = f"This pull request adds usage example." + self.inputs["pr_header"] = "This pull request adds usage example." outputs = PR(self.inputs).run() self.inputs.update(outputs) diff --git a/patchwork/patchflows/GenerateDiagram/GenerateDiagram.py b/patchwork/patchflows/GenerateDiagram/GenerateDiagram.py index b59b95ee1..15eea19fd 100644 --- a/patchwork/patchflows/GenerateDiagram/GenerateDiagram.py +++ b/patchwork/patchflows/GenerateDiagram/GenerateDiagram.py @@ -25,8 +25,11 @@ def __init__(self, inputs): if "prompt_template_file" not in final_inputs: final_inputs["prompt_template_file"] = _DEFAULT_PROMPT_JSON - final_inputs["pr_title"] = f"PatchWork System Architecture Diagram" - final_inputs["branch_prefix"] = f"{self.__class__.__name__.lower()}-" + if "pr_title" not in final_inputs.keys(): + final_inputs["pr_title"] = "PatchWork System Architecture Diagram" + + if "branch_prefix" not in final_inputs.keys(): + final_inputs["branch_prefix"] = f"{self.__class__.__name__.lower()}-" validate_steps_with_inputs( set(final_inputs.keys()).union({"prompt_values", "files_to_patch"}), LLM, CallCode2Prompt, ModifyCode, PR @@ -44,7 +47,7 @@ def run(self): self.inputs.update(outputs) outputs = ModifyCode(self.inputs).run() self.inputs.update(outputs) - self.inputs["pr_header"] = f"This pull request from patchwork generates system architecture diagram." + self.inputs["pr_header"] = "This pull request from patchwork generates system architecture diagram." outputs = PR(self.inputs).run() self.inputs.update(outputs) diff --git a/patchwork/patchflows/GenerateDocstring/GenerateDocstring.py b/patchwork/patchflows/GenerateDocstring/GenerateDocstring.py index 108d02d24..aad33b08b 100644 --- a/patchwork/patchflows/GenerateDocstring/GenerateDocstring.py +++ b/patchwork/patchflows/GenerateDocstring/GenerateDocstring.py @@ -47,8 +47,12 @@ def __init__(self, inputs: dict): if "prompt_template_file" not in final_inputs.keys(): final_inputs["prompt_template_file"] = _DEFAULT_PROMPT_JSON - final_inputs["pr_title"] = f"PatchWork {self.__class__.__name__}" - final_inputs["branch_prefix"] = f"{self.__class__.__name__.lower()}-" + if "pr_title" not in final_inputs.keys(): + final_inputs["pr_title"] = f"PatchWork {self.__class__.__name__}" + + if "branch_prefix" not in final_inputs.keys(): + final_inputs["branch_prefix"] = f"{self.__class__.__name__.lower()}-" + final_inputs["context_grouping"] = "FUNCTION" final_inputs["allow_overlap_contexts"] = False final_inputs["force_code_contexts"] = final_inputs.get("rewrite_existing", False) @@ -75,9 +79,9 @@ def run(self) -> dict: self.inputs.update(outputs) # Commit changes and create PR - self.inputs[ - "pr_header" - ] = f'This pull request from patchwork fixes {len(self.inputs["prompt_values"])} docstrings.' + self.inputs["pr_header"] = ( + f"This pull request from patchwork fixes {len(self.inputs['prompt_values'])} docstrings." + ) outputs = PR(self.inputs).run() self.inputs.update(outputs) diff --git a/patchwork/patchflows/GenerateREADME/GenerateREADME.py b/patchwork/patchflows/GenerateREADME/GenerateREADME.py index 0be781791..74945ef96 100644 --- a/patchwork/patchflows/GenerateREADME/GenerateREADME.py +++ b/patchwork/patchflows/GenerateREADME/GenerateREADME.py @@ -51,7 +51,8 @@ def __init__(self, inputs: dict): else: final_inputs["folder_path"] = Path(final_inputs["folder_path"]) - final_inputs["pr_title"] = f"PatchWork {self.__class__.__name__}" + if "pr_title" not in final_inputs.keys(): + final_inputs["pr_title"] = f"PatchWork {self.__class__.__name__}" validate_steps_with_inputs( set(final_inputs.keys()).union({"prompt_values", "files_to_patch"}), CallCode2Prompt, LLM, ModifyCode, PR diff --git a/patchwork/patchflows/GenerateUnitTests/GenerateUnitTests.py b/patchwork/patchflows/GenerateUnitTests/GenerateUnitTests.py index b82abe294..009260830 100644 --- a/patchwork/patchflows/GenerateUnitTests/GenerateUnitTests.py +++ b/patchwork/patchflows/GenerateUnitTests/GenerateUnitTests.py @@ -29,8 +29,11 @@ def __init__(self, inputs): if "prompt_template_file" not in final_inputs: final_inputs["prompt_template_file"] = _DEFAULT_PROMPT_JSON - final_inputs["pr_title"] = f"PatchWork Unit Tests generated" - final_inputs["branch_prefix"] = f"{self.__class__.__name__.lower()}-" + if "pr_title" not in final_inputs.keys(): + final_inputs["pr_title"] = "PatchWork Unit Tests generated" + + if "branch_prefix" not in final_inputs.keys(): + final_inputs["branch_prefix"] = f"{self.__class__.__name__.lower()}-" validate_steps_with_inputs( set(final_inputs.keys()).union({"prompt_values", "files_to_patch"}), LLM, CallCode2Prompt, ModifyCode, PR @@ -50,7 +53,7 @@ def run(self): outputs = ModifyCode(self.inputs).run() self.inputs.update(outputs) number = len(self.inputs["modified_code_files"]) - self.inputs["pr_header"] = f"This pull request from patchwork adds tests." + self.inputs["pr_header"] = "This pull request from patchwork adds tests." outputs = PR(self.inputs).run() self.inputs.update(outputs) diff --git a/patchwork/patchflows/ResolveIssue/ResolveIssue.py b/patchwork/patchflows/ResolveIssue/ResolveIssue.py index 850318d30..67b507c5e 100644 --- a/patchwork/patchflows/ResolveIssue/ResolveIssue.py +++ b/patchwork/patchflows/ResolveIssue/ResolveIssue.py @@ -20,8 +20,11 @@ def __init__(self, inputs: dict): final_inputs = yaml.safe_load(_DEFAULT_INPUT_FILE.read_text()) or dict() final_inputs.update(inputs) - final_inputs["pr_title"] = f"PatchWork {self.__class__.__name__}" - final_inputs["branch_prefix"] = f"{self.__class__.__name__.lower()}-" + if "pr_title" not in final_inputs.keys(): + final_inputs["pr_title"] = f"PatchWork {self.__class__.__name__}" + + if "branch_prefix" not in final_inputs.keys(): + final_inputs["branch_prefix"] = f"{self.__class__.__name__.lower()}-" validate_steps_with_inputs( {"issue_description"}.union(final_inputs.keys()), @@ -46,8 +49,8 @@ def run(self) -> dict: issue=outputs["issue_description"], ), system_prompt="""\ -You are a senior software engineer tasked to analyze a issue. -Your analysis will be used to guide the junior engineer to resolve this issue. +You are a senior software engineer tasked to analyze a issue. +Your analysis will be used to guide the junior engineer to resolve this issue. """, user_prompt="""\ @@ -130,7 +133,7 @@ def run(self) -> dict: 1. Edit the sourcecode of the repo to resolve the issue 2. Think about edge cases and make sure your fix handles them as well -I've already taken care of all changes to any of the test files described in the PR. +I've already taken care of all changes to any of the test files described in the PR. This means you DON'T have to modify the testing logic or any of the tests in any way! """, max_llm_calls=200,