patchpilot 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ ANTHROPIC_API_KEY=your-key-here
@@ -0,0 +1,26 @@
1
+ # Secrets
2
+ .env
3
+ .env.*
4
+ !.env.example
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *.egg-info/
10
+ .venv/
11
+ venv/
12
+ build/
13
+ dist/
14
+ .pytest_cache/
15
+ .mypy_cache/
16
+ .ruff_cache/
17
+ .coverage
18
+ htmlcov/
19
+
20
+ # Editors / OS
21
+ .vscode/
22
+ .idea/
23
+ .DS_Store
24
+
25
+ # Agent workspaces
26
+ workspaces/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ilmee De Silva
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.5
2
+ Name: patchpilot
3
+ Version: 0.1.0
4
+ Summary: An AI agent that upgrades your dependencies, fixes what breaks, and opens a PR
5
+ Project-URL: Homepage, https://github.com/ilmeedesilva/patchpilot
6
+ Project-URL: Issues, https://github.com/ilmeedesilva/patchpilot/issues
7
+ Project-URL: Demo, https://github.com/ilmeedesilva/patchpilot-demo
8
+ Author: Ilmee De Silva
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai-agent,claude,dependencies,developer-tools,llm,upgrade
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Build Tools
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Requires-Python: >=3.11
24
+ Requires-Dist: anthropic>=2.3.0
25
+ Requires-Dist: python-dotenv>=1.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: build>=1.2; extra == 'dev'
28
+ Requires-Dist: pytest>=9; extra == 'dev'
29
+ Requires-Dist: ruff>=0.17; extra == 'dev'
30
+ Requires-Dist: twine>=6; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # patchpilot
34
+
35
+ [![CI](https://github.com/ilmeedesilva/patchpilot/actions/workflows/ci.yml/badge.svg)](https://github.com/ilmeedesilva/patchpilot/actions/workflows/ci.yml)
36
+ ![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)
37
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/ilmeedesilva/patchpilot/blob/main/LICENSE)
38
+
39
+ **An AI agent that upgrades a dependency, fixes the code that breaks, and opens a pull request explaining what changed and why.**
40
+
41
+ Dependabot and Renovate bump version numbers. When the new version breaks your build, the fix is still on you. patchpilot does that part: it upgrades the package inside a sandbox, runs your tests, reads the failures and the changelog, edits your code until the tests pass, and hands you a reviewable PR.
42
+
43
+ <!-- TODO: demo GIF here: `patchpilot upgrade . pydantic --pr` → PR appears -->
44
+
45
+ ```console
46
+ $ patchpilot upgrade . pydantic --pr
47
+ • Creating an isolated copy of the repo on branch patchpilot/upgrade-pydantic
48
+ • Starting a Docker sandbox (python:3.13-slim) and installing the project
49
+ • Running the tests before upgrading
50
+ • Agent is upgrading pydantic
51
+ → read_dependencies {}
52
+ → get_latest_version {"package": "pydantic"}
53
+ → pip_install {"requirement": "pydantic==2.14.1"}
54
+ → run_tests {} # fails: BaseSettings moved out of pydantic
55
+ → pip_install {"requirement": "pydantic-settings==2.16.2"}
56
+ → write_file {"path": "app/config.py"}
57
+ → write_file {"path": "app/models.py"}
58
+ → run_tests {} # passes
59
+ • Re-running the tests to verify the agent's work
60
+ • Pull request: https://github.com/<you>/<repo>/pull/1
61
+ ```
62
+
63
+ The PR description is written by the agent, and includes:
64
+
65
+ > **Breaking changes**
66
+ > - **`BaseSettings` moved to `pydantic-settings`**: importing it from `pydantic` now fails. Installed `pydantic-settings`, added it to `requirements.txt`, and now import from `pydantic_settings`.
67
+ > - **`@root_validator` without `skip_on_failure=True` is an error in v2**: replaced with `@model_validator(mode="after")`.
68
+ > - …
69
+ >
70
+ > **Needs your attention**
71
+ > - In v1 the password check ran even when another field had failed; in v2 it only runs once all fields are valid, so a bad email plus mismatched passwords now reports only the email error.
72
+
73
+ *(Trimmed output and report from a real run on [`examples/pydantic-demo`](https://github.com/ilmeedesilva/patchpilot/tree/main/examples/pydantic-demo).)*
74
+
75
+ ## Quick start
76
+
77
+ You need Python 3.11+, [Docker Desktop](https://www.docker.com/products/docker-desktop/) running, and an [Anthropic API key](https://console.anthropic.com/settings/keys).
78
+
79
+ ```bash
80
+ pipx install git+https://github.com/ilmeedesilva/patchpilot.git # soon: pipx install patchpilot
81
+ export ANTHROPIC_API_KEY=sk-ant-... # or put it in a .env file where you run patchpilot
82
+
83
+ cd your-project # a git repo with a test suite
84
+ patchpilot analyze . # read-only report: what's outdated, what's risky
85
+ patchpilot upgrade . pydantic # upgrade + fix, committed to a new branch
86
+ patchpilot upgrade . pydantic --pr # ...and open a pull request
87
+ ```
88
+
89
+ Each run uses Anthropic API credit. The amount depends on the size of the project and how much breaks.
90
+
91
+ ## Usage
92
+
93
+ ### `patchpilot analyze <project>`
94
+
95
+ Reads your dependency files, checks PyPI for the latest versions and runs your tests once, then prints a Markdown report. It covers what's outdated, which upgrades are major-version jumps likely to need code changes, and a suggested order. It changes nothing.
96
+
97
+ ### `patchpilot upgrade <project> <package>`
98
+
99
+ 1. Checks that `<package>` really is a dependency of `<project>`. If it's declared in a subfolder instead, it tells you which one.
100
+ 2. Makes a separate checkout of your repo (a git worktree) on a new branch, `patchpilot/upgrade-<package>`. **Your working copy is never touched.**
101
+ 3. Starts a Docker sandbox, installs your project into it, and checks that the tests pass *before* changing anything.
102
+ 4. Lets the agent work: upgrade the package, run the tests, read the failures and changelog, edit the code, and repeat.
103
+ 5. **Re-runs the tests itself** instead of trusting the agent's word, then commits to the branch with the agent's report as the commit message.
104
+
105
+ Review the result with `git diff HEAD...patchpilot/upgrade-<package>`, then merge it or delete it.
106
+
107
+ | Option | |
108
+ |---|---|
109
+ | `--pr` | Push the branch and open a GitHub pull request. If the tests still fail, it's opened as a **draft**. Uses the [GitHub CLI](https://cli.github.com/), so patchpilot never handles a GitHub token. |
110
+ | `--test-command "..."` | How to run your tests (default: `python -m pytest -q`). |
111
+ | `--image python:3.11-slim` | Docker image to run in. Match it to your project's Python version (default: `python:3.13-slim`). |
112
+ | `--no-docker` | Use a local virtualenv instead of Docker. Faster, but your project's code runs directly on your machine. |
113
+
114
+ ## How it works
115
+
116
+ ```mermaid
117
+ flowchart LR
118
+ subgraph host["Your machine"]
119
+ CLI["patchpilot CLI"] --> Loop["Agent loop"]
120
+ Loop <-->|"tool calls ⇄ results"| Claude["Claude API"]
121
+ Loop --> Files["Read / edit files<br/>in a temporary worktree"]
122
+ end
123
+ subgraph sandbox["Docker sandbox: sees only the worktree"]
124
+ Pip["pip install &lt;one package&gt;"]
125
+ Tests["your test suite"]
126
+ end
127
+ Loop --> Pip
128
+ Loop --> Tests
129
+ Loop -->|"verify, commit"| Branch["patchpilot/upgrade-&lt;pkg&gt;"]
130
+ Branch -->|"--pr"| PR["GitHub pull request"]
131
+ ```
132
+
133
+ At the centre is a hand-written [tool-use loop](https://github.com/ilmeedesilva/patchpilot/blob/main/src/patchpilot/agent.py). patchpilot sends Claude the task and a list of tools. Claude replies with the tool it wants to call, patchpilot runs it and sends back the result, and this repeats until Claude writes its final report. Claude decides *what* to do next; patchpilot's code is the only thing that *does* anything.
134
+
135
+ | Tool | Runs where | What it does |
136
+ |---|---|---|
137
+ | `read_dependencies` | host | Parses `requirements*.txt` and `pyproject.toml` |
138
+ | `get_latest_version` | host | Queries PyPI, including changelog URLs |
139
+ | `list_files`, `read_file` | host | Read-only, confined to the project folder |
140
+ | `edit_file`, `write_file` | host | Exact-match edits; refuses `.git/` and virtualenvs |
141
+ | `pip_install` | **sandbox** | One requirement at a time; flags, URLs and file paths are rejected |
142
+ | `run_tests` | **sandbox** | Your test command, with a timeout |
143
+ | `web_fetch` | Anthropic | Reads changelogs and migration guides |
144
+
145
+ ### Safety
146
+
147
+ Upgrading means running code that isn't yours: your tests, and whatever a package runs when it installs. So that code runs inside a Docker container that:
148
+
149
+ - mounts **only the temporary worktree**, not your home folder, SSH keys or other projects
150
+ - runs as your user, not root, with all Linux capabilities dropped and `no-new-privileges`
151
+ - is capped at 2 CPUs, 2 GB of memory and 512 processes
152
+ - is deleted when the run ends, even if the run fails
153
+
154
+ The agent loop and your API key never enter the container. These properties are checked by [integration tests](https://github.com/ilmeedesilva/patchpilot/blob/main/tests/test_sandbox.py), not just described here.
155
+
156
+ ### Design decisions
157
+
158
+ - **Verify, don't trust.** The agent says when it's done, but patchpilot re-runs the tests itself before committing. The ✅ in the output is patchpilot's verdict, not the model's.
159
+ - **Refuse to run when "tests pass" would mean nothing.** patchpilot stops if the tests already fail before the upgrade, or if the package isn't a dependency of the folder you pointed it at. Otherwise a green result could be meaningless.
160
+ - **Fail before spending, not after.** Docker, GitHub login, remotes and branch names are all checked before the first API call.
161
+ - **Leave nothing behind.** The worktree, container and virtualenv are cleaned up on success, failure or Ctrl-C. A branch only survives if it has a commit worth reviewing.
162
+ - **A small, explicit tool surface** instead of a general shell. It's easier to reason about what the agent can do, and easier to sandbox.
163
+
164
+ ## Limitations
165
+
166
+ - Python projects only, with `requirements*.txt` or a PEP 621 `pyproject.toml` (no Poetry or uv lockfiles yet).
167
+ - One package per run.
168
+ - The fix is only as good as your tests. If they don't cover what changed, patchpilot can't catch it, though the agent flags behaviour changes it notices under **Needs your attention**.
169
+ - The sandbox needs network access to install packages, so code inside it can reach the internet.
170
+
171
+ ## Roadmap
172
+
173
+ - [ ] Publish to PyPI (`pipx install patchpilot`)
174
+ - [ ] GitHub Action: run weekly and open PRs automatically
175
+ - [ ] Upgrade several packages in one run, ordered by risk
176
+ - [ ] Turn off the sandbox's network access while tests run
177
+ - [ ] uv and Poetry lockfile support
178
+
179
+ ## Development
180
+
181
+ ```bash
182
+ git clone git@github.com:ilmeedesilva/patchpilot.git && cd patchpilot
183
+ python3 -m venv .venv && source .venv/bin/activate
184
+ pip install -e ".[dev]"
185
+ pytest # Docker tests are skipped if Docker isn't running
186
+ ruff check . && ruff format --check .
187
+ ```
188
+
189
+ The test suite doesn't call the Anthropic API: the agent is replaced with a fake that makes known edits, and GitHub with a local bare repo plus a stand-in `gh`.
190
+
191
+ ## License
192
+
193
+ [MIT](https://github.com/ilmeedesilva/patchpilot/blob/main/LICENSE)
@@ -0,0 +1,161 @@
1
+ # patchpilot
2
+
3
+ [![CI](https://github.com/ilmeedesilva/patchpilot/actions/workflows/ci.yml/badge.svg)](https://github.com/ilmeedesilva/patchpilot/actions/workflows/ci.yml)
4
+ ![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/ilmeedesilva/patchpilot/blob/main/LICENSE)
6
+
7
+ **An AI agent that upgrades a dependency, fixes the code that breaks, and opens a pull request explaining what changed and why.**
8
+
9
+ Dependabot and Renovate bump version numbers. When the new version breaks your build, the fix is still on you. patchpilot does that part: it upgrades the package inside a sandbox, runs your tests, reads the failures and the changelog, edits your code until the tests pass, and hands you a reviewable PR.
10
+
11
+ <!-- TODO: demo GIF here: `patchpilot upgrade . pydantic --pr` → PR appears -->
12
+
13
+ ```console
14
+ $ patchpilot upgrade . pydantic --pr
15
+ • Creating an isolated copy of the repo on branch patchpilot/upgrade-pydantic
16
+ • Starting a Docker sandbox (python:3.13-slim) and installing the project
17
+ • Running the tests before upgrading
18
+ • Agent is upgrading pydantic
19
+ → read_dependencies {}
20
+ → get_latest_version {"package": "pydantic"}
21
+ → pip_install {"requirement": "pydantic==2.14.1"}
22
+ → run_tests {} # fails: BaseSettings moved out of pydantic
23
+ → pip_install {"requirement": "pydantic-settings==2.16.2"}
24
+ → write_file {"path": "app/config.py"}
25
+ → write_file {"path": "app/models.py"}
26
+ → run_tests {} # passes
27
+ • Re-running the tests to verify the agent's work
28
+ • Pull request: https://github.com/<you>/<repo>/pull/1
29
+ ```
30
+
31
+ The PR description is written by the agent, and includes:
32
+
33
+ > **Breaking changes**
34
+ > - **`BaseSettings` moved to `pydantic-settings`**: importing it from `pydantic` now fails. Installed `pydantic-settings`, added it to `requirements.txt`, and now import from `pydantic_settings`.
35
+ > - **`@root_validator` without `skip_on_failure=True` is an error in v2**: replaced with `@model_validator(mode="after")`.
36
+ > - …
37
+ >
38
+ > **Needs your attention**
39
+ > - In v1 the password check ran even when another field had failed; in v2 it only runs once all fields are valid, so a bad email plus mismatched passwords now reports only the email error.
40
+
41
+ *(Trimmed output and report from a real run on [`examples/pydantic-demo`](https://github.com/ilmeedesilva/patchpilot/tree/main/examples/pydantic-demo).)*
42
+
43
+ ## Quick start
44
+
45
+ You need Python 3.11+, [Docker Desktop](https://www.docker.com/products/docker-desktop/) running, and an [Anthropic API key](https://console.anthropic.com/settings/keys).
46
+
47
+ ```bash
48
+ pipx install git+https://github.com/ilmeedesilva/patchpilot.git # soon: pipx install patchpilot
49
+ export ANTHROPIC_API_KEY=sk-ant-... # or put it in a .env file where you run patchpilot
50
+
51
+ cd your-project # a git repo with a test suite
52
+ patchpilot analyze . # read-only report: what's outdated, what's risky
53
+ patchpilot upgrade . pydantic # upgrade + fix, committed to a new branch
54
+ patchpilot upgrade . pydantic --pr # ...and open a pull request
55
+ ```
56
+
57
+ Each run uses Anthropic API credit. The amount depends on the size of the project and how much breaks.
58
+
59
+ ## Usage
60
+
61
+ ### `patchpilot analyze <project>`
62
+
63
+ Reads your dependency files, checks PyPI for the latest versions and runs your tests once, then prints a Markdown report. It covers what's outdated, which upgrades are major-version jumps likely to need code changes, and a suggested order. It changes nothing.
64
+
65
+ ### `patchpilot upgrade <project> <package>`
66
+
67
+ 1. Checks that `<package>` really is a dependency of `<project>`. If it's declared in a subfolder instead, it tells you which one.
68
+ 2. Makes a separate checkout of your repo (a git worktree) on a new branch, `patchpilot/upgrade-<package>`. **Your working copy is never touched.**
69
+ 3. Starts a Docker sandbox, installs your project into it, and checks that the tests pass *before* changing anything.
70
+ 4. Lets the agent work: upgrade the package, run the tests, read the failures and changelog, edit the code, and repeat.
71
+ 5. **Re-runs the tests itself** instead of trusting the agent's word, then commits to the branch with the agent's report as the commit message.
72
+
73
+ Review the result with `git diff HEAD...patchpilot/upgrade-<package>`, then merge it or delete it.
74
+
75
+ | Option | |
76
+ |---|---|
77
+ | `--pr` | Push the branch and open a GitHub pull request. If the tests still fail, it's opened as a **draft**. Uses the [GitHub CLI](https://cli.github.com/), so patchpilot never handles a GitHub token. |
78
+ | `--test-command "..."` | How to run your tests (default: `python -m pytest -q`). |
79
+ | `--image python:3.11-slim` | Docker image to run in. Match it to your project's Python version (default: `python:3.13-slim`). |
80
+ | `--no-docker` | Use a local virtualenv instead of Docker. Faster, but your project's code runs directly on your machine. |
81
+
82
+ ## How it works
83
+
84
+ ```mermaid
85
+ flowchart LR
86
+ subgraph host["Your machine"]
87
+ CLI["patchpilot CLI"] --> Loop["Agent loop"]
88
+ Loop <-->|"tool calls ⇄ results"| Claude["Claude API"]
89
+ Loop --> Files["Read / edit files<br/>in a temporary worktree"]
90
+ end
91
+ subgraph sandbox["Docker sandbox: sees only the worktree"]
92
+ Pip["pip install &lt;one package&gt;"]
93
+ Tests["your test suite"]
94
+ end
95
+ Loop --> Pip
96
+ Loop --> Tests
97
+ Loop -->|"verify, commit"| Branch["patchpilot/upgrade-&lt;pkg&gt;"]
98
+ Branch -->|"--pr"| PR["GitHub pull request"]
99
+ ```
100
+
101
+ At the centre is a hand-written [tool-use loop](https://github.com/ilmeedesilva/patchpilot/blob/main/src/patchpilot/agent.py). patchpilot sends Claude the task and a list of tools. Claude replies with the tool it wants to call, patchpilot runs it and sends back the result, and this repeats until Claude writes its final report. Claude decides *what* to do next; patchpilot's code is the only thing that *does* anything.
102
+
103
+ | Tool | Runs where | What it does |
104
+ |---|---|---|
105
+ | `read_dependencies` | host | Parses `requirements*.txt` and `pyproject.toml` |
106
+ | `get_latest_version` | host | Queries PyPI, including changelog URLs |
107
+ | `list_files`, `read_file` | host | Read-only, confined to the project folder |
108
+ | `edit_file`, `write_file` | host | Exact-match edits; refuses `.git/` and virtualenvs |
109
+ | `pip_install` | **sandbox** | One requirement at a time; flags, URLs and file paths are rejected |
110
+ | `run_tests` | **sandbox** | Your test command, with a timeout |
111
+ | `web_fetch` | Anthropic | Reads changelogs and migration guides |
112
+
113
+ ### Safety
114
+
115
+ Upgrading means running code that isn't yours: your tests, and whatever a package runs when it installs. So that code runs inside a Docker container that:
116
+
117
+ - mounts **only the temporary worktree**, not your home folder, SSH keys or other projects
118
+ - runs as your user, not root, with all Linux capabilities dropped and `no-new-privileges`
119
+ - is capped at 2 CPUs, 2 GB of memory and 512 processes
120
+ - is deleted when the run ends, even if the run fails
121
+
122
+ The agent loop and your API key never enter the container. These properties are checked by [integration tests](https://github.com/ilmeedesilva/patchpilot/blob/main/tests/test_sandbox.py), not just described here.
123
+
124
+ ### Design decisions
125
+
126
+ - **Verify, don't trust.** The agent says when it's done, but patchpilot re-runs the tests itself before committing. The ✅ in the output is patchpilot's verdict, not the model's.
127
+ - **Refuse to run when "tests pass" would mean nothing.** patchpilot stops if the tests already fail before the upgrade, or if the package isn't a dependency of the folder you pointed it at. Otherwise a green result could be meaningless.
128
+ - **Fail before spending, not after.** Docker, GitHub login, remotes and branch names are all checked before the first API call.
129
+ - **Leave nothing behind.** The worktree, container and virtualenv are cleaned up on success, failure or Ctrl-C. A branch only survives if it has a commit worth reviewing.
130
+ - **A small, explicit tool surface** instead of a general shell. It's easier to reason about what the agent can do, and easier to sandbox.
131
+
132
+ ## Limitations
133
+
134
+ - Python projects only, with `requirements*.txt` or a PEP 621 `pyproject.toml` (no Poetry or uv lockfiles yet).
135
+ - One package per run.
136
+ - The fix is only as good as your tests. If they don't cover what changed, patchpilot can't catch it, though the agent flags behaviour changes it notices under **Needs your attention**.
137
+ - The sandbox needs network access to install packages, so code inside it can reach the internet.
138
+
139
+ ## Roadmap
140
+
141
+ - [ ] Publish to PyPI (`pipx install patchpilot`)
142
+ - [ ] GitHub Action: run weekly and open PRs automatically
143
+ - [ ] Upgrade several packages in one run, ordered by risk
144
+ - [ ] Turn off the sandbox's network access while tests run
145
+ - [ ] uv and Poetry lockfile support
146
+
147
+ ## Development
148
+
149
+ ```bash
150
+ git clone git@github.com:ilmeedesilva/patchpilot.git && cd patchpilot
151
+ python3 -m venv .venv && source .venv/bin/activate
152
+ pip install -e ".[dev]"
153
+ pytest # Docker tests are skipped if Docker isn't running
154
+ ruff check . && ruff format --check .
155
+ ```
156
+
157
+ The test suite doesn't call the Anthropic API: the agent is replaced with a fake that makes known edits, and GitHub with a local bare repo plus a stand-in `gh`.
158
+
159
+ ## License
160
+
161
+ [MIT](https://github.com/ilmeedesilva/patchpilot/blob/main/LICENSE)
@@ -0,0 +1,61 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "patchpilot"
7
+ dynamic = ["version"]
8
+ description = "An AI agent that upgrades your dependencies, fixes what breaks, and opens a PR"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Ilmee De Silva" }]
13
+ requires-python = ">=3.11"
14
+ keywords = ["ai-agent", "dependencies", "upgrade", "llm", "claude", "developer-tools"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: MacOS",
20
+ "Operating System :: POSIX :: Linux",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Software Development :: Build Tools",
26
+ "Topic :: Software Development :: Quality Assurance",
27
+ ]
28
+ dependencies = [
29
+ "anthropic>=2.3.0",
30
+ "python-dotenv>=1.0",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ dev = ["pytest>=9", "ruff>=0.17", "build>=1.2", "twine>=6"]
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/ilmeedesilva/patchpilot"
38
+ Issues = "https://github.com/ilmeedesilva/patchpilot/issues"
39
+ Demo = "https://github.com/ilmeedesilva/patchpilot-demo"
40
+
41
+ [project.scripts]
42
+ patchpilot = "patchpilot.cli:main"
43
+
44
+ [tool.hatch.version]
45
+ path = "src/patchpilot/__init__.py"
46
+
47
+ [tool.hatch.build.targets.sdist]
48
+ exclude = ["examples", ".github"]
49
+
50
+ [tool.hatch.build.targets.wheel]
51
+ packages = ["src/patchpilot"]
52
+
53
+ [tool.pytest.ini_options]
54
+ testpaths = ["tests"]
55
+
56
+ [tool.ruff]
57
+ line-length = 100
58
+ target-version = "py311"
59
+
60
+ [tool.ruff.lint]
61
+ select = ["E", "F", "I", "B", "UP"]
@@ -0,0 +1,3 @@
1
+ """patchpilot: an AI agent that upgrades dependencies and fixes what breaks."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,90 @@
1
+ """The agent loop: send the conversation to Claude, run the tools it asks for, repeat."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+
7
+ import anthropic
8
+
9
+ from patchpilot.tools import TOOL_FUNCTIONS, ToolError, Workspace
10
+
11
+ MODEL = "claude-opus-5-5"
12
+
13
+ # Anthropic-hosted tool: Claude fetches URLs (like changelogs) itself, no code needed on our side.
14
+ # It only fetches URLs that already appear in the conversation, e.g. from get_latest_version.
15
+ WEB_FETCH_TOOL = {"type": "web_fetch_20260209", "name": "web_fetch", "max_uses": 8}
16
+
17
+
18
+ def execute_tool(ws: Workspace, name: str, tool_input: dict) -> tuple[str, bool]:
19
+ """Run one tool. Returns (result_text, is_error)."""
20
+ func = TOOL_FUNCTIONS.get(name)
21
+ if func is None:
22
+ return f"Unknown tool '{name}'.", True
23
+ try:
24
+ return func(ws, **tool_input), False
25
+ except ToolError as e:
26
+ return str(e), True
27
+ except TypeError as e:
28
+ return f"Invalid arguments for '{name}': {e}", True
29
+
30
+
31
+ def run_agent(
32
+ ws: Workspace,
33
+ system: str,
34
+ task: str,
35
+ tools: list[dict],
36
+ max_turns: int = 30,
37
+ client: anthropic.Anthropic | None = None,
38
+ on_tool_call: Callable[[str, dict], None] | None = None,
39
+ ) -> str:
40
+ """Run the agent until it finishes and return its final text."""
41
+ client = client or anthropic.Anthropic()
42
+ allowed = {t["name"] for t in tools if "input_schema" in t}
43
+ messages: list[dict] = [{"role": "user", "content": task}]
44
+
45
+ for _ in range(max_turns):
46
+ response = client.beta.messages.create(
47
+ model=MODEL,
48
+ max_tokens=16000,
49
+ system=system,
50
+ tools=tools,
51
+ messages=messages,
52
+ output_config={"effort": "high"},
53
+ # If a request is declined by a safety classifier, retry on the server's default
54
+ # fallback model instead of failing outright.
55
+ betas=["server-side-fallback-2026-07-01"],
56
+ fallbacks="default",
57
+ )
58
+ messages.append({"role": "assistant", "content": response.content})
59
+
60
+ if response.stop_reason == "refusal":
61
+ raise RuntimeError("Claude declined this request.")
62
+ if response.stop_reason == "max_tokens":
63
+ raise RuntimeError("Response hit max_tokens before finishing.")
64
+ if response.stop_reason == "pause_turn":
65
+ continue
66
+
67
+ tool_uses = [b for b in response.content if b.type == "tool_use"]
68
+ if not tool_uses:
69
+ return "\n".join(b.text for b in response.content if b.type == "text")
70
+
71
+ tool_results = []
72
+ for block in tool_uses:
73
+ if on_tool_call:
74
+ on_tool_call(block.name, block.input)
75
+ if block.name in allowed:
76
+ result, is_error = execute_tool(ws, block.name, block.input)
77
+ else:
78
+ result, is_error = f"Tool '{block.name}' is not available here.", True
79
+ tool_results.append(
80
+ {
81
+ "type": "tool_result",
82
+ "tool_use_id": block.id,
83
+ "content": result,
84
+ "is_error": is_error,
85
+ }
86
+ )
87
+ # All results for one turn go back in a single user message.
88
+ messages.append({"role": "user", "content": tool_results})
89
+
90
+ raise RuntimeError(f"Agent did not finish within {max_turns} turns.")
@@ -0,0 +1,36 @@
1
+ """`patchpilot analyze`: a read-only report of what could be upgraded."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+
7
+ from patchpilot.agent import run_agent
8
+ from patchpilot.tools import READ_TOOL_SCHEMAS, Workspace
9
+
10
+ SYSTEM_PROMPT = """\
11
+ You are patchpilot, an agent that keeps a Python project's dependencies up to date.
12
+
13
+ Your job right now is to produce an upgrade report. Use the tools to:
14
+ 1. Read the project's declared dependencies.
15
+ 2. Look up the latest version of each one on PyPI.
16
+ 3. Run the test suite to establish a baseline.
17
+
18
+ Then finish with a concise report in Markdown:
19
+ - A table of dependencies: name, current specifier, latest version, and whether the
20
+ current specifier already allows the latest version.
21
+ - Which upgrades look like major-version jumps that are likely to need code changes.
22
+ - The baseline test result.
23
+ - A recommended order to upgrade in, with one line of reasoning each.
24
+
25
+ You can only read and run tests in this phase; do not suggest that you changed any files.\
26
+ """
27
+
28
+
29
+ def analyze(ws: Workspace, on_tool_call: Callable[[str, dict], None] | None = None) -> str:
30
+ return run_agent(
31
+ ws,
32
+ system=SYSTEM_PROMPT,
33
+ task=f"Analyze the project at {ws.root} and write the report.",
34
+ tools=READ_TOOL_SCHEMAS,
35
+ on_tool_call=on_tool_call,
36
+ )