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.
- maajun-0.1.0/.coverage +0 -0
- maajun-0.1.0/.github/workflows/ci.yml +65 -0
- maajun-0.1.0/.github/workflows/release.yml +44 -0
- maajun-0.1.0/.gitignore +29 -0
- maajun-0.1.0/.python-version +1 -0
- maajun-0.1.0/LICENSE +21 -0
- maajun-0.1.0/PKG-INFO +241 -0
- maajun-0.1.0/README.md +207 -0
- maajun-0.1.0/docs/architecture.md +201 -0
- maajun-0.1.0/docs/commands.md +187 -0
- maajun-0.1.0/docs/monitoring.md +472 -0
- maajun-0.1.0/pyproject.toml +89 -0
- maajun-0.1.0/src/maajun/__init__.py +0 -0
- maajun-0.1.0/src/maajun/agent/__init__.py +3 -0
- maajun-0.1.0/src/maajun/agent/core.py +324 -0
- maajun-0.1.0/src/maajun/agent/tools/__init__.py +36 -0
- maajun-0.1.0/src/maajun/agent/tools/base.py +64 -0
- maajun-0.1.0/src/maajun/agent/tools/files.py +133 -0
- maajun-0.1.0/src/maajun/agent/tools/search.py +201 -0
- maajun-0.1.0/src/maajun/agent/tools/vcs_status.py +62 -0
- maajun-0.1.0/src/maajun/auth.py +171 -0
- maajun-0.1.0/src/maajun/cli/__init__.py +17 -0
- maajun-0.1.0/src/maajun/cli/__main__.py +4 -0
- maajun-0.1.0/src/maajun/cli/_shared.py +118 -0
- maajun-0.1.0/src/maajun/cli/credentials.py +78 -0
- maajun-0.1.0/src/maajun/cli/incidents.py +140 -0
- maajun-0.1.0/src/maajun/cli/monitor.py +342 -0
- maajun-0.1.0/src/maajun/cli/settings.py +161 -0
- maajun-0.1.0/src/maajun/cli/setup.py +536 -0
- maajun-0.1.0/src/maajun/cli/status_checks.py +156 -0
- maajun-0.1.0/src/maajun/config.py +516 -0
- maajun-0.1.0/src/maajun/daemon/__init__.py +20 -0
- maajun-0.1.0/src/maajun/daemon/core.py +556 -0
- maajun-0.1.0/src/maajun/daemon/prompts.py +73 -0
- maajun-0.1.0/src/maajun/daemon/reports.py +125 -0
- maajun-0.1.0/src/maajun/daemon/store.py +188 -0
- maajun-0.1.0/src/maajun/daemon/wiring.py +179 -0
- maajun-0.1.0/src/maajun/monitors/__init__.py +12 -0
- maajun-0.1.0/src/maajun/monitors/base.py +174 -0
- maajun-0.1.0/src/maajun/monitors/defaults.py +30 -0
- maajun-0.1.0/src/maajun/monitors/github_actions.py +85 -0
- maajun-0.1.0/src/maajun/monitors/logfile.py +273 -0
- maajun-0.1.0/src/maajun/progress.py +51 -0
- maajun-0.1.0/src/maajun/providers/__init__.py +0 -0
- maajun-0.1.0/src/maajun/providers/base.py +100 -0
- maajun-0.1.0/src/maajun/providers/chat_completions.py +294 -0
- maajun-0.1.0/src/maajun/providers/deepseek.py +23 -0
- maajun-0.1.0/src/maajun/providers/factory.py +25 -0
- maajun-0.1.0/src/maajun/providers/openai.py +20 -0
- maajun-0.1.0/src/maajun/providers/pricing.py +77 -0
- maajun-0.1.0/src/maajun/utils/__init__.py +19 -0
- maajun-0.1.0/src/maajun/utils/dates.py +18 -0
- maajun-0.1.0/src/maajun/utils/repos.py +10 -0
- maajun-0.1.0/src/maajun/utils/text.py +9 -0
- maajun-0.1.0/src/maajun/vcs/__init__.py +10 -0
- maajun-0.1.0/src/maajun/vcs/api.py +14 -0
- maajun-0.1.0/src/maajun/vcs/git.py +175 -0
- maajun-0.1.0/src/maajun/vcs/github.py +132 -0
- maajun-0.1.0/tests/agent/__init__.py +0 -0
- maajun-0.1.0/tests/agent/test_core.py +99 -0
- maajun-0.1.0/tests/agent/test_tool_loop.py +501 -0
- maajun-0.1.0/tests/agent/test_tools.py +320 -0
- maajun-0.1.0/tests/cli/__init__.py +0 -0
- maajun-0.1.0/tests/cli/test_commands.py +296 -0
- maajun-0.1.0/tests/cli/test_incidents.py +134 -0
- maajun-0.1.0/tests/cli/test_setup.py +273 -0
- maajun-0.1.0/tests/cli/test_status_checks.py +170 -0
- maajun-0.1.0/tests/conftest.py +109 -0
- maajun-0.1.0/tests/daemon/__init__.py +0 -0
- maajun-0.1.0/tests/daemon/test_core.py +891 -0
- maajun-0.1.0/tests/daemon/test_store.py +222 -0
- maajun-0.1.0/tests/daemon/test_wiring.py +40 -0
- maajun-0.1.0/tests/monitors/__init__.py +0 -0
- maajun-0.1.0/tests/monitors/test_github_actions.py +175 -0
- maajun-0.1.0/tests/monitors/test_logfile.py +410 -0
- maajun-0.1.0/tests/test_auth.py +140 -0
- maajun-0.1.0/tests/test_config.py +304 -0
- maajun-0.1.0/tests/test_progress.py +42 -0
- maajun-0.1.0/tests/test_providers_deepseek.py +300 -0
- maajun-0.1.0/tests/test_providers_factory.py +60 -0
- maajun-0.1.0/tests/test_providers_pricing.py +124 -0
- maajun-0.1.0/tests/test_utils.py +59 -0
- maajun-0.1.0/tests/vcs/__init__.py +0 -0
- maajun-0.1.0/tests/vcs/test_client.py +108 -0
- maajun-0.1.0/tests/vcs/test_workspace.py +60 -0
- 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
|
maajun-0.1.0/.gitignore
ADDED
|
@@ -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
|
+
[](https://github.com/Morvin-Ian/maajun/actions/workflows/ci.yml?query=branch%3Amain)
|
|
38
|
+
[](https://pypi.org/project/maajun/)
|
|
39
|
+
[](https://pypi.org/project/maajun/)
|
|
40
|
+
[](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
|
+
[](https://github.com/Morvin-Ian/maajun/actions/workflows/ci.yml?query=branch%3Amain)
|
|
4
|
+
[](https://pypi.org/project/maajun/)
|
|
5
|
+
[](https://pypi.org/project/maajun/)
|
|
6
|
+
[](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).
|