bug2context 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.
- bug2context-0.1.0/.github/workflows/ci.yml +50 -0
- bug2context-0.1.0/.github/workflows/release.yml +27 -0
- bug2context-0.1.0/.gitignore +8 -0
- bug2context-0.1.0/LICENSE +21 -0
- bug2context-0.1.0/PKG-INFO +232 -0
- bug2context-0.1.0/PLAN-bug2context.md +200 -0
- bug2context-0.1.0/README.md +202 -0
- bug2context-0.1.0/RELEASE.md +111 -0
- bug2context-0.1.0/examples/report.md +26 -0
- bug2context-0.1.0/pyproject.toml +53 -0
- bug2context-0.1.0/src/bug2context/__init__.py +1 -0
- bug2context-0.1.0/src/bug2context/android.py +179 -0
- bug2context-0.1.0/src/bug2context/cli.py +90 -0
- bug2context-0.1.0/src/bug2context/config.py +93 -0
- bug2context-0.1.0/src/bug2context/mcp_server.py +119 -0
- bug2context-0.1.0/src/bug2context/menu.py +160 -0
- bug2context-0.1.0/src/bug2context/pipeline/__init__.py +0 -0
- bug2context-0.1.0/src/bug2context/pipeline/assemble.py +216 -0
- bug2context-0.1.0/src/bug2context/pipeline/audio.py +94 -0
- bug2context-0.1.0/src/bug2context/pipeline/frames.py +185 -0
- bug2context-0.1.0/src/bug2context/pipeline/logs.py +187 -0
- bug2context-0.1.0/src/bug2context/pipeline/ocr.py +207 -0
- bug2context-0.1.0/tests/__init__.py +0 -0
- bug2context-0.1.0/tests/conftest.py +116 -0
- bug2context-0.1.0/tests/test_android.py +159 -0
- bug2context-0.1.0/tests/test_assemble.py +162 -0
- bug2context-0.1.0/tests/test_audio.py +106 -0
- bug2context-0.1.0/tests/test_frames.py +195 -0
- bug2context-0.1.0/tests/test_logs.py +315 -0
- bug2context-0.1.0/tests/test_mcp_server.py +95 -0
- bug2context-0.1.0/tests/test_menu.py +135 -0
- bug2context-0.1.0/tests/test_ocr.py +295 -0
- bug2context-0.1.0/uv.lock +1463 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
# ubuntu proves the tesseract/no-Vision path; macos is the dev platform.
|
|
14
|
+
os: [ubuntu-latest, macos-latest]
|
|
15
|
+
python: ["3.12", "3.13"]
|
|
16
|
+
runs-on: ${{ matrix.os }}
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Install ffmpeg (Linux)
|
|
21
|
+
if: runner.os == 'Linux'
|
|
22
|
+
run: sudo apt-get update && sudo apt-get install -y ffmpeg
|
|
23
|
+
|
|
24
|
+
- name: Install ffmpeg (macOS)
|
|
25
|
+
if: runner.os == 'macOS'
|
|
26
|
+
run: brew install ffmpeg
|
|
27
|
+
|
|
28
|
+
- uses: astral-sh/setup-uv@v5
|
|
29
|
+
with:
|
|
30
|
+
enable-cache: true
|
|
31
|
+
|
|
32
|
+
- run: uv sync --python ${{ matrix.python }} --extra ocr
|
|
33
|
+
|
|
34
|
+
- run: uv run pytest -q
|
|
35
|
+
|
|
36
|
+
build:
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
- uses: astral-sh/setup-uv@v5
|
|
41
|
+
- run: uv build
|
|
42
|
+
- name: Install the wheel on its own and run it
|
|
43
|
+
run: |
|
|
44
|
+
uv venv /tmp/clean
|
|
45
|
+
uv pip install --python /tmp/clean/bin/python dist/*.whl
|
|
46
|
+
/tmp/clean/bin/bug2context --help
|
|
47
|
+
- uses: actions/upload-artifact@v4
|
|
48
|
+
with:
|
|
49
|
+
name: dist
|
|
50
|
+
path: dist/
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishing is deliberately manual: push a tag when you actually mean to ship.
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment: pypi
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write # PyPI trusted publishing — no API token to leak
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
|
|
18
|
+
- name: Fail if the tag and pyproject version disagree
|
|
19
|
+
run: |
|
|
20
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
21
|
+
version=$(grep -m1 '^version' pyproject.toml | cut -d'"' -f2)
|
|
22
|
+
test "$tag" = "$version" || {
|
|
23
|
+
echo "tag $tag != pyproject $version"; exit 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
- run: uv build
|
|
27
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Emanuel Duran
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: bug2context
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Turns bug screen recordings into structured, chronological context for AI agents.
|
|
5
|
+
Project-URL: Homepage, https://github.com/emanueld92/bug2context
|
|
6
|
+
Project-URL: Issues, https://github.com/emanueld92/bug2context/issues
|
|
7
|
+
Author: Emanuel Duran
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: bug-report,claude,debugging,llm,mcp,ocr,screen-recording
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Multimedia :: Video
|
|
17
|
+
Classifier: Topic :: Software Development :: Bug Tracking
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: imagehash>=4.3
|
|
21
|
+
Requires-Dist: mcp>=1.2
|
|
22
|
+
Requires-Dist: pillow>=11.0
|
|
23
|
+
Requires-Dist: typer>=0.15
|
|
24
|
+
Provides-Extra: audio
|
|
25
|
+
Requires-Dist: faster-whisper>=1.1; extra == 'audio'
|
|
26
|
+
Provides-Extra: ocr
|
|
27
|
+
Requires-Dist: pyobjc-framework-vision>=10.3; (sys_platform == 'darwin') and extra == 'ocr'
|
|
28
|
+
Requires-Dist: pytesseract>=0.3.13; extra == 'ocr'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# bug2context
|
|
32
|
+
|
|
33
|
+
Turns a screen recording of a bug into structured, chronological context an AI
|
|
34
|
+
agent can actually read.
|
|
35
|
+
|
|
36
|
+
Screenshots lose the story: bugs happen fast, a still frame has no timeline, and
|
|
37
|
+
the logs and the narration live somewhere else entirely. LLMs don't take video.
|
|
38
|
+
This distills a recording into one markdown chronology — key frames, the text on
|
|
39
|
+
screen, device logs and spoken narration, all on the same clock.
|
|
40
|
+
|
|
41
|
+
- **No SDK.** Works on any video, including one someone sent you over WhatsApp.
|
|
42
|
+
- **Local-first.** Nothing leaves your machine. No account, no API key, no cloud.
|
|
43
|
+
- **MCP-native.** Claude Code calls it as a tool; you just point at the file.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv run bug2context # guided menu
|
|
47
|
+
uv run bug2context process bug.mp4 --out bundle/
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Running it with no arguments opens a menu — record an Android device, process a
|
|
51
|
+
video you already have, or list previous bundles — and writes to
|
|
52
|
+
`~/.bug2context/`, which is where the MCP server looks. Everything it does is
|
|
53
|
+
reachable through the flags below; it just stops you having to remember them.
|
|
54
|
+
|
|
55
|
+
## What comes out
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
- `[00:00]` 🖼 `frames/frame_001_00-00.png` — start of recording
|
|
59
|
+
OCR: Cart 2 items
|
|
60
|
+
- `[00:04]` 🖼 `frames/frame_002_00-04.png` — screen changed
|
|
61
|
+
OCR: Applying discount..
|
|
62
|
+
- `[00:04]` 📋 W/DiscountEngine: rate field missing
|
|
63
|
+
- `[00:06]` 🖼 `frames/frame_003_00-06.png` — screen changed
|
|
64
|
+
OCR: TypeError: cannot read 'rate'
|
|
65
|
+
- `[00:06]` 📋 E/RideService: NullPointerException at RideRequest.kt:142
|
|
66
|
+
- `[00:06]` 📋 F/AndroidRuntime: FATAL EXCEPTION: main
|
|
67
|
+
- `[00:06]` 🎙 "ahí está, explota al aplicar el descuento"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
A full run is in [examples/report.md](examples/report.md).
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
bundle/
|
|
74
|
+
├── report.md the chronology above
|
|
75
|
+
├── frames/ the key frames, named by timestamp
|
|
76
|
+
└── meta.json the same data, structured, for programmatic use
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Install
|
|
80
|
+
|
|
81
|
+
Requires [`ffmpeg`](https://ffmpeg.org) on PATH (`brew install ffmpeg`).
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git clone https://github.com/emanueld92/bug2context
|
|
85
|
+
cd bug2context
|
|
86
|
+
uv sync --extra ocr # OCR: Apple Vision on macOS, tesseract elsewhere
|
|
87
|
+
uv sync --extra audio # optional: spoken narration
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Every stage is optional and degrades quietly: no OCR backend still gives you
|
|
91
|
+
frames, no audio track still gives you the visual timeline.
|
|
92
|
+
|
|
93
|
+
## Use it from Claude Code
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
claude mcp add bug2context -- uvx --from /path/to/bug2context bug2context-mcp
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Then just ask: *"analiza este video del bug: ~/Desktop/crash.mp4"*.
|
|
100
|
+
|
|
101
|
+
| Tool | Purpose |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `analyze_bug_video` | Video → chronological report (returns the text itself) |
|
|
104
|
+
| `get_frame` | Fetch one frame as an image so the agent can look at it |
|
|
105
|
+
| `list_bundles` | Previously processed bundles, newest first |
|
|
106
|
+
|
|
107
|
+
Bundles default to `~/.bug2context/<video>-<timestamp>/`.
|
|
108
|
+
|
|
109
|
+
## Android: screen + logcat in one command
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
uv run bug2context record --seconds 60 --package com.example.app --out bundle/
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Reproduce the bug while it records. Both streams start from a single host
|
|
116
|
+
timestamp — that anchor is what makes a stack trace land next to the frame
|
|
117
|
+
showing the crash.
|
|
118
|
+
|
|
119
|
+
**Pass `--package`.** Level filtering alone is not enough on a real phone: a
|
|
120
|
+
2 min capture held 7,634 W/E/F lines and *none* came from the app under test —
|
|
121
|
+
they were all `AppOpsControllerImpl`, `GNSSMGT` and friends. Scoping to the
|
|
122
|
+
app's process left 35. Crash tags (`AndroidRuntime`, `DEBUG`) are kept
|
|
123
|
+
regardless of process, since a crash report is the whole point.
|
|
124
|
+
|
|
125
|
+
Logs are captured unfiltered and scoped afterwards, so an app that crashes and
|
|
126
|
+
restarts still has its death recorded. Consecutive identical entries fold into
|
|
127
|
+
one with a `(×N)` count — framework chatter arrives in bursts of fifteen.
|
|
128
|
+
|
|
129
|
+
Already have a log file? Merge it into any video:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
uv run bug2context process bug.mp4 --logcat logcat.txt --log-tag RideService
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Reads both `-v time` and `-v threadtime`. `--log-levels` defaults to `WEF` and
|
|
136
|
+
`--log-max-lines` to 80, ranked by severity so a cap never trades the crash for
|
|
137
|
+
boot chatter. `--log-pid` scopes to a process when you already know it. If the device clock and the
|
|
138
|
+
recorder disagree, `--log-offset` shifts everything by N seconds; with no
|
|
139
|
+
`--video-started-at`, the first log entry becomes the anchor.
|
|
140
|
+
|
|
141
|
+
`screenrecord` caps at 3 minutes, so `record` refuses longer rather than handing
|
|
142
|
+
back a silently truncated video. Ctrl-C cuts a recording short safely — the file
|
|
143
|
+
is finalised on the device first, because killing the local adb leaves one
|
|
144
|
+
`ffprobe` cannot open.
|
|
145
|
+
|
|
146
|
+
Videos over 10 minutes are refused with the `ffmpeg` command to trim them
|
|
147
|
+
(`--max-duration 0` overrides). Nothing scales badly with length except time,
|
|
148
|
+
but it scales linearly: decoding alone runs at about a third of real time.
|
|
149
|
+
|
|
150
|
+
## Narration
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
uv run bug2context process bug.mp4 --transcribe --language es
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Off by default: it downloads a model on first use and is the slowest stage,
|
|
157
|
+
while most recordings have no voice-over. Skipped automatically when there is no
|
|
158
|
+
audio track, or the track is quieter than −50 dB — whisper invents confident
|
|
159
|
+
sentences out of silence, so that guard is about output quality, not just speed.
|
|
160
|
+
|
|
161
|
+
## How frames get picked
|
|
162
|
+
|
|
163
|
+
ffmpeg over-produces candidates (scene cuts **and** a fixed interval, so slowly
|
|
164
|
+
changing screens are not skipped), then perceptual hashing collapses the
|
|
165
|
+
near-duplicates. `--max-frames` caps the result, always keeping the opening frame
|
|
166
|
+
and then whichever changed most.
|
|
167
|
+
|
|
168
|
+
| Flag | Default | Notes |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `--scene-threshold` | `0.08` | ffmpeg scene score for a cut |
|
|
171
|
+
| `--interval-seconds` | `1.0` | forced sample when no cut fires |
|
|
172
|
+
| `--max-frames` | auto | one frame per 2 s, between 20 and 40 |
|
|
173
|
+
| `--phash-distance` | `4` | below this, frames count as duplicates |
|
|
174
|
+
| `--ocr-upscale` | `2` | enlarge before OCR; skipped above 1600 px wide |
|
|
175
|
+
|
|
176
|
+
Measured end to end, OCR on:
|
|
177
|
+
|
|
178
|
+
| Source | Result | Time |
|
|
179
|
+
|---|---|---|
|
|
180
|
+
| synthetic 3 min, 1080×1920 | 19 frames | 8.5 s |
|
|
181
|
+
| Android capture, 2 min, 720×1612 @ ~12 fps | 118 candidates → 20 frames | 16 s |
|
|
182
|
+
| macOS capture, 90 s, 2880×1864 @ 60 fps | 97 candidates → 18 frames | 86 s |
|
|
183
|
+
|
|
184
|
+
Recognised text is cached by frame content in `~/.cache/bug2context/`, so
|
|
185
|
+
re-running a video with different settings only pays for what actually changed —
|
|
186
|
+
measured 9.0 s cold against 3.3 s warm on a 44 s clip, identical output.
|
|
187
|
+
Extracting the same video twice yields byte-identical frames, which is what
|
|
188
|
+
makes the cache safe. Delete the directory to reset it.
|
|
189
|
+
|
|
190
|
+
Retina desktop recordings are the slow case and there is no trick to remove:
|
|
191
|
+
the cost is decoding 60 fps at 5.4 megapixels (26 s) plus OCR, which Vision
|
|
192
|
+
charges at ~1.6 s per frame regardless of size. Budget roughly real time for
|
|
193
|
+
those; phone captures stay far under it.
|
|
194
|
+
|
|
195
|
+
On `--ocr-upscale`: a stack trace on a recompressed 1080×1920 frame read as
|
|
196
|
+
`Null PointerSxception Ride Requestkt14` at 1× and correctly as
|
|
197
|
+
`NullPointerException Ride Request kt 142` at 3×. Raise it for badly
|
|
198
|
+
recompressed sources — ask for the video as a file, not as a WhatsApp video.
|
|
199
|
+
It is skipped on frames already 1600 px or wider, where it changed nothing
|
|
200
|
+
measurable while building a 5760×3728 image per frame.
|
|
201
|
+
|
|
202
|
+
OCR also drops lines that are debris rather than text — `»`, `.lll (100 4`,
|
|
203
|
+
a misread status-bar clock. On a real capture that removed 41 of 210 report
|
|
204
|
+
lines and no real text. A line that looks like a failure is never dropped.
|
|
205
|
+
|
|
206
|
+
## Known limitation
|
|
207
|
+
|
|
208
|
+
Perceptual hashing compares visual *structure*. It sees navigation, dialogs and
|
|
209
|
+
error banners, but it cannot detect a small **text-only** change on an otherwise
|
|
210
|
+
identical layout — measured on 1080×1920 those differences fall inside the noise
|
|
211
|
+
floor at every hash size, so no `--phash-distance` value separates them.
|
|
212
|
+
|
|
213
|
+
OCR does not rescue this: it runs on frames that survive deduplication, so a
|
|
214
|
+
screen already collapsed is never read. Reading every candidate instead measured
|
|
215
|
+
48–59 s for a 3 min video, over the entire time budget. If your bug *is* a small
|
|
216
|
+
text change on an identical screen, record cropped or at lower resolution so the
|
|
217
|
+
text occupies more of the frame.
|
|
218
|
+
|
|
219
|
+
## Development
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
uv sync --extra ocr
|
|
223
|
+
uv run pytest -q
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
183 tests. Device recording is mocked on purpose — a test suite should not record
|
|
227
|
+
anyone's phone. Whisper is opt-in via `BUG2CONTEXT_TEST_WHISPER=1` so the suite
|
|
228
|
+
stays offline.
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# PLAN — bug2context
|
|
2
|
+
|
|
3
|
+
> Herramienta local-first que transforma grabaciones de video de bugs (especialmente de apps móviles) en contexto estructurado y cronológico listo para consumir por motores de IA, entregada como CLI + servidor MCP.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Visión y problema
|
|
8
|
+
|
|
9
|
+
**Problema:** al reportar o depurar bugs, las capturas de pantalla son ineficientes — los errores suceden muy rápido, no reflejan cronología, y el contexto (logs, narración, causa→efecto) se pierde. Los LLMs (Claude incluido) no aceptan video directamente.
|
|
10
|
+
|
|
11
|
+
**Solución:** un pipeline que destila un video en un "paquete de contexto": frames clave con timestamps + texto extraído por OCR + logs del dispositivo sincronizados + transcripción de narración, ensamblados en un reporte markdown cronológico que un agente de IA puede leer directamente.
|
|
12
|
+
|
|
13
|
+
**Diferenciadores vs. mercado existente (Bugsee, Shake, Instabug):**
|
|
14
|
+
- Sin SDK: funciona con cualquier video, incluso uno recibido por WhatsApp.
|
|
15
|
+
- Local-first: sin cloud, sin suscripción, privacidad total.
|
|
16
|
+
- MCP-first: Claude Code lo consume como una tool nativa.
|
|
17
|
+
- Open source (licencia MIT o Apache 2.0).
|
|
18
|
+
|
|
19
|
+
## 2. Principios de diseño
|
|
20
|
+
|
|
21
|
+
1. **Local-first:** todo el procesamiento corre en la máquina del usuario. Ningún dato sale sin acción explícita.
|
|
22
|
+
2. **MCP como interfaz principal:** el CLI existe, pero el caso de uso estrella es `Claude Code → tool MCP → contexto`.
|
|
23
|
+
3. **Degradación elegante:** cada etapa del pipeline es opcional. Sin audio → sin transcripción. Sin logcat → solo video. Sin OCR disponible → solo frames.
|
|
24
|
+
4. **Salida en texto plano primero:** el reporte es markdown legible por humanos y por IA; los frames son adjuntos referenciados.
|
|
25
|
+
5. **Costo cero por defecto:** ninguna etapa del núcleo requiere API keys. El análisis con vision-LLM es una capa opcional posterior.
|
|
26
|
+
|
|
27
|
+
## 3. Arquitectura del pipeline
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
video.mp4 ──► [1] Extracción de frames (ffmpeg, detección de escena + intervalo fijo)
|
|
31
|
+
──► [2] Deduplicación (hash perceptual)
|
|
32
|
+
──► [3] OCR por frame (macOS Vision / tesseract fallback)
|
|
33
|
+
audio ──► [4] Transcripción (faster-whisper, opcional)
|
|
34
|
+
logcat.txt ──► [5] Parser de logs con timestamps (opcional)
|
|
35
|
+
──► [6] Ensamblador: fusiona las 3 líneas de tiempo (frames, narración, logs)
|
|
36
|
+
──► [7] Salida: bundle/
|
|
37
|
+
├── report.md (cronología unificada)
|
|
38
|
+
├── frames/ (PNGs clave, numerados con timestamp)
|
|
39
|
+
└── meta.json (datos estructurados para consumo programático)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Detalles por etapa
|
|
43
|
+
|
|
44
|
+
**[1] Extracción de frames**
|
|
45
|
+
- `ffmpeg -i video.mp4 -vf "select='gt(scene,0.08)',showinfo" -vsync vfr` para cambios de escena.
|
|
46
|
+
- Parsear stderr de `showinfo` para obtener `pts_time` (timestamp exacto) de cada frame.
|
|
47
|
+
- Muestreo de respaldo: 1 frame cada N segundos (configurable, default 3 s) para transiciones lentas.
|
|
48
|
+
- Umbral de escena configurable (`--scene-threshold`).
|
|
49
|
+
|
|
50
|
+
**[2] Deduplicación**
|
|
51
|
+
- `imagehash.phash` con distancia Hamming configurable (default ≤ 6 = duplicado).
|
|
52
|
+
- Meta: de un video de 2 min quedar con 8–25 frames.
|
|
53
|
+
- Límite duro configurable (`--max-frames`, default 20) priorizando frames con mayor cambio de escena.
|
|
54
|
+
|
|
55
|
+
**[3] OCR**
|
|
56
|
+
- macOS: framework Vision vía `pyobjc` (calidad superior para texto de pantalla, gratis, offline).
|
|
57
|
+
- Fallback multiplataforma: `pytesseract`.
|
|
58
|
+
- Guardar texto por frame en `meta.json`; incluir en el reporte solo líneas relevantes (heurística: contiene "error", "exception", "fatal", "warning", stack traces, o difiere del frame anterior).
|
|
59
|
+
|
|
60
|
+
**[4] Transcripción (opcional)**
|
|
61
|
+
- `faster-whisper` modelo `small` por defecto (balance velocidad/calidad), con timestamps por segmento.
|
|
62
|
+
- Detectar si el video tiene pista de audio con contenido antes de transcribir (skip si es silencio).
|
|
63
|
+
|
|
64
|
+
**[5] Parser de logs (opcional)**
|
|
65
|
+
- Entrada: archivo logcat (`adb logcat -v time`) o log genérico con timestamps.
|
|
66
|
+
- Normalizar timestamps al reloj del video (offset configurable o auto-alineado por hora de inicio de grabación).
|
|
67
|
+
- Filtro por nivel (default: W/E/F) y por tag/paquete de la app.
|
|
68
|
+
|
|
69
|
+
**[6] Ensamblador**
|
|
70
|
+
- Fusiona los tres streams en una sola cronología ordenada por timestamp.
|
|
71
|
+
- Formato de entrada del reporte:
|
|
72
|
+
```
|
|
73
|
+
[00:06] 🖼 frame_003.png — cambio de escena
|
|
74
|
+
OCR: "TypeError: cannot read property 'rate'"
|
|
75
|
+
[00:06] 📋 LOG E/RideService: NullPointerException at RideRequest.kt:142
|
|
76
|
+
[00:08] 🎙 "ahí está, explota al aplicar el descuento"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 4. Stack técnico
|
|
80
|
+
|
|
81
|
+
| Componente | Elección | Notas |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| Lenguaje | Python 3.12+ | ecosistema ffmpeg/whisper/MCP maduro |
|
|
84
|
+
| CLI | `typer` | subcomandos, help autogenerado |
|
|
85
|
+
| Video | `ffmpeg` (subprocess) | dependencia de sistema, verificar en arranque |
|
|
86
|
+
| Dedup | `Pillow` + `imagehash` | |
|
|
87
|
+
| OCR | `pyobjc` (Vision) / `pytesseract` | detección de plataforma en runtime |
|
|
88
|
+
| Audio | `faster-whisper` | extra opcional: `pip install bug2context[audio]` |
|
|
89
|
+
| MCP | SDK oficial de Python MCP (`fastmcp`) | servidor stdio |
|
|
90
|
+
| Empaquetado | `uv` + `pyproject.toml` | instalable vía `uvx bug2context` |
|
|
91
|
+
| Tests | `pytest` | fixtures con videos cortos sintéticos generados con ffmpeg |
|
|
92
|
+
|
|
93
|
+
## 5. Estructura del repo
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
bug2context/
|
|
97
|
+
├── pyproject.toml
|
|
98
|
+
├── README.md
|
|
99
|
+
├── LICENSE
|
|
100
|
+
├── src/bug2context/
|
|
101
|
+
│ ├── __init__.py
|
|
102
|
+
│ ├── cli.py # typer app
|
|
103
|
+
│ ├── pipeline/
|
|
104
|
+
│ │ ├── frames.py # etapas 1-2
|
|
105
|
+
│ │ ├── ocr.py # etapa 3
|
|
106
|
+
│ │ ├── audio.py # etapa 4
|
|
107
|
+
│ │ ├── logs.py # etapa 5
|
|
108
|
+
│ │ └── assemble.py # etapas 6-7
|
|
109
|
+
│ ├── mcp_server.py # servidor MCP
|
|
110
|
+
│ └── config.py # dataclass de opciones + defaults
|
|
111
|
+
└── tests/
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 6. Fases (metodología GSD)
|
|
115
|
+
|
|
116
|
+
### Fase 1 — Núcleo de video (MVP)
|
|
117
|
+
**Objetivo:** `bug2context process video.mp4` produce `bundle/` con `report.md` + frames.
|
|
118
|
+
- [ ] 1.1 Scaffold del repo (`pyproject.toml`, typer, estructura src/)
|
|
119
|
+
- [ ] 1.2 `frames.py`: extracción por escena + intervalo, parseo de timestamps de showinfo
|
|
120
|
+
- [ ] 1.3 Deduplicación por phash con límite de frames
|
|
121
|
+
- [ ] 1.4 `assemble.py`: report.md cronológico solo con frames + meta.json
|
|
122
|
+
- [ ] 1.5 Tests con video sintético (ffmpeg genera pantallas de color con texto que cambian)
|
|
123
|
+
|
|
124
|
+
**Criterio de aceptación:** un screen recording real de 1–3 min produce ≤ 20 frames relevantes con timestamps correctos, en < 30 s de procesamiento.
|
|
125
|
+
|
|
126
|
+
### Fase 2 — OCR
|
|
127
|
+
- [ ] 2.1 Backend Vision (macOS) vía pyobjc
|
|
128
|
+
- [ ] 2.2 Backend tesseract como fallback
|
|
129
|
+
- [ ] 2.3 Heurística de relevancia (errores, diffs entre frames)
|
|
130
|
+
- [ ] 2.4 Integración al reporte
|
|
131
|
+
|
|
132
|
+
**Criterio de aceptación:** un video con un stack trace visible en pantalla produce ese texto legible en report.md.
|
|
133
|
+
|
|
134
|
+
### Fase 3 — Servidor MCP
|
|
135
|
+
- [ ] 3.1 `mcp_server.py` con fastmcp, transporte stdio
|
|
136
|
+
- [ ] 3.2 Tool `analyze_bug_video(path, options)` → devuelve report.md + rutas de frames
|
|
137
|
+
- [ ] 3.3 Tool `get_frame(bundle, index)` → devuelve imagen para que el agente la vea
|
|
138
|
+
- [ ] 3.4 Documentar instalación en Claude Code (`claude mcp add`)
|
|
139
|
+
|
|
140
|
+
**Criterio de aceptación:** desde Claude Code, pedir "analiza este video del bug" ejecuta la tool y el agente razona sobre la cronología sin pasos manuales.
|
|
141
|
+
|
|
142
|
+
### Fase 4 — Audio (narración)
|
|
143
|
+
- [ ] 4.1 Extracción de pista de audio + detección de silencio
|
|
144
|
+
- [ ] 4.2 faster-whisper con timestamps por segmento
|
|
145
|
+
- [ ] 4.3 Fusión en la cronología
|
|
146
|
+
|
|
147
|
+
### Fase 5 — Orquestador móvil (Android primero)
|
|
148
|
+
- [ ] 5.1 `bug2context record --android`: lanza `adb shell screenrecord` + `adb logcat -v time` en paralelo
|
|
149
|
+
- [ ] 5.2 Alineación de relojes video↔logcat
|
|
150
|
+
- [ ] 5.3 Parser de logcat con filtros por nivel y paquete
|
|
151
|
+
- [ ] 5.4 Fusión de logs en la cronología
|
|
152
|
+
- [ ] 5.5 (Posterior) iOS: simctl + log stream
|
|
153
|
+
|
|
154
|
+
**Criterio de aceptación:** reproducir un crash en un dispositivo Android conectado produce un reporte donde el stack trace del logcat aparece en el instante del frame del crash.
|
|
155
|
+
|
|
156
|
+
### Fase 6 — Publicación
|
|
157
|
+
- [ ] 6.1 README con demo GIF, ejemplos de uso con Claude Code
|
|
158
|
+
- [ ] 6.2 Publicar en PyPI + anuncio (X, Reddit r/ClaudeAI, HN)
|
|
159
|
+
- [ ] 6.3 Recoger feedback y definir capa de monetización (cloud, GitHub Issues, equipo)
|
|
160
|
+
|
|
161
|
+
## 7. Diseño del servidor MCP (contrato)
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
@mcp.tool()
|
|
165
|
+
def analyze_bug_video(
|
|
166
|
+
video_path: str,
|
|
167
|
+
logcat_path: str | None = None,
|
|
168
|
+
transcribe_audio: bool = False,
|
|
169
|
+
max_frames: int = 20,
|
|
170
|
+
) -> str:
|
|
171
|
+
"""Procesa un video de un bug y devuelve un reporte cronológico
|
|
172
|
+
en markdown con referencias a frames clave."""
|
|
173
|
+
|
|
174
|
+
@mcp.tool()
|
|
175
|
+
def get_frame(bundle_path: str, frame_index: int) -> Image:
|
|
176
|
+
"""Devuelve un frame específico del bundle para inspección visual."""
|
|
177
|
+
|
|
178
|
+
@mcp.tool()
|
|
179
|
+
def list_bundles(workdir: str = "~/.bug2context") -> list[dict]:
|
|
180
|
+
"""Lista los bundles procesados con fecha y video de origen."""
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## 8. Riesgos y mitigaciones
|
|
184
|
+
|
|
185
|
+
| Riesgo | Mitigación |
|
|
186
|
+
|---|---|
|
|
187
|
+
| OCR pobre en videos comprimidos (WhatsApp recomprime mucho) | upscaling 2x con ffmpeg antes de OCR; documentar "pide el video como archivo, no como video de WhatsApp" |
|
|
188
|
+
| Detección de escena falla en apps con animaciones constantes | combinar escena + intervalo fijo; umbral configurable |
|
|
189
|
+
| Ventana competitiva (otros ya construyen en el nicho) | priorizar Fases 1–3 para tener el MVP con MCP publicado rápido; el orquestador adb (Fase 5) es el foso |
|
|
190
|
+
| Desalineación de relojes video↔logcat | registrar timestamp de inicio de screenrecord; offset manual como escape |
|
|
191
|
+
| pyobjc/Vision frágil entre versiones de macOS | tesseract siempre disponible como fallback |
|
|
192
|
+
|
|
193
|
+
## 9. Instrucciones de trabajo para Claude Code
|
|
194
|
+
|
|
195
|
+
- Ejecutar comando por comando, confirmando entre pasos.
|
|
196
|
+
- Trabajar en un worktree de git dedicado.
|
|
197
|
+
- Cada fase = una rama; merge al completar criterio de aceptación.
|
|
198
|
+
- Escribir tests antes de cerrar cada tarea numerada.
|
|
199
|
+
- No agregar dependencias fuera de las listadas sin consultar.
|
|
200
|
+
- Máquina de desarrollo: macOS (Homebrew disponible para ffmpeg/tesseract).
|