messygit 0.3.1__tar.gz → 0.4.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.
- messygit-0.4.0/.github/workflows/test.yml +27 -0
- messygit-0.4.0/ARCHITECTURE.md +89 -0
- messygit-0.4.0/CHANGELOG.md +21 -0
- {messygit-0.3.1 → messygit-0.4.0}/PKG-INFO +75 -4
- {messygit-0.3.1 → messygit-0.4.0}/README.md +72 -3
- {messygit-0.3.1 → messygit-0.4.0}/messygit/agent/agent.py +68 -6
- {messygit-0.3.1 → messygit-0.4.0}/messygit/agent/tool.py +8 -4
- messygit-0.4.0/messygit/agent/tools.py +247 -0
- messygit-0.4.0/messygit/cli.py +216 -0
- messygit-0.4.0/messygit/commands/__init__.py +0 -0
- messygit-0.4.0/messygit/commands/account_cmds.py +128 -0
- messygit-0.4.0/messygit/commands/agent_cmds.py +82 -0
- messygit-0.4.0/messygit/commands/app_cmds.py +86 -0
- messygit-0.4.0/messygit/commands/git_cmds.py +134 -0
- messygit-0.4.0/messygit/commands/trace.py +115 -0
- messygit-0.4.0/messygit/commands/usage.py +60 -0
- {messygit-0.3.1 → messygit-0.4.0}/messygit/config.py +11 -0
- {messygit-0.3.1 → messygit-0.4.0}/messygit/git.py +45 -1
- messygit-0.4.0/messygit/prompts.py +230 -0
- messygit-0.4.0/messygit/ui/__init__.py +0 -0
- messygit-0.4.0/messygit/ui/banner.py +54 -0
- messygit-0.4.0/messygit/ui/output.py +29 -0
- messygit-0.4.0/messygit/ui/spinner.py +94 -0
- messygit-0.4.0/messygit/ui/theme.py +59 -0
- {messygit-0.3.1 → messygit-0.4.0}/pyproject.toml +4 -1
- messygit-0.4.0/tests/agent_test.py +245 -0
- messygit-0.4.0/tests/config_test.py +128 -0
- messygit-0.4.0/tests/git_test.py +199 -0
- messygit-0.4.0/tests/llm_test.py +105 -0
- messygit-0.4.0/tests/tool_schema_test.py +86 -0
- messygit-0.4.0/tests/trace_test.py +115 -0
- messygit-0.4.0/tests/verbose_test.py +145 -0
- messygit-0.3.1/ARCHITECTURE.md +0 -64
- messygit-0.3.1/messygit/agent/tools.py +0 -82
- messygit-0.3.1/messygit/cli.py +0 -732
- messygit-0.3.1/messygit/prompts.py +0 -108
- {messygit-0.3.1 → messygit-0.4.0}/.github/workflows/publish.yml +0 -0
- {messygit-0.3.1 → messygit-0.4.0}/.gitignore +0 -0
- {messygit-0.3.1 → messygit-0.4.0}/.vscode/settings.json +0 -0
- {messygit-0.3.1 → messygit-0.4.0}/messygit/__init__.py +0 -0
- {messygit-0.3.1 → messygit-0.4.0}/messygit/llm.py +0 -0
- {messygit-0.3.1 → messygit-0.4.0}/messygit/models.py +0 -0
- {messygit-0.3.1 → messygit-0.4.0}/messygit/usage.py +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
|
|
23
|
+
- name: Install package with dev dependencies
|
|
24
|
+
run: pip install -e ".[dev]"
|
|
25
|
+
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: pytest -q
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
This document describes the purpose of each file in the `messygit` project.
|
|
4
|
+
|
|
5
|
+
The package is organized in layers. `ui/` is the shared presentation foundation
|
|
6
|
+
(console, theme, spinner, banner). `commands/` holds the command handlers grouped
|
|
7
|
+
the same way the `help` screen groups them. `agent/` is a self-contained tool-use
|
|
8
|
+
loop. `cli.py` is a thin shell that wires everything together. Dependencies flow
|
|
9
|
+
one way — `ui/` knows nothing about `commands/`, and `commands/` knows nothing
|
|
10
|
+
about `cli` — so there are no import cycles.
|
|
11
|
+
|
|
12
|
+
## Root
|
|
13
|
+
|
|
14
|
+
| File | Purpose |
|
|
15
|
+
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
16
|
+
| `pyproject.toml` | Package metadata, dependencies (`anthropic`, `click`, `rich`), build system (hatchling), and the `messygit` console script. |
|
|
17
|
+
| `README.md` | User-facing documentation: install, usage, commands, and development instructions. |
|
|
18
|
+
| `ARCHITECTURE.md` | This file — a per-module map of the codebase. |
|
|
19
|
+
| `CHANGELOG.md` | Release notes, generated/updated by the `changelog` command. |
|
|
20
|
+
| `.gitignore` | Keeps `.venv/`, `__pycache__/`, `dist/`, and `*.egg-info/` out of version control. |
|
|
21
|
+
| `.github/workflows/test.yml` | Runs `pytest` on every push/PR across Python 3.10–3.13. |
|
|
22
|
+
| `.github/workflows/publish.yml` | Builds and publishes to PyPI via trusted publishing on release. |
|
|
23
|
+
|
|
24
|
+
## `messygit/` (core package)
|
|
25
|
+
|
|
26
|
+
| File | Purpose |
|
|
27
|
+
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
28
|
+
| `__init__.py` | Marks the directory as a Python package. |
|
|
29
|
+
| `cli.py` | The REPL shell: prints the startup dashboard, runs the prompt loop, owns the `COMMANDS` dispatch table and the `help` screen, and applies the saved theme on launch. Delegates all real work to `commands/`. |
|
|
30
|
+
| `git.py` | All subprocess calls to `git`. Reads staged diffs (`git diff --cached -U0`), parses them into a compact changed-lines format, filters noise files, handles the large-diff fallback (stat summary + top-N changed files), and runs `add`/`commit`/`push`. |
|
|
31
|
+
| `llm.py` | Anthropic SDK integration for the (non-agentic) `commit` path. Creates the client, calls `messages.create`, extracts text, and maps SDK exceptions (auth, permission, billing/402) into user-friendly error classes. Exposes helpers reused by `agent/agent.py`. |
|
|
32
|
+
| `config.py` | Reads/writes `~/.messygit/config.json`: API key (plus `ANTHROPIC_API_KEY` env resolution), theme, model, todo, and the `verbose` flag. Masks keys for display and defines user-facing error messages and exception classes. |
|
|
33
|
+
| `models.py` | The selectable Claude models, their labels, and approximate per-million-token pricing; resolves the active model from config. |
|
|
34
|
+
| `usage.py` | Session-local token-usage tracker (`SESSION_USAGE`) and the billing URL. The API exposes no balance endpoint, so usage/cost are accumulated per session. |
|
|
35
|
+
| `prompts.py` | The three system prompts — `COMMIT_SYSTEM_PROMPT`, `SUGGESTION_SYSTEM_PROMPT`, `CHANGELOG_SYSTEM_PROMPT` — plus `build_user_prompt`. Each prompt includes anti-hallucination and untrusted-input guardrails. |
|
|
36
|
+
|
|
37
|
+
## `messygit/ui/` (shared presentation)
|
|
38
|
+
|
|
39
|
+
| File | Purpose |
|
|
40
|
+
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| `theme.py` | Color palette and theme state. `BRAND`/`BRAND_RGB`/`BANNER_COLOR` are reassigned at runtime by `apply_theme()`, so other modules read them as `theme.BRAND` (never import the name directly). Also `brand_ansi()` and `active_theme()`. |
|
|
42
|
+
| `output.py` | The shared `console`/`err_console` objects and the small print helpers (`print_error`, `success`, `warn`, `field`). |
|
|
43
|
+
| `spinner.py` | The threaded `_CharSpinner` loading animation and the `spinner()` factory. |
|
|
44
|
+
| `banner.py` | The ASCII banner art and its boot animation (`animate_banner`). |
|
|
45
|
+
|
|
46
|
+
## `messygit/commands/` (command handlers)
|
|
47
|
+
|
|
48
|
+
| File | Purpose |
|
|
49
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
50
|
+
| `git_cmds.py` | The git group: `add`, `commit` (AI message + Y/n/e prompt), `push`, `outbox`. |
|
|
51
|
+
| `agent_cmds.py` | The agent group: `suggest` and `changelog`. `_drive()` runs an agent either under a spinner or streaming live (verbose), and records the run's trace. |
|
|
52
|
+
| `account_cmds.py` | The account group: `config`, `show`, `model`, `tokens`. |
|
|
53
|
+
| `app_cmds.py` | The app group: `todo`, `theme`, `verbose`. |
|
|
54
|
+
| `usage.py` | Token-usage/cost *display* helpers (`usage_summary`, `print_usage_delta`, `model_pricing`, the high-usage warning). Distinct from the root `usage.py` tracker. |
|
|
55
|
+
| `trace.py` | The `trace` command. Stores the last agent run's steps and renders them as a panel; also provides `live_reporter()`, the per-step formatter that verbose mode streams. |
|
|
56
|
+
|
|
57
|
+
## `messygit/agent/` (agentic tool-use subpackage)
|
|
58
|
+
|
|
59
|
+
| File | Purpose |
|
|
60
|
+
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
61
|
+
| `tool.py` | `Tool` dataclass wrapping a Python callable with a name, description, and parameter schema. `run()` invokes the function; `to_schema()` emits an Anthropic-compatible tool definition. |
|
|
62
|
+
| `tools.py` | Concrete tools: `run_git_tool` (allowlisted read-only git, including tag ranges), `read_file_tool`, `list_directory_tool`, `search_code_tool` (`git grep`), `write_file_tool` (full overwrite), and `edit_file_tool` (unique-match string replace). All file tools reject paths outside the repo root. |
|
|
63
|
+
| `agent.py` | The agentic loop. Sends messages to Claude with tool definitions, dispatches `tool_use` blocks to matching `Tool`s, feeds results back, and iterates up to `max_iterations`. Records each step as a `TraceStep` (optionally streamed via an `on_step` callback) and warns when the iteration cap is hit before finishing. |
|
|
64
|
+
|
|
65
|
+
## Data flow
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
User runs `messygit` → cli.py REPL (dispatch table)
|
|
69
|
+
│
|
|
70
|
+
├── "add <files>" / "push" ─────► commands/git_cmds.py ─► git.py
|
|
71
|
+
├── "config" / "show" / "model" / "tokens" ─► commands/account_cmds.py ─► config.py / usage.py
|
|
72
|
+
├── "todo" / "theme" / "verbose" ─────► commands/app_cmds.py ─► config.py
|
|
73
|
+
├── "trace" ─────────────────────► commands/trace.py (render last run)
|
|
74
|
+
│
|
|
75
|
+
├── "commit" ──► commands/git_cmds.py
|
|
76
|
+
│ │ ├── git.py (staged diff + token budget)
|
|
77
|
+
│ │ └── llm.py ─► prompts.COMMIT_SYSTEM_PROMPT, config.py (key)
|
|
78
|
+
│ ▼
|
|
79
|
+
│ Y/n/e prompt ─► git.py (git commit)
|
|
80
|
+
│
|
|
81
|
+
└── "suggest" / "changelog" ──► commands/agent_cmds.py
|
|
82
|
+
│ └── agent/agent.py (tool-use loop)
|
|
83
|
+
│ ├── config.py (key) + models.py
|
|
84
|
+
│ ├── prompts.{SUGGESTION,CHANGELOG}_SYSTEM_PROMPT
|
|
85
|
+
│ └── agent/tools.py (run_git, read_file, list_directory,
|
|
86
|
+
│ write_file, edit_file)
|
|
87
|
+
▼
|
|
88
|
+
spinner OR live step stream (verbose); steps saved for `trace`
|
|
89
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## [v0.3.2] - 2026-06-20
|
|
2
|
+
### Added
|
|
3
|
+
- Error handling for unknown tools—agent now returns an error result instead of crashing when the model requests a non-existent tool
|
|
4
|
+
- Exception handling for tool execution—tool errors are caught and reported back to the model as error results
|
|
5
|
+
- `outbox` command to display committed but unpushed commits, showing how many commits are ahead of the upstream branch
|
|
6
|
+
- Input validation and security checks for `read_file` and `list_directory` tools to prevent directory traversal attacks
|
|
7
|
+
- Enhanced tool descriptions with detailed documentation of parameters and usage examples
|
|
8
|
+
- Support for `required` field in tool schemas for better validation
|
|
9
|
+
- Comprehensive pytest test suite covering agent tool execution, config management, git diff parsing, LLM error handling, and tool schemas
|
|
10
|
+
- GitHub Actions CI workflow for automated testing across Python 3.10–3.13
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Tool schema generation now includes required field declarations
|
|
14
|
+
- Improved error messages throughout the CLI for better user guidance
|
|
15
|
+
- Tool functions now validate inputs for security and proper error reporting
|
|
16
|
+
|
|
17
|
+
## [v0.3.1] - 2026-06-20
|
|
18
|
+
|
|
19
|
+
## [v0.3.0] - 2026-06-20
|
|
20
|
+
|
|
21
|
+
## [v0.2.1] - 2026-06-20
|
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: messygit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: An AI-powered interactive git CLI with agentic tools for commits, code suggestions, and workflow automation.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Requires-Dist: anthropic>=0.39.0
|
|
8
8
|
Requires-Dist: click>=8.0
|
|
9
9
|
Requires-Dist: rich>=13.0
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
10
12
|
Description-Content-Type: text/markdown
|
|
11
13
|
|
|
12
14
|
# messygit
|
|
13
15
|
|
|
14
|
-
**messygit** is an interactive CLI that turns messy git workflows into clean Conventional Commits — stage, commit, push, and get AI-powered project suggestions, all from one interface powered by [Claude](https://www.anthropic.com/api).
|
|
16
|
+
**messygit** is an interactive CLI that turns messy git workflows into clean Conventional Commits — stage, commit, push, generate changelogs, and get AI-powered project suggestions, all from one interface powered by [Claude](https://www.anthropic.com/api).
|
|
15
17
|
|
|
16
18
|
## Why use it
|
|
17
19
|
|
|
18
20
|
- **Interactive REPL** — one command drops you into a persistent session where you can stage, commit, push, and more without leaving.
|
|
19
21
|
- **AI commit messages** — sends your staged diff to Claude and suggests a clean Conventional Commits subject line.
|
|
20
22
|
- **Project suggestions** — an AI agent inspects your repo and recommends concrete next steps.
|
|
23
|
+
- **Changelog generation** — an agent reads the commits between your two latest tags, drills into unclear ones, categorizes the changes, and writes/updates `CHANGELOG.md`.
|
|
24
|
+
- **See what the agent did** — runs are clean by default; type `trace` to expand the last run's tool calls, or flip `verbose` on to stream each step live.
|
|
21
25
|
- **Token usage & cost** — tracks the tokens each session uses and shows a rough cost estimate, with a one-command jump to billing.
|
|
22
26
|
- **Themed UI** — a colored startup animation and prompt you can recolor with the `theme` command.
|
|
23
|
-
- **Safe by default** — only the staged diff is sent
|
|
27
|
+
- **Safe by default** — only the staged diff is sent for `commit`; agents use read-only git plus repo-scoped file tools that reject paths outside the repository. Your API key is never printed in full.
|
|
24
28
|
|
|
25
29
|
## Requirements
|
|
26
30
|
|
|
@@ -94,12 +98,15 @@ Commands are grouped on the `help` screen:
|
|
|
94
98
|
| `add <file>` or `add .` | Stage files for commit |
|
|
95
99
|
| `commit` | Generate an AI commit message from staged changes, then commit / cancel / edit |
|
|
96
100
|
| `push` | Push commits to remote |
|
|
101
|
+
| `outbox` | Show commits made locally but not yet pushed to the upstream branch |
|
|
97
102
|
|
|
98
103
|
**messyagent**
|
|
99
104
|
|
|
100
105
|
| Command | Description |
|
|
101
106
|
|---------|-------------|
|
|
102
107
|
| `suggest` | Get AI-powered next-step suggestions for your project |
|
|
108
|
+
| `changelog` | Generate or update `CHANGELOG.md` from the commits between your two latest tags (requires at least one tag) |
|
|
109
|
+
| `trace` | Show what the last agent run actually did — its tool calls and results |
|
|
103
110
|
|
|
104
111
|
**account**
|
|
105
112
|
|
|
@@ -116,6 +123,7 @@ Commands are grouped on the `help` screen:
|
|
|
116
123
|
|---------|-------------|
|
|
117
124
|
| `todo` | Open your todo list in `$EDITOR` (saved to `~/.messygit/todo.md`) |
|
|
118
125
|
| `theme` or `theme <name>` | Change the UI color (run `theme` to list presets) |
|
|
126
|
+
| `verbose` or `verbose on\|off` | Toggle live streaming of agent steps (persists; off by default) |
|
|
119
127
|
| `help` | List available commands |
|
|
120
128
|
| `quit` / `exit` | Exit messygit |
|
|
121
129
|
|
|
@@ -134,6 +142,41 @@ messygit > push
|
|
|
134
142
|
|
|
135
143
|
> Tip: run `suggest` for AI next-step ideas, or `theme` to recolor the UI.
|
|
136
144
|
|
|
145
|
+
### Agent commands & transparency
|
|
146
|
+
|
|
147
|
+
`suggest` and `changelog` are backed by an agent that uses tools (read-only git,
|
|
148
|
+
file reads, and — for `changelog` — repo-scoped file writes) over several steps.
|
|
149
|
+
|
|
150
|
+
By default a run shows only a spinner and the final result. Two commands let you
|
|
151
|
+
see inside:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
messygit > changelog # generates/updates CHANGELOG.md
|
|
155
|
+
messygit > trace # expand what that run just did
|
|
156
|
+
|
|
157
|
+
╭─ trace · changelog · 4 tool calls ─────────────╮
|
|
158
|
+
│ 1. run_git tag --sort=-creatordate │
|
|
159
|
+
│ └ v0.4.0 (+3 more lines) │
|
|
160
|
+
│ 2. run_git log v0.3.2..v0.4.0 │
|
|
161
|
+
│ └ commit a1b2c3 feat: … (+12 more lines) │
|
|
162
|
+
│ 3. read_file CHANGELOG.md │
|
|
163
|
+
│ └ ## [v0.3.2] - 2026-06-20 │
|
|
164
|
+
│ 4. edit_file CHANGELOG.md │
|
|
165
|
+
│ └ File edited successfully. │
|
|
166
|
+
╰─────────────────────────────────────────────────╯
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Prefer to watch it happen live? Turn on `verbose` — the same steps stream as the
|
|
170
|
+
agent works (no spinner). The setting persists in `~/.messygit/config.json`:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
messygit > verbose on
|
|
174
|
+
Verbose on — agent runs will stream their steps live (no spinner)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`changelog` requires at least one git tag; it documents the range between your two
|
|
178
|
+
most recent tags (or the latest tag to `HEAD` when only one exists).
|
|
179
|
+
|
|
137
180
|
### Commit message style
|
|
138
181
|
|
|
139
182
|
The model follows **Conventional Commits**: `type(scope): description`
|
|
@@ -142,7 +185,7 @@ Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`. Subj
|
|
|
142
185
|
|
|
143
186
|
### Token usage & cost
|
|
144
187
|
|
|
145
|
-
messygit tracks the tokens used by AI commands (`commit`, `suggest`) for the current session and shows a running total after each call. Run `tokens` for a breakdown and a one-key jump to the Anthropic billing console:
|
|
188
|
+
messygit tracks the tokens used by AI commands (`commit`, `suggest`, `changelog`) for the current session and shows a running total after each call. Run `tokens` for a breakdown and a one-key jump to the Anthropic billing console:
|
|
146
189
|
|
|
147
190
|
```
|
|
148
191
|
messygit > tokens
|
|
@@ -174,6 +217,34 @@ Switch anyway? [y/N]:
|
|
|
174
217
|
|
|
175
218
|
Switching to a **more expensive** model prompts for confirmation first. The session cost estimate prices each request at the model used for it, so it stays accurate even if you switch mid-session.
|
|
176
219
|
|
|
220
|
+
## Development & testing
|
|
221
|
+
|
|
222
|
+
Install the package with its dev dependencies (pytest), then run the suite:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
pip install -e ".[dev]"
|
|
226
|
+
pytest -q
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The tests live in `tests/` and are pure unit tests — no network calls and no
|
|
230
|
+
API key required (the Anthropic client is simulated). They cover:
|
|
231
|
+
|
|
232
|
+
| File | What it covers |
|
|
233
|
+
|------|----------------|
|
|
234
|
+
| `tests/config_test.py` | API-key resolution order (env → file), key save/load, theme/model/todo persistence, malformed-config handling |
|
|
235
|
+
| `tests/git_test.py` | The diff parser — noise-file filtering and the compact changed-lines format |
|
|
236
|
+
| `tests/llm_test.py` | Insufficient-balance / billing-error detection and user messaging |
|
|
237
|
+
| `tests/tool_schema_test.py` | Agent tool schemas match the shape the Anthropic Messages API expects |
|
|
238
|
+
| `tests/agent_test.py` | The agent's tool-use loop, driven by a simulated Anthropic client with scripted responses |
|
|
239
|
+
| `tests/trace_test.py` | The `trace` renderer — step numbering, result truncation, empty state, and markup-safety of raw tool output |
|
|
240
|
+
| `tests/verbose_test.py` | The `verbose` setting, the toggle command, and `_drive` choosing live-stream vs. spinner |
|
|
241
|
+
|
|
242
|
+
### Continuous integration
|
|
243
|
+
|
|
244
|
+
`.github/workflows/test.yml` runs `pytest` on every push to `main` and on every
|
|
245
|
+
pull request, across Python 3.10–3.13. No secrets are required because the tests
|
|
246
|
+
mock the Anthropic client.
|
|
247
|
+
|
|
177
248
|
## Publishing to PyPI
|
|
178
249
|
|
|
179
250
|
This project uses GitHub Actions with [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/) — no API tokens needed in your repo.
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# messygit
|
|
2
2
|
|
|
3
|
-
**messygit** is an interactive CLI that turns messy git workflows into clean Conventional Commits — stage, commit, push, and get AI-powered project suggestions, all from one interface powered by [Claude](https://www.anthropic.com/api).
|
|
3
|
+
**messygit** is an interactive CLI that turns messy git workflows into clean Conventional Commits — stage, commit, push, generate changelogs, and get AI-powered project suggestions, all from one interface powered by [Claude](https://www.anthropic.com/api).
|
|
4
4
|
|
|
5
5
|
## Why use it
|
|
6
6
|
|
|
7
7
|
- **Interactive REPL** — one command drops you into a persistent session where you can stage, commit, push, and more without leaving.
|
|
8
8
|
- **AI commit messages** — sends your staged diff to Claude and suggests a clean Conventional Commits subject line.
|
|
9
9
|
- **Project suggestions** — an AI agent inspects your repo and recommends concrete next steps.
|
|
10
|
+
- **Changelog generation** — an agent reads the commits between your two latest tags, drills into unclear ones, categorizes the changes, and writes/updates `CHANGELOG.md`.
|
|
11
|
+
- **See what the agent did** — runs are clean by default; type `trace` to expand the last run's tool calls, or flip `verbose` on to stream each step live.
|
|
10
12
|
- **Token usage & cost** — tracks the tokens each session uses and shows a rough cost estimate, with a one-command jump to billing.
|
|
11
13
|
- **Themed UI** — a colored startup animation and prompt you can recolor with the `theme` command.
|
|
12
|
-
- **Safe by default** — only the staged diff is sent
|
|
14
|
+
- **Safe by default** — only the staged diff is sent for `commit`; agents use read-only git plus repo-scoped file tools that reject paths outside the repository. Your API key is never printed in full.
|
|
13
15
|
|
|
14
16
|
## Requirements
|
|
15
17
|
|
|
@@ -83,12 +85,15 @@ Commands are grouped on the `help` screen:
|
|
|
83
85
|
| `add <file>` or `add .` | Stage files for commit |
|
|
84
86
|
| `commit` | Generate an AI commit message from staged changes, then commit / cancel / edit |
|
|
85
87
|
| `push` | Push commits to remote |
|
|
88
|
+
| `outbox` | Show commits made locally but not yet pushed to the upstream branch |
|
|
86
89
|
|
|
87
90
|
**messyagent**
|
|
88
91
|
|
|
89
92
|
| Command | Description |
|
|
90
93
|
|---------|-------------|
|
|
91
94
|
| `suggest` | Get AI-powered next-step suggestions for your project |
|
|
95
|
+
| `changelog` | Generate or update `CHANGELOG.md` from the commits between your two latest tags (requires at least one tag) |
|
|
96
|
+
| `trace` | Show what the last agent run actually did — its tool calls and results |
|
|
92
97
|
|
|
93
98
|
**account**
|
|
94
99
|
|
|
@@ -105,6 +110,7 @@ Commands are grouped on the `help` screen:
|
|
|
105
110
|
|---------|-------------|
|
|
106
111
|
| `todo` | Open your todo list in `$EDITOR` (saved to `~/.messygit/todo.md`) |
|
|
107
112
|
| `theme` or `theme <name>` | Change the UI color (run `theme` to list presets) |
|
|
113
|
+
| `verbose` or `verbose on\|off` | Toggle live streaming of agent steps (persists; off by default) |
|
|
108
114
|
| `help` | List available commands |
|
|
109
115
|
| `quit` / `exit` | Exit messygit |
|
|
110
116
|
|
|
@@ -123,6 +129,41 @@ messygit > push
|
|
|
123
129
|
|
|
124
130
|
> Tip: run `suggest` for AI next-step ideas, or `theme` to recolor the UI.
|
|
125
131
|
|
|
132
|
+
### Agent commands & transparency
|
|
133
|
+
|
|
134
|
+
`suggest` and `changelog` are backed by an agent that uses tools (read-only git,
|
|
135
|
+
file reads, and — for `changelog` — repo-scoped file writes) over several steps.
|
|
136
|
+
|
|
137
|
+
By default a run shows only a spinner and the final result. Two commands let you
|
|
138
|
+
see inside:
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
messygit > changelog # generates/updates CHANGELOG.md
|
|
142
|
+
messygit > trace # expand what that run just did
|
|
143
|
+
|
|
144
|
+
╭─ trace · changelog · 4 tool calls ─────────────╮
|
|
145
|
+
│ 1. run_git tag --sort=-creatordate │
|
|
146
|
+
│ └ v0.4.0 (+3 more lines) │
|
|
147
|
+
│ 2. run_git log v0.3.2..v0.4.0 │
|
|
148
|
+
│ └ commit a1b2c3 feat: … (+12 more lines) │
|
|
149
|
+
│ 3. read_file CHANGELOG.md │
|
|
150
|
+
│ └ ## [v0.3.2] - 2026-06-20 │
|
|
151
|
+
│ 4. edit_file CHANGELOG.md │
|
|
152
|
+
│ └ File edited successfully. │
|
|
153
|
+
╰─────────────────────────────────────────────────╯
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Prefer to watch it happen live? Turn on `verbose` — the same steps stream as the
|
|
157
|
+
agent works (no spinner). The setting persists in `~/.messygit/config.json`:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
messygit > verbose on
|
|
161
|
+
Verbose on — agent runs will stream their steps live (no spinner)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`changelog` requires at least one git tag; it documents the range between your two
|
|
165
|
+
most recent tags (or the latest tag to `HEAD` when only one exists).
|
|
166
|
+
|
|
126
167
|
### Commit message style
|
|
127
168
|
|
|
128
169
|
The model follows **Conventional Commits**: `type(scope): description`
|
|
@@ -131,7 +172,7 @@ Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`. Subj
|
|
|
131
172
|
|
|
132
173
|
### Token usage & cost
|
|
133
174
|
|
|
134
|
-
messygit tracks the tokens used by AI commands (`commit`, `suggest`) for the current session and shows a running total after each call. Run `tokens` for a breakdown and a one-key jump to the Anthropic billing console:
|
|
175
|
+
messygit tracks the tokens used by AI commands (`commit`, `suggest`, `changelog`) for the current session and shows a running total after each call. Run `tokens` for a breakdown and a one-key jump to the Anthropic billing console:
|
|
135
176
|
|
|
136
177
|
```
|
|
137
178
|
messygit > tokens
|
|
@@ -163,6 +204,34 @@ Switch anyway? [y/N]:
|
|
|
163
204
|
|
|
164
205
|
Switching to a **more expensive** model prompts for confirmation first. The session cost estimate prices each request at the model used for it, so it stays accurate even if you switch mid-session.
|
|
165
206
|
|
|
207
|
+
## Development & testing
|
|
208
|
+
|
|
209
|
+
Install the package with its dev dependencies (pytest), then run the suite:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
pip install -e ".[dev]"
|
|
213
|
+
pytest -q
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The tests live in `tests/` and are pure unit tests — no network calls and no
|
|
217
|
+
API key required (the Anthropic client is simulated). They cover:
|
|
218
|
+
|
|
219
|
+
| File | What it covers |
|
|
220
|
+
|------|----------------|
|
|
221
|
+
| `tests/config_test.py` | API-key resolution order (env → file), key save/load, theme/model/todo persistence, malformed-config handling |
|
|
222
|
+
| `tests/git_test.py` | The diff parser — noise-file filtering and the compact changed-lines format |
|
|
223
|
+
| `tests/llm_test.py` | Insufficient-balance / billing-error detection and user messaging |
|
|
224
|
+
| `tests/tool_schema_test.py` | Agent tool schemas match the shape the Anthropic Messages API expects |
|
|
225
|
+
| `tests/agent_test.py` | The agent's tool-use loop, driven by a simulated Anthropic client with scripted responses |
|
|
226
|
+
| `tests/trace_test.py` | The `trace` renderer — step numbering, result truncation, empty state, and markup-safety of raw tool output |
|
|
227
|
+
| `tests/verbose_test.py` | The `verbose` setting, the toggle command, and `_drive` choosing live-stream vs. spinner |
|
|
228
|
+
|
|
229
|
+
### Continuous integration
|
|
230
|
+
|
|
231
|
+
`.github/workflows/test.yml` runs `pytest` on every push to `main` and on every
|
|
232
|
+
pull request, across Python 3.10–3.13. No secrets are required because the tests
|
|
233
|
+
mock the Anthropic client.
|
|
234
|
+
|
|
166
235
|
## Publishing to PyPI
|
|
167
236
|
|
|
168
237
|
This project uses GitHub Actions with [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/) — no API tokens needed in your repo.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
1
3
|
from .tool import Tool
|
|
2
4
|
from anthropic import (
|
|
3
5
|
Anthropic,
|
|
@@ -20,21 +22,52 @@ from ..usage import SESSION_USAGE
|
|
|
20
22
|
|
|
21
23
|
DEFAULT_MAX_TOKENS = 4096
|
|
22
24
|
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class TraceStep:
|
|
28
|
+
"""One recorded step of an agent run, for the `trace` command.
|
|
29
|
+
|
|
30
|
+
kind == "text": the model's narration (`text` holds it).
|
|
31
|
+
kind == "tool": a tool call (`name`/`tool_input`/`result`/`is_error`).
|
|
32
|
+
"""
|
|
33
|
+
kind: str
|
|
34
|
+
text: str = ""
|
|
35
|
+
name: str = ""
|
|
36
|
+
tool_input: dict = field(default_factory=dict)
|
|
37
|
+
result: str = ""
|
|
38
|
+
is_error: bool = False
|
|
39
|
+
|
|
40
|
+
|
|
23
41
|
class Agent:
|
|
24
42
|
def __init__(self, name: str, system_prompt: str, max_iterations: int, tools: list[Tool]):
|
|
25
43
|
self.name = name
|
|
26
44
|
self.system_prompt = system_prompt
|
|
27
45
|
self.max_iterations = max_iterations
|
|
28
46
|
self.tools = tools
|
|
47
|
+
# Populated on each run(); read afterwards by the `trace` command.
|
|
48
|
+
self.steps: list[TraceStep] = []
|
|
29
49
|
|
|
30
|
-
def run(self, user_input: str) -> str:
|
|
31
|
-
"""Run the agent.
|
|
50
|
+
def run(self, user_input: str, on_step=None) -> str:
|
|
51
|
+
"""Run the agent.
|
|
52
|
+
|
|
53
|
+
`on_step`, if given, is called with each TraceStep as it happens (used by
|
|
54
|
+
verbose mode to stream steps live). The full list is also kept on
|
|
55
|
+
self.steps for the `trace` command.
|
|
56
|
+
"""
|
|
32
57
|
client = Anthropic(api_key=resolve_api_key())
|
|
33
58
|
model = current_model()
|
|
34
59
|
messages = []
|
|
60
|
+
self.steps = []
|
|
61
|
+
|
|
62
|
+
def record(step: TraceStep) -> None:
|
|
63
|
+
self.steps.append(step)
|
|
64
|
+
if on_step is not None:
|
|
65
|
+
on_step(step)
|
|
66
|
+
|
|
35
67
|
try:
|
|
36
68
|
messages.append({"role": "user", "content": user_input})
|
|
37
69
|
response = None
|
|
70
|
+
completed = False
|
|
38
71
|
for i in range(self.max_iterations):
|
|
39
72
|
response = client.messages.create(
|
|
40
73
|
model=model.id,
|
|
@@ -47,18 +80,38 @@ class Agent:
|
|
|
47
80
|
SESSION_USAGE.record(response.usage, model)
|
|
48
81
|
messages.append({"role": "assistant", "content": response.content})
|
|
49
82
|
|
|
83
|
+
for block in response.content:
|
|
84
|
+
if getattr(block, "type", None) == "text" and block.text.strip():
|
|
85
|
+
record(TraceStep(kind="text", text=block.text.strip()))
|
|
86
|
+
|
|
50
87
|
tool_use_blocks = [b for b in response.content if b.type == "tool_use"]
|
|
51
88
|
if not tool_use_blocks:
|
|
89
|
+
completed = True
|
|
52
90
|
break
|
|
53
91
|
|
|
54
92
|
tool_results = []
|
|
55
93
|
for block in tool_use_blocks:
|
|
56
|
-
tool = next(t for t in self.tools if t.name == block.name)
|
|
57
|
-
|
|
94
|
+
tool = next((t for t in self.tools if t.name == block.name), None)
|
|
95
|
+
if tool is None:
|
|
96
|
+
content, is_error = f"Unknown tool: {block.name!r}.", True
|
|
97
|
+
else:
|
|
98
|
+
try:
|
|
99
|
+
content, is_error = str(tool.run(**block.input)), False
|
|
100
|
+
except Exception as e:
|
|
101
|
+
content = f"Error running tool {block.name!r}: {e}"
|
|
102
|
+
is_error = True
|
|
103
|
+
record(TraceStep(
|
|
104
|
+
kind="tool",
|
|
105
|
+
name=block.name,
|
|
106
|
+
tool_input=dict(block.input),
|
|
107
|
+
result=content,
|
|
108
|
+
is_error=is_error,
|
|
109
|
+
))
|
|
58
110
|
tool_results.append({
|
|
59
111
|
"type": "tool_result",
|
|
60
112
|
"tool_use_id": block.id,
|
|
61
|
-
"content":
|
|
113
|
+
"content": content,
|
|
114
|
+
**({"is_error": True} if is_error else {}),
|
|
62
115
|
})
|
|
63
116
|
messages.append({"role": "user", "content": tool_results})
|
|
64
117
|
except AuthenticationError as e:
|
|
@@ -79,4 +132,13 @@ class Agent:
|
|
|
79
132
|
raise
|
|
80
133
|
if not response:
|
|
81
134
|
return "No response from the agent."
|
|
82
|
-
|
|
135
|
+
text = _text_from_message(response)
|
|
136
|
+
if not completed:
|
|
137
|
+
warning = (
|
|
138
|
+
f"⚠️ Stopped after reaching the {self.max_iterations}-iteration "
|
|
139
|
+
"limit before finishing. The task is incomplete — any file the "
|
|
140
|
+
"agent was meant to write may be missing or partial. Try again, "
|
|
141
|
+
"and consider raising the iteration limit if this recurs."
|
|
142
|
+
)
|
|
143
|
+
return f"{warning}\n\n{text}" if text else warning
|
|
144
|
+
return text
|
|
@@ -12,17 +12,21 @@ class Tool:
|
|
|
12
12
|
description: str
|
|
13
13
|
function: Callable[..., Any]
|
|
14
14
|
parameters: dict[str, Any] = field(default_factory=dict)
|
|
15
|
+
required: list[str] = field(default_factory=list)
|
|
15
16
|
|
|
16
17
|
def run(self, **kwargs: Any) -> Any:
|
|
17
18
|
return self.function(**kwargs)
|
|
18
19
|
|
|
19
20
|
def to_schema(self) -> dict[str, Any]:
|
|
20
21
|
"""Return an Anthropic-compatible tool schema for API calls."""
|
|
22
|
+
input_schema: dict[str, Any] = {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"properties": self.parameters,
|
|
25
|
+
}
|
|
26
|
+
if self.required:
|
|
27
|
+
input_schema["required"] = self.required
|
|
21
28
|
return {
|
|
22
29
|
"name": self.name,
|
|
23
30
|
"description": self.description,
|
|
24
|
-
"input_schema":
|
|
25
|
-
"type": "object",
|
|
26
|
-
"properties": self.parameters,
|
|
27
|
-
},
|
|
31
|
+
"input_schema": input_schema,
|
|
28
32
|
}
|