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.
Files changed (43) hide show
  1. messygit-0.4.0/.github/workflows/test.yml +27 -0
  2. messygit-0.4.0/ARCHITECTURE.md +89 -0
  3. messygit-0.4.0/CHANGELOG.md +21 -0
  4. {messygit-0.3.1 → messygit-0.4.0}/PKG-INFO +75 -4
  5. {messygit-0.3.1 → messygit-0.4.0}/README.md +72 -3
  6. {messygit-0.3.1 → messygit-0.4.0}/messygit/agent/agent.py +68 -6
  7. {messygit-0.3.1 → messygit-0.4.0}/messygit/agent/tool.py +8 -4
  8. messygit-0.4.0/messygit/agent/tools.py +247 -0
  9. messygit-0.4.0/messygit/cli.py +216 -0
  10. messygit-0.4.0/messygit/commands/__init__.py +0 -0
  11. messygit-0.4.0/messygit/commands/account_cmds.py +128 -0
  12. messygit-0.4.0/messygit/commands/agent_cmds.py +82 -0
  13. messygit-0.4.0/messygit/commands/app_cmds.py +86 -0
  14. messygit-0.4.0/messygit/commands/git_cmds.py +134 -0
  15. messygit-0.4.0/messygit/commands/trace.py +115 -0
  16. messygit-0.4.0/messygit/commands/usage.py +60 -0
  17. {messygit-0.3.1 → messygit-0.4.0}/messygit/config.py +11 -0
  18. {messygit-0.3.1 → messygit-0.4.0}/messygit/git.py +45 -1
  19. messygit-0.4.0/messygit/prompts.py +230 -0
  20. messygit-0.4.0/messygit/ui/__init__.py +0 -0
  21. messygit-0.4.0/messygit/ui/banner.py +54 -0
  22. messygit-0.4.0/messygit/ui/output.py +29 -0
  23. messygit-0.4.0/messygit/ui/spinner.py +94 -0
  24. messygit-0.4.0/messygit/ui/theme.py +59 -0
  25. {messygit-0.3.1 → messygit-0.4.0}/pyproject.toml +4 -1
  26. messygit-0.4.0/tests/agent_test.py +245 -0
  27. messygit-0.4.0/tests/config_test.py +128 -0
  28. messygit-0.4.0/tests/git_test.py +199 -0
  29. messygit-0.4.0/tests/llm_test.py +105 -0
  30. messygit-0.4.0/tests/tool_schema_test.py +86 -0
  31. messygit-0.4.0/tests/trace_test.py +115 -0
  32. messygit-0.4.0/tests/verbose_test.py +145 -0
  33. messygit-0.3.1/ARCHITECTURE.md +0 -64
  34. messygit-0.3.1/messygit/agent/tools.py +0 -82
  35. messygit-0.3.1/messygit/cli.py +0 -732
  36. messygit-0.3.1/messygit/prompts.py +0 -108
  37. {messygit-0.3.1 → messygit-0.4.0}/.github/workflows/publish.yml +0 -0
  38. {messygit-0.3.1 → messygit-0.4.0}/.gitignore +0 -0
  39. {messygit-0.3.1 → messygit-0.4.0}/.vscode/settings.json +0 -0
  40. {messygit-0.3.1 → messygit-0.4.0}/messygit/__init__.py +0 -0
  41. {messygit-0.3.1 → messygit-0.4.0}/messygit/llm.py +0 -0
  42. {messygit-0.3.1 → messygit-0.4.0}/messygit/models.py +0 -0
  43. {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.1
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 to the model. Your API key is never printed in full.
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 to the model. Your API key is never printed in full.
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
- result = tool.run(**block.input)
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": str(result),
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
- return _text_from_message(response)
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
  }