maajun 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.
Files changed (86) hide show
  1. maajun-0.1.0/.coverage +0 -0
  2. maajun-0.1.0/.github/workflows/ci.yml +65 -0
  3. maajun-0.1.0/.github/workflows/release.yml +44 -0
  4. maajun-0.1.0/.gitignore +29 -0
  5. maajun-0.1.0/.python-version +1 -0
  6. maajun-0.1.0/LICENSE +21 -0
  7. maajun-0.1.0/PKG-INFO +241 -0
  8. maajun-0.1.0/README.md +207 -0
  9. maajun-0.1.0/docs/architecture.md +201 -0
  10. maajun-0.1.0/docs/commands.md +187 -0
  11. maajun-0.1.0/docs/monitoring.md +472 -0
  12. maajun-0.1.0/pyproject.toml +89 -0
  13. maajun-0.1.0/src/maajun/__init__.py +0 -0
  14. maajun-0.1.0/src/maajun/agent/__init__.py +3 -0
  15. maajun-0.1.0/src/maajun/agent/core.py +324 -0
  16. maajun-0.1.0/src/maajun/agent/tools/__init__.py +36 -0
  17. maajun-0.1.0/src/maajun/agent/tools/base.py +64 -0
  18. maajun-0.1.0/src/maajun/agent/tools/files.py +133 -0
  19. maajun-0.1.0/src/maajun/agent/tools/search.py +201 -0
  20. maajun-0.1.0/src/maajun/agent/tools/vcs_status.py +62 -0
  21. maajun-0.1.0/src/maajun/auth.py +171 -0
  22. maajun-0.1.0/src/maajun/cli/__init__.py +17 -0
  23. maajun-0.1.0/src/maajun/cli/__main__.py +4 -0
  24. maajun-0.1.0/src/maajun/cli/_shared.py +118 -0
  25. maajun-0.1.0/src/maajun/cli/credentials.py +78 -0
  26. maajun-0.1.0/src/maajun/cli/incidents.py +140 -0
  27. maajun-0.1.0/src/maajun/cli/monitor.py +342 -0
  28. maajun-0.1.0/src/maajun/cli/settings.py +161 -0
  29. maajun-0.1.0/src/maajun/cli/setup.py +536 -0
  30. maajun-0.1.0/src/maajun/cli/status_checks.py +156 -0
  31. maajun-0.1.0/src/maajun/config.py +516 -0
  32. maajun-0.1.0/src/maajun/daemon/__init__.py +20 -0
  33. maajun-0.1.0/src/maajun/daemon/core.py +556 -0
  34. maajun-0.1.0/src/maajun/daemon/prompts.py +73 -0
  35. maajun-0.1.0/src/maajun/daemon/reports.py +125 -0
  36. maajun-0.1.0/src/maajun/daemon/store.py +188 -0
  37. maajun-0.1.0/src/maajun/daemon/wiring.py +179 -0
  38. maajun-0.1.0/src/maajun/monitors/__init__.py +12 -0
  39. maajun-0.1.0/src/maajun/monitors/base.py +174 -0
  40. maajun-0.1.0/src/maajun/monitors/defaults.py +30 -0
  41. maajun-0.1.0/src/maajun/monitors/github_actions.py +85 -0
  42. maajun-0.1.0/src/maajun/monitors/logfile.py +273 -0
  43. maajun-0.1.0/src/maajun/progress.py +51 -0
  44. maajun-0.1.0/src/maajun/providers/__init__.py +0 -0
  45. maajun-0.1.0/src/maajun/providers/base.py +100 -0
  46. maajun-0.1.0/src/maajun/providers/chat_completions.py +294 -0
  47. maajun-0.1.0/src/maajun/providers/deepseek.py +23 -0
  48. maajun-0.1.0/src/maajun/providers/factory.py +25 -0
  49. maajun-0.1.0/src/maajun/providers/openai.py +20 -0
  50. maajun-0.1.0/src/maajun/providers/pricing.py +77 -0
  51. maajun-0.1.0/src/maajun/utils/__init__.py +19 -0
  52. maajun-0.1.0/src/maajun/utils/dates.py +18 -0
  53. maajun-0.1.0/src/maajun/utils/repos.py +10 -0
  54. maajun-0.1.0/src/maajun/utils/text.py +9 -0
  55. maajun-0.1.0/src/maajun/vcs/__init__.py +10 -0
  56. maajun-0.1.0/src/maajun/vcs/api.py +14 -0
  57. maajun-0.1.0/src/maajun/vcs/git.py +175 -0
  58. maajun-0.1.0/src/maajun/vcs/github.py +132 -0
  59. maajun-0.1.0/tests/agent/__init__.py +0 -0
  60. maajun-0.1.0/tests/agent/test_core.py +99 -0
  61. maajun-0.1.0/tests/agent/test_tool_loop.py +501 -0
  62. maajun-0.1.0/tests/agent/test_tools.py +320 -0
  63. maajun-0.1.0/tests/cli/__init__.py +0 -0
  64. maajun-0.1.0/tests/cli/test_commands.py +296 -0
  65. maajun-0.1.0/tests/cli/test_incidents.py +134 -0
  66. maajun-0.1.0/tests/cli/test_setup.py +273 -0
  67. maajun-0.1.0/tests/cli/test_status_checks.py +170 -0
  68. maajun-0.1.0/tests/conftest.py +109 -0
  69. maajun-0.1.0/tests/daemon/__init__.py +0 -0
  70. maajun-0.1.0/tests/daemon/test_core.py +891 -0
  71. maajun-0.1.0/tests/daemon/test_store.py +222 -0
  72. maajun-0.1.0/tests/daemon/test_wiring.py +40 -0
  73. maajun-0.1.0/tests/monitors/__init__.py +0 -0
  74. maajun-0.1.0/tests/monitors/test_github_actions.py +175 -0
  75. maajun-0.1.0/tests/monitors/test_logfile.py +410 -0
  76. maajun-0.1.0/tests/test_auth.py +140 -0
  77. maajun-0.1.0/tests/test_config.py +304 -0
  78. maajun-0.1.0/tests/test_progress.py +42 -0
  79. maajun-0.1.0/tests/test_providers_deepseek.py +300 -0
  80. maajun-0.1.0/tests/test_providers_factory.py +60 -0
  81. maajun-0.1.0/tests/test_providers_pricing.py +124 -0
  82. maajun-0.1.0/tests/test_utils.py +59 -0
  83. maajun-0.1.0/tests/vcs/__init__.py +0 -0
  84. maajun-0.1.0/tests/vcs/test_client.py +108 -0
  85. maajun-0.1.0/tests/vcs/test_workspace.py +60 -0
  86. maajun-0.1.0/uv.lock +1007 -0
maajun-0.1.0/.coverage ADDED
Binary file
@@ -0,0 +1,65 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ci-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ lint:
18
+ name: ruff
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: astral-sh/setup-uv@v5
23
+ with:
24
+ enable-cache: true
25
+ - run: uv sync --frozen
26
+ - run: uv run ruff check
27
+
28
+ test:
29
+ name: pytest (${{ matrix.python-version }})
30
+ runs-on: ubuntu-latest
31
+ strategy:
32
+ fail-fast: false
33
+ matrix:
34
+ python-version: ["3.11", "3.12", "3.13"]
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+ - uses: astral-sh/setup-uv@v5
38
+ with:
39
+ enable-cache: true
40
+ python-version: ${{ matrix.python-version }}
41
+ - run: uv sync --frozen
42
+ # No credentials are configured on CI, so any code path that reaches a
43
+ # provider or GitHub would hang rather than fail loudly: keep it bounded.
44
+ - run: uv run pytest -q --timeout=120
45
+ env:
46
+ # Keyring has no backend on a CI runner; fail fast instead of
47
+ # blocking on a prompt.
48
+ PYTHON_KEYRING_BACKEND: keyring.backends.null.Keyring
49
+
50
+ build:
51
+ name: build wheel
52
+ runs-on: ubuntu-latest
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+ - uses: astral-sh/setup-uv@v5
56
+ with:
57
+ enable-cache: true
58
+ - run: uv build
59
+ # Catches a malformed README or metadata before it reaches PyPI, where
60
+ # a version number can never be reused.
61
+ - run: uvx twine check dist/*
62
+ - uses: actions/upload-artifact@v4
63
+ with:
64
+ name: dist
65
+ path: dist/
@@ -0,0 +1,44 @@
1
+ name: Release
2
+
3
+ # Publishes to PyPI when a version tag is pushed:
4
+ #
5
+ # git tag v0.1.0 && git push origin v0.1.0
6
+ #
7
+ # Uses PyPI Trusted Publishing (OIDC) — no API token is stored in the repo.
8
+ # One-time setup: on https://pypi.org/manage/account/publishing/ add a pending
9
+ # publisher for project `maajun`, owner `Morvin-Ian`, repo `maajun`, workflow
10
+ # `release.yml`, environment `pypi`.
11
+
12
+ on:
13
+ push:
14
+ tags: ["v*"]
15
+ workflow_dispatch:
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ build:
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: astral-sh/setup-uv@v5
26
+ - run: uv build
27
+ - run: uvx twine check dist/*
28
+ - uses: actions/upload-artifact@v4
29
+ with:
30
+ name: dist
31
+ path: dist/
32
+
33
+ publish:
34
+ needs: build
35
+ runs-on: ubuntu-latest
36
+ environment: pypi
37
+ permissions:
38
+ id-token: write # required for trusted publishing
39
+ steps:
40
+ - uses: actions/download-artifact@v4
41
+ with:
42
+ name: dist
43
+ path: dist/
44
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,29 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+
9
+ # Virtual environments
10
+ .venv/
11
+ venv/
12
+
13
+ # Tooling caches
14
+ .pytest_cache/
15
+ .ruff_cache/
16
+ .mypy_cache/
17
+
18
+ # Editors / OS
19
+ .vscode/
20
+ .idea/
21
+ .DS_Store
22
+
23
+ # maajun's own runtime state, if it is ever run from inside the checkout.
24
+ # config.toml is what `maajun setup` writes; incidents.db holds the tracebacks
25
+ # and log excerpts it has collected. Neither belongs in a public repo.
26
+ config.toml
27
+ *.db
28
+ .env
29
+ .env.*
@@ -0,0 +1 @@
1
+ 3.11
maajun-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ian Morvin
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.
maajun-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: maajun
3
+ Version: 0.1.0
4
+ Summary: Error monitoring that files the bug report — and can write the fix.
5
+ Project-URL: Homepage, https://github.com/Morvin-Ian/maajun
6
+ Project-URL: Repository, https://github.com/Morvin-Ian/maajun
7
+ Project-URL: Documentation, https://github.com/Morvin-Ian/maajun/blob/main/docs/monitoring.md
8
+ Project-URL: Issues, https://github.com/Morvin-Ian/maajun/issues
9
+ Author-email: Ian Morvin <morvinian@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent,ai,devops,error-monitoring,github,incident-response,llm,observability
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: System Administrators
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Bug Tracking
23
+ Classifier: Topic :: System :: Monitoring
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: httpx>=0.27
26
+ Requires-Dist: keyring>=25.0.0
27
+ Requires-Dist: openai>=1.0.0
28
+ Requires-Dist: prompt-toolkit>=3.0
29
+ Requires-Dist: pydantic>=2.13.4
30
+ Requires-Dist: rich>=13.0.0
31
+ Requires-Dist: tomlkit>=0.15.1
32
+ Requires-Dist: typer>=0.12.0
33
+ Description-Content-Type: text/markdown
34
+
35
+ # Maajun
36
+
37
+ [![CI](https://github.com/Morvin-Ian/maajun/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Morvin-Ian/maajun/actions/workflows/ci.yml?query=branch%3Amain)
38
+ [![PyPI](https://img.shields.io/pypi/v/maajun.svg)](https://pypi.org/project/maajun/)
39
+ [![Python](https://img.shields.io/pypi/pyversions/maajun.svg)](https://pypi.org/project/maajun/)
40
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/Morvin-Ian/maajun/blob/main/LICENSE)
41
+
42
+ **Error monitoring that files the bug report — and can write the fix.**
43
+
44
+ Maajun watches your error sources — local log files (e.g. your app's error
45
+ log on a VPS) and failed GitHub Actions runs — and when a new error
46
+ appears it investigates the code and documents the incident on GitHub:
47
+ in *suggest* mode it files an issue with the analysis and a suggested fix;
48
+ in *fix* mode it applies the fix on a branch, runs your test suite against
49
+ it, and opens a pull request with the result. It records what each analysis
50
+ cost, caps what it may spend per day, and never reports the same error twice.
51
+
52
+ ```
53
+ error detected ──▶ fingerprint & dedup ──▶ AI reads your code
54
+ (logs / CI) │
55
+ issue (suggest) ◀─────────────────────────────── ┤
56
+ PR (fix) ◀──── branch + applied fix ───────┘
57
+ ```
58
+
59
+ ## What it actually files
60
+
61
+ An unhandled `KeyError` hits your error log at 3 a.m. Nobody is awake. By
62
+ morning there is an issue on the repo — this is the shape of it (`suggest`
63
+ mode, the default):
64
+
65
+ > ### \[maajun] KeyError: 'discount'
66
+ >
67
+ > # `KeyError: 'discount'` when a cart has no promotion applied
68
+ >
69
+ > ## What happened
70
+ >
71
+ > Checkout raised an unhandled `KeyError` for carts created before a
72
+ > promotion was attached. 41 requests hit it in 12 minutes; every one
73
+ > returned a 500 to the customer at the payment step.
74
+ >
75
+ > ## Root cause
76
+ >
77
+ > `cart/totals.py:88` reads `cart["discount"]` directly. The key is only
78
+ > written by `promotions.apply()` (`cart/promotions.py:23`), which is
79
+ > skipped entirely when no promotion matches — so the key is absent rather
80
+ > than zero. The `.get()` used one line above at `totals.py:87` is what
81
+ > makes the omission easy to miss on review.
82
+ >
83
+ > ## Likely cause commit
84
+ >
85
+ > `4f1c9ab` — *"only apply promotions when one matches"*. It added the
86
+ > early return in `promotions.apply()` that leaves `discount` unset; every
87
+ > caller before it could assume the key existed.
88
+ >
89
+ > ## Suggested fix
90
+ >
91
+ > Default the lookup, so an absent promotion means no discount:
92
+ >
93
+ > ```python
94
+ > - discount = cart["discount"]
95
+ > + discount = cart.get("discount", Decimal("0"))
96
+ > ```
97
+ >
98
+ > ---
99
+ >
100
+ > ## Error details
101
+ >
102
+ > ```
103
+ > Traceback (most recent call last):
104
+ > File "/srv/shop/checkout/views.py", line 142, in post
105
+ > total = compute_total(cart)
106
+ > File "/srv/shop/cart/totals.py", line 88, in compute_total
107
+ > discount = cart["discount"]
108
+ > KeyError: 'discount'
109
+ > ```
110
+ >
111
+ > - Source: `logfile:/var/log/shop/error.log`
112
+ > - First seen: 2026-08-03T03:14:22Z
113
+ > - Fingerprint: `9f3c1ab77e02d418`
114
+ > - Opened automatically by [maajun](https://github.com/Morvin-Ian/maajun).
115
+
116
+ In `fix` mode the same analysis arrives as a **pull request** instead: the
117
+ applied diff, the report committed as `docs/incidents/<fingerprint>.md`, and
118
+ your own test suite's verdict at the top of the body —
119
+
120
+ > ✅ **Tests pass** — `pytest -q`
121
+ > <details><summary>Output</summary>…</details>
122
+
123
+ — or `❌ **Tests fail** (exit 1)`, which still opens the PR, because
124
+ "this fix breaks the suite" is exactly what a reviewer needs to know.
125
+
126
+ **Nothing merges without your review**, in either mode.
127
+
128
+ ## Install
129
+
130
+ ```bash
131
+ uv tool install maajun # or: pipx install maajun / pip install maajun
132
+ ```
133
+
134
+ From source:
135
+
136
+ ```bash
137
+ git clone https://github.com/Morvin-Ian/maajun && cd maajun && uv sync
138
+ ```
139
+
140
+ ## Quick start
141
+
142
+ Investigate something right now, without setting up any monitoring:
143
+
144
+ ```bash
145
+ maajun setup # stores your API key
146
+ maajun report "Checkout 500s when the cart is empty"
147
+ ```
148
+
149
+ That clones your repo, reads the code, and files an issue with a root-cause
150
+ report (`-m fix` opens a PR with the fix applied instead; `--dry-run`
151
+ just prints the analysis). Get a DeepSeek API key at
152
+ [platform.deepseek.com](https://platform.deepseek.com).
153
+
154
+ ## Quick start: continuous monitoring
155
+
156
+ One command sets up everything:
157
+
158
+ ```bash
159
+ maajun setup # API key, GitHub, log files, GitHub Actions
160
+ maajun watch --dry-run # analyze errors without opening PRs — test your config
161
+ maajun watch # keep monitoring
162
+ ```
163
+
164
+ Only the API key is required. `setup` offers GitHub, log files, and GitHub
165
+ Actions in turn, and each is skippable with Enter — so a minimal install
166
+ is a key and a log path. It detects your repo from the git remote and
167
+ picks up an existing `GITHUB_TOKEN` or `gh auth login` session, and it
168
+ re-runs safely: every answer defaults to what you already have.
169
+
170
+ Without a GitHub repo, maajun still detects and analyzes errors — the
171
+ incident report is written under `daemon.workdir` instead of opening a
172
+ pull request. Add a repo whenever you want PRs:
173
+
174
+ ```bash
175
+ maajun setup --repo you/yourapp # or re-run 'maajun setup' interactively
176
+ ```
177
+
178
+ For CI, every prompt has a flag, and secrets come from the environment so
179
+ they never reach shell history:
180
+
181
+ ```bash
182
+ DEEPSEEK_API_KEY=... GITHUB_TOKEN=... \
183
+ maajun setup --non-interactive --repo you/yourapp --logs /var/log/app/error.log
184
+ ```
185
+
186
+ Tweak any setting later with `maajun config <key> <value>`, watch more
187
+ than one repo with `maajun add-repo <owner/name>`, re-check your wiring
188
+ with `maajun status`, and review what it did with `maajun incidents`.
189
+
190
+ The same error is never reported twice — repeat sightings only bump a counter.
191
+
192
+ ## What it costs, and what it can do
193
+
194
+ Both questions people reasonably ask before pointing an AI daemon at their
195
+ repo with their API key in it:
196
+
197
+ **Spend is bounded by default.** `daemon.max_usd_per_day` starts at **$5**:
198
+ past that, maajun stops analyzing for the rest of the UTC day, warns once,
199
+ and keeps polling — skipped errors are picked up later, not dropped. Raise
200
+ it with `maajun config daemon.max_usd_per_day 20`, or set `0` for no cap.
201
+ Every incident's exact token count and cost is recorded and shown by
202
+ `maajun incidents`; `--dry-run` prints what an analysis *would* have cost
203
+ before you commit to anything. At DeepSeek's published rate ($0.27/$1.10 per
204
+ 1M input/output tokens) an analysis is cents, not dollars — but measure your
205
+ own with `--dry-run` rather than trusting an estimate.
206
+
207
+ **The agent is deliberately small.** It has no shell access in any mode —
208
+ there is no bash tool to grant. In `suggest` mode it is strictly read-only;
209
+ in `fix` mode it may edit files *only* inside its own clone under
210
+ `daemon.workdir`, never your running application. Your `test_command` comes
211
+ from your config, not from the model, so verification can't be redirected.
212
+ The GitHub token is passed to git via `GIT_ASKPASS` and never lands in a
213
+ remote URL, `.git/config`, or the process list.
214
+
215
+ ## Documentation
216
+
217
+ - [How it works](https://github.com/Morvin-Ian/maajun/blob/main/docs/architecture.md)
218
+ — components, monitors, and the incident pipeline
219
+ - [Monitoring guide](https://github.com/Morvin-Ian/maajun/blob/main/docs/monitoring.md)
220
+ — config reference, error sources (logs, GitHub Actions), cost tracking,
221
+ running on a VPS
222
+ - [Command reference](https://github.com/Morvin-Ian/maajun/blob/main/docs/commands.md)
223
+ — every CLI command and flag
224
+
225
+ ## Supported AI providers
226
+
227
+ DeepSeek and OpenAI. Both speak the same wire protocol, so any compatible
228
+ gateway works too — point `ai.base_url` at it. Pick one during
229
+ `maajun setup`, or switch later with `maajun config ai.provider openai`.
230
+
231
+ ## Development
232
+
233
+ ```bash
234
+ uv sync # installs dev dependencies (pytest, ruff)
235
+ uv run pytest # run tests
236
+ uv run ruff check # lint
237
+ ```
238
+
239
+ ## License
240
+
241
+ [MIT](https://github.com/Morvin-Ian/maajun/blob/main/LICENSE).
maajun-0.1.0/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # Maajun
2
+
3
+ [![CI](https://github.com/Morvin-Ian/maajun/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Morvin-Ian/maajun/actions/workflows/ci.yml?query=branch%3Amain)
4
+ [![PyPI](https://img.shields.io/pypi/v/maajun.svg)](https://pypi.org/project/maajun/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/maajun.svg)](https://pypi.org/project/maajun/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/Morvin-Ian/maajun/blob/main/LICENSE)
7
+
8
+ **Error monitoring that files the bug report — and can write the fix.**
9
+
10
+ Maajun watches your error sources — local log files (e.g. your app's error
11
+ log on a VPS) and failed GitHub Actions runs — and when a new error
12
+ appears it investigates the code and documents the incident on GitHub:
13
+ in *suggest* mode it files an issue with the analysis and a suggested fix;
14
+ in *fix* mode it applies the fix on a branch, runs your test suite against
15
+ it, and opens a pull request with the result. It records what each analysis
16
+ cost, caps what it may spend per day, and never reports the same error twice.
17
+
18
+ ```
19
+ error detected ──▶ fingerprint & dedup ──▶ AI reads your code
20
+ (logs / CI) │
21
+ issue (suggest) ◀─────────────────────────────── ┤
22
+ PR (fix) ◀──── branch + applied fix ───────┘
23
+ ```
24
+
25
+ ## What it actually files
26
+
27
+ An unhandled `KeyError` hits your error log at 3 a.m. Nobody is awake. By
28
+ morning there is an issue on the repo — this is the shape of it (`suggest`
29
+ mode, the default):
30
+
31
+ > ### \[maajun] KeyError: 'discount'
32
+ >
33
+ > # `KeyError: 'discount'` when a cart has no promotion applied
34
+ >
35
+ > ## What happened
36
+ >
37
+ > Checkout raised an unhandled `KeyError` for carts created before a
38
+ > promotion was attached. 41 requests hit it in 12 minutes; every one
39
+ > returned a 500 to the customer at the payment step.
40
+ >
41
+ > ## Root cause
42
+ >
43
+ > `cart/totals.py:88` reads `cart["discount"]` directly. The key is only
44
+ > written by `promotions.apply()` (`cart/promotions.py:23`), which is
45
+ > skipped entirely when no promotion matches — so the key is absent rather
46
+ > than zero. The `.get()` used one line above at `totals.py:87` is what
47
+ > makes the omission easy to miss on review.
48
+ >
49
+ > ## Likely cause commit
50
+ >
51
+ > `4f1c9ab` — *"only apply promotions when one matches"*. It added the
52
+ > early return in `promotions.apply()` that leaves `discount` unset; every
53
+ > caller before it could assume the key existed.
54
+ >
55
+ > ## Suggested fix
56
+ >
57
+ > Default the lookup, so an absent promotion means no discount:
58
+ >
59
+ > ```python
60
+ > - discount = cart["discount"]
61
+ > + discount = cart.get("discount", Decimal("0"))
62
+ > ```
63
+ >
64
+ > ---
65
+ >
66
+ > ## Error details
67
+ >
68
+ > ```
69
+ > Traceback (most recent call last):
70
+ > File "/srv/shop/checkout/views.py", line 142, in post
71
+ > total = compute_total(cart)
72
+ > File "/srv/shop/cart/totals.py", line 88, in compute_total
73
+ > discount = cart["discount"]
74
+ > KeyError: 'discount'
75
+ > ```
76
+ >
77
+ > - Source: `logfile:/var/log/shop/error.log`
78
+ > - First seen: 2026-08-03T03:14:22Z
79
+ > - Fingerprint: `9f3c1ab77e02d418`
80
+ > - Opened automatically by [maajun](https://github.com/Morvin-Ian/maajun).
81
+
82
+ In `fix` mode the same analysis arrives as a **pull request** instead: the
83
+ applied diff, the report committed as `docs/incidents/<fingerprint>.md`, and
84
+ your own test suite's verdict at the top of the body —
85
+
86
+ > ✅ **Tests pass** — `pytest -q`
87
+ > <details><summary>Output</summary>…</details>
88
+
89
+ — or `❌ **Tests fail** (exit 1)`, which still opens the PR, because
90
+ "this fix breaks the suite" is exactly what a reviewer needs to know.
91
+
92
+ **Nothing merges without your review**, in either mode.
93
+
94
+ ## Install
95
+
96
+ ```bash
97
+ uv tool install maajun # or: pipx install maajun / pip install maajun
98
+ ```
99
+
100
+ From source:
101
+
102
+ ```bash
103
+ git clone https://github.com/Morvin-Ian/maajun && cd maajun && uv sync
104
+ ```
105
+
106
+ ## Quick start
107
+
108
+ Investigate something right now, without setting up any monitoring:
109
+
110
+ ```bash
111
+ maajun setup # stores your API key
112
+ maajun report "Checkout 500s when the cart is empty"
113
+ ```
114
+
115
+ That clones your repo, reads the code, and files an issue with a root-cause
116
+ report (`-m fix` opens a PR with the fix applied instead; `--dry-run`
117
+ just prints the analysis). Get a DeepSeek API key at
118
+ [platform.deepseek.com](https://platform.deepseek.com).
119
+
120
+ ## Quick start: continuous monitoring
121
+
122
+ One command sets up everything:
123
+
124
+ ```bash
125
+ maajun setup # API key, GitHub, log files, GitHub Actions
126
+ maajun watch --dry-run # analyze errors without opening PRs — test your config
127
+ maajun watch # keep monitoring
128
+ ```
129
+
130
+ Only the API key is required. `setup` offers GitHub, log files, and GitHub
131
+ Actions in turn, and each is skippable with Enter — so a minimal install
132
+ is a key and a log path. It detects your repo from the git remote and
133
+ picks up an existing `GITHUB_TOKEN` or `gh auth login` session, and it
134
+ re-runs safely: every answer defaults to what you already have.
135
+
136
+ Without a GitHub repo, maajun still detects and analyzes errors — the
137
+ incident report is written under `daemon.workdir` instead of opening a
138
+ pull request. Add a repo whenever you want PRs:
139
+
140
+ ```bash
141
+ maajun setup --repo you/yourapp # or re-run 'maajun setup' interactively
142
+ ```
143
+
144
+ For CI, every prompt has a flag, and secrets come from the environment so
145
+ they never reach shell history:
146
+
147
+ ```bash
148
+ DEEPSEEK_API_KEY=... GITHUB_TOKEN=... \
149
+ maajun setup --non-interactive --repo you/yourapp --logs /var/log/app/error.log
150
+ ```
151
+
152
+ Tweak any setting later with `maajun config <key> <value>`, watch more
153
+ than one repo with `maajun add-repo <owner/name>`, re-check your wiring
154
+ with `maajun status`, and review what it did with `maajun incidents`.
155
+
156
+ The same error is never reported twice — repeat sightings only bump a counter.
157
+
158
+ ## What it costs, and what it can do
159
+
160
+ Both questions people reasonably ask before pointing an AI daemon at their
161
+ repo with their API key in it:
162
+
163
+ **Spend is bounded by default.** `daemon.max_usd_per_day` starts at **$5**:
164
+ past that, maajun stops analyzing for the rest of the UTC day, warns once,
165
+ and keeps polling — skipped errors are picked up later, not dropped. Raise
166
+ it with `maajun config daemon.max_usd_per_day 20`, or set `0` for no cap.
167
+ Every incident's exact token count and cost is recorded and shown by
168
+ `maajun incidents`; `--dry-run` prints what an analysis *would* have cost
169
+ before you commit to anything. At DeepSeek's published rate ($0.27/$1.10 per
170
+ 1M input/output tokens) an analysis is cents, not dollars — but measure your
171
+ own with `--dry-run` rather than trusting an estimate.
172
+
173
+ **The agent is deliberately small.** It has no shell access in any mode —
174
+ there is no bash tool to grant. In `suggest` mode it is strictly read-only;
175
+ in `fix` mode it may edit files *only* inside its own clone under
176
+ `daemon.workdir`, never your running application. Your `test_command` comes
177
+ from your config, not from the model, so verification can't be redirected.
178
+ The GitHub token is passed to git via `GIT_ASKPASS` and never lands in a
179
+ remote URL, `.git/config`, or the process list.
180
+
181
+ ## Documentation
182
+
183
+ - [How it works](https://github.com/Morvin-Ian/maajun/blob/main/docs/architecture.md)
184
+ — components, monitors, and the incident pipeline
185
+ - [Monitoring guide](https://github.com/Morvin-Ian/maajun/blob/main/docs/monitoring.md)
186
+ — config reference, error sources (logs, GitHub Actions), cost tracking,
187
+ running on a VPS
188
+ - [Command reference](https://github.com/Morvin-Ian/maajun/blob/main/docs/commands.md)
189
+ — every CLI command and flag
190
+
191
+ ## Supported AI providers
192
+
193
+ DeepSeek and OpenAI. Both speak the same wire protocol, so any compatible
194
+ gateway works too — point `ai.base_url` at it. Pick one during
195
+ `maajun setup`, or switch later with `maajun config ai.provider openai`.
196
+
197
+ ## Development
198
+
199
+ ```bash
200
+ uv sync # installs dev dependencies (pytest, ruff)
201
+ uv run pytest # run tests
202
+ uv run ruff check # lint
203
+ ```
204
+
205
+ ## License
206
+
207
+ [MIT](https://github.com/Morvin-Ian/maajun/blob/main/LICENSE).