bugbee 0.1.0__tar.gz → 0.1.3__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 (31) hide show
  1. {bugbee-0.1.0 → bugbee-0.1.3}/PKG-INFO +15 -15
  2. {bugbee-0.1.0 → bugbee-0.1.3}/README.md +14 -14
  3. bugbee-0.1.3/bugbee/cli/main.py +641 -0
  4. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/ui.py +5 -5
  5. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee.egg-info/PKG-INFO +15 -15
  6. {bugbee-0.1.0 → bugbee-0.1.3}/pyproject.toml +1 -1
  7. bugbee-0.1.0/bugbee/cli/main.py +0 -258
  8. {bugbee-0.1.0 → bugbee-0.1.3}/LICENSE +0 -0
  9. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/__init__.py +0 -0
  10. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/cache/__init__.py +0 -0
  11. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/cli/__init__.py +0 -0
  12. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/config/settings.py +0 -0
  13. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/lazy.py +0 -0
  14. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/legacy.py +0 -0
  15. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/llm/__init__.py +0 -0
  16. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/metrics/__init__.py +0 -0
  17. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/parser/__init__.py +0 -0
  18. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/parser/error_parser.py +0 -0
  19. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/patcher/__init__.py +0 -0
  20. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/retrieval/__init__.py +0 -0
  21. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/utils/logging.py +0 -0
  22. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee/validator/__init__.py +0 -0
  23. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee.egg-info/SOURCES.txt +0 -0
  24. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee.egg-info/dependency_links.txt +0 -0
  25. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee.egg-info/entry_points.txt +0 -0
  26. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee.egg-info/requires.txt +0 -0
  27. {bugbee-0.1.0 → bugbee-0.1.3}/bugbee.egg-info/top_level.txt +0 -0
  28. {bugbee-0.1.0 → bugbee-0.1.3}/setup.cfg +0 -0
  29. {bugbee-0.1.0 → bugbee-0.1.3}/tests/test.py +0 -0
  30. {bugbee-0.1.0 → bugbee-0.1.3}/tests/test2.py +0 -0
  31. {bugbee-0.1.0 → bugbee-0.1.3}/tests/test_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bugbee
3
- Version: 0.1.0
3
+ Version: 0.1.3
4
4
  Summary: AI-powered CLI debugging and auto-repair tool with RAG, automatic patch generation, and validation.
5
5
  Author-email: Pranav Kurmadasu <kurmadasu.pranav@example.com>
6
6
  License-Expression: Apache-2.0
@@ -46,7 +46,7 @@ Requires-Dist: build>=1.2.1; extra == "dev"
46
46
  Requires-Dist: twine>=5.1.1; extra == "dev"
47
47
  Dynamic: license-file
48
48
 
49
- # 🐝 BugBee
49
+ # BugBee
50
50
 
51
51
  > **AI-powered CLI debugging assistant that automatically analyzes runtime errors, retrieves relevant documentation using RAG, and suggests or applies intelligent fixes.**
52
52
 
@@ -54,19 +54,19 @@ BugBee wraps your terminal commands, intercepts runtime failures, analyzes the t
54
54
 
55
55
  ---
56
56
 
57
- ## ✨ Features
58
-
59
- * 🤖 AI-powered debugging using **DeepSeek-R1**
60
- * 📚 Retrieval-Augmented Generation (RAG) using **ChromaDB**
61
- * 🔍 Automatic traceback parsing
62
- * 🧠 Context-aware code analysis
63
- * 📖 Retrieval from official framework documentation
64
- * ⚡ SHA256-based intelligent response caching
65
- * 🩹 Automatic code patch generation
66
- * ✅ Validation after applying fixes
67
- * 📊 Metrics collection
68
- * 💻 Cross-platform support (Windows, Linux, macOS)
69
- * 🚀 Packaged for PyPI
57
+ ## Features
58
+
59
+ * AI-powered debugging using **DeepSeek-R1**
60
+ * Retrieval-Augmented Generation (RAG) using **ChromaDB**
61
+ * Automatic traceback parsing
62
+ * Context-aware code analysis
63
+ * Retrieval from official framework documentation
64
+ * SHA256-based intelligent response caching
65
+ * Automatic code patch generation
66
+ * Validation after applying fixes
67
+ * Metrics collection
68
+ * Cross-platform support (Windows, Linux, macOS)
69
+ * Packaged for PyPI
70
70
 
71
71
  ---
72
72
 
@@ -1,4 +1,4 @@
1
- # 🐝 BugBee
1
+ # BugBee
2
2
 
3
3
  > **AI-powered CLI debugging assistant that automatically analyzes runtime errors, retrieves relevant documentation using RAG, and suggests or applies intelligent fixes.**
4
4
 
@@ -6,19 +6,19 @@ BugBee wraps your terminal commands, intercepts runtime failures, analyzes the t
6
6
 
7
7
  ---
8
8
 
9
- ## ✨ Features
10
-
11
- * 🤖 AI-powered debugging using **DeepSeek-R1**
12
- * 📚 Retrieval-Augmented Generation (RAG) using **ChromaDB**
13
- * 🔍 Automatic traceback parsing
14
- * 🧠 Context-aware code analysis
15
- * 📖 Retrieval from official framework documentation
16
- * ⚡ SHA256-based intelligent response caching
17
- * 🩹 Automatic code patch generation
18
- * ✅ Validation after applying fixes
19
- * 📊 Metrics collection
20
- * 💻 Cross-platform support (Windows, Linux, macOS)
21
- * 🚀 Packaged for PyPI
9
+ ## Features
10
+
11
+ * AI-powered debugging using **DeepSeek-R1**
12
+ * Retrieval-Augmented Generation (RAG) using **ChromaDB**
13
+ * Automatic traceback parsing
14
+ * Context-aware code analysis
15
+ * Retrieval from official framework documentation
16
+ * SHA256-based intelligent response caching
17
+ * Automatic code patch generation
18
+ * Validation after applying fixes
19
+ * Metrics collection
20
+ * Cross-platform support (Windows, Linux, macOS)
21
+ * Packaged for PyPI
22
22
 
23
23
  ---
24
24
 
@@ -0,0 +1,641 @@
1
+ """CLI entry point for the BugBee package.
2
+
3
+ The original monolithic implementation lived in ``cli-wrapper/src/cli_wrapper/main.py``.
4
+ This version keeps the ``run`` command as a clean pipeline built on top of:
5
+
6
+ * ``bugbee.parser.error_parser`` – stack‑trace extraction, sanitisation, hashing
7
+ * ``bugbee.cache`` – JSON cache wrapper
8
+ * ``bugbee.retrieval`` – ChromaDB document retrieval
9
+ * ``bugbee.llm`` – LLM provider abstraction
10
+ * ``bugbee.patcher`` – safe patch application with backup
11
+ * ``bugbee.validator`` – post‑patch validation runner
12
+ * ``bugbee.metrics`` – metrics collection and JSONL persistence
13
+ * ``bugbee.ui`` – ALL logging and terminal presentation (spinners, colored
14
+ status lines, panels, tables, confirmation prompts) lives here. This file
15
+ contains no direct ``log.*`` or ``typer.echo`` calls — every user-facing
16
+ message is routed through the ``ui`` singleton so the pipeline below reads
17
+ as pure business logic.
18
+
19
+ The command line mimics the historic behaviour while being fully testable,
20
+ type‑safe, and pleasant to actually use.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import re
26
+ import sys
27
+ from pathlib import Path
28
+ from typing import List, Tuple
29
+
30
+ import typer
31
+
32
+ from bugbee import __version__
33
+ from bugbee.config.settings import settings
34
+ from bugbee.parser.error_parser import (
35
+ extract_location,
36
+ sanitize_error,
37
+ error_hash,
38
+ )
39
+ from bugbee.cache import get_cache
40
+ from bugbee.lazy import lazy_import
41
+ from bugbee.ui import ui
42
+ from bugbee.utils.logging import enable_console_logging
43
+ # Heavy imports are lazy‑loaded inside the command handlers.
44
+ # from bugbee.retrieval import get_related_docs
45
+ # from bugbee.llm import default_provider
46
+ # from bugbee.metrics import MetricsCollector
47
+ # from bugbee.patcher import apply_patch
48
+ # from bugbee.validator import run_validation
49
+
50
+ app = typer.Typer(
51
+ help="BugBee – AI‑powered CLI debugger and auto‑repair tool",
52
+ rich_markup_mode="rich",
53
+ )
54
+
55
+
56
+ @app.callback()
57
+ def main(
58
+ verbose: bool = typer.Option(
59
+ False,
60
+ "--verbose",
61
+ "-v",
62
+ help="Also print raw, timestamped log lines to the console (in addition to the normal UI).",
63
+ ),
64
+ ) -> None:
65
+ """BugBee – AI‑powered CLI debugger and auto‑repair tool."""
66
+ if verbose:
67
+ enable_console_logging()
68
+
69
+
70
+ def _run_subprocess(command: List[str]) -> Tuple[int, str, str]:
71
+ """Execute *command* and return ``(returncode, stdout, stderr)``.
72
+
73
+ The function deliberately forwards stdout directly to the console (as the
74
+ original tool did) and captures stderr for analysis.
75
+ """
76
+ import subprocess
77
+
78
+ ui.running_command(command)
79
+ proc = subprocess.Popen(
80
+ command,
81
+ stdout=subprocess.PIPE,
82
+ stderr=subprocess.PIPE,
83
+ text=True,
84
+ )
85
+ out, err = proc.communicate()
86
+ # Echo stdout to the console to preserve original behaviour.
87
+ if out:
88
+ sys.stdout.write(out)
89
+ return proc.returncode, out, err
90
+
91
+
92
+ def _parse_llm_output(output: str) -> Tuple[str, str, str]:
93
+ """Extract ``error_line`` and ``fix`` from the LLM ``output``.
94
+
95
+ The original script expected XML‑like tags <ERROR_LINE> and <FIX>. We keep
96
+ that contract for backward compatibility.
97
+ """
98
+ error_line_match = re.search(r"<ERROR_LINE>(.*?)</ERROR_LINE>", output, re.DOTALL)
99
+ fix_match = re.search(r"<FIX>(.*?)</FIX>", output, re.DOTALL)
100
+ error_line = error_line_match.group(1).strip() if error_line_match else ""
101
+ fix = fix_match.group(1).strip() if fix_match else ""
102
+ # The full content without the tags is also useful for reporting.
103
+ content = output.strip()
104
+ return content, error_line, fix
105
+
106
+
107
+ @app.command()
108
+ def run(
109
+ command: List[str] = typer.Argument(..., help="Command to execute and debug"),
110
+ yes: bool = typer.Option(False, "--yes", "-y", help="Auto‑accept fix without prompting"),
111
+ ) -> None:
112
+ """Execute *command*, analyse failures and optionally apply an auto‑fix."""
113
+ # Lazy imports of heavy modules – they are only loaded when this command runs.
114
+ MetricsCollector = lazy_import('bugbee.metrics').MetricsCollector
115
+ get_related_docs = lazy_import('bugbee.retrieval').get_related_docs
116
+ default_provider = lazy_import('bugbee.llm').default_provider
117
+ apply_patch = lazy_import('bugbee.patcher').apply_patch
118
+ run_validation = lazy_import('bugbee.validator').run_validation
119
+
120
+ metrics = MetricsCollector()
121
+ metrics.update("command", " ".join(command))
122
+
123
+ returncode, _, stderr = _run_subprocess(command)
124
+ if returncode == 0:
125
+ ui.command_succeeded()
126
+ metrics.write()
127
+ raise typer.Exit(code=0)
128
+
129
+ ui.command_failed()
130
+
131
+ # 1️⃣ Extract location info (file + line) – may be ``None``.
132
+ file_path, line_number = extract_location(stderr, command)
133
+ if file_path and line_number:
134
+ ui.location_found(file_path, line_number)
135
+ else:
136
+ ui.location_not_found()
137
+
138
+ # 2️⃣ Sanitize error and compute hash for caching.
139
+ cleaned = sanitize_error(stderr)
140
+ err_hash = error_hash(cleaned)
141
+ cache_obj = get_cache()
142
+ cached = cache_obj.get(err_hash)
143
+ if cached:
144
+ ui.cache_hit()
145
+ llm_content, error_line, fix = cached
146
+ else:
147
+ ui.cache_miss()
148
+ # Retrieve relevant docs.
149
+ with ui.spinner("Retrieving relevant documentation…"):
150
+ docs, top_score, avg_score = get_related_docs(stderr)
151
+ metrics.update("retrieval.top_score", top_score)
152
+ metrics.update("retrieval.avg_score", avg_score)
153
+ ui.retrieval_stats(top_score, avg_score)
154
+
155
+ # Very simple prompt composition.
156
+ prompt = (
157
+ f"Error message:\n{stderr}\n\n"
158
+ f"Relevant documentation:\n{docs}\n\n"
159
+ "Provide an XML-like response containing <ERROR_LINE> and <FIX>."
160
+ )
161
+ with ui.spinner("Consulting the model for a fix…"):
162
+ llm_resp = default_provider().generate(prompt)
163
+
164
+ llm_content, error_line, fix = _parse_llm_output(llm_resp)
165
+ cache_obj.set(err_hash, (llm_content, error_line, fix))
166
+
167
+ # Show the LLM analysis to the user.
168
+ ui.show_analysis(llm_content)
169
+
170
+ # Auto‑fix handling.
171
+ performed = False
172
+ accept = ui.confirm_fix(yes)
173
+ if accept and file_path:
174
+ ui.applying_patch(file_path, line_number)
175
+ try:
176
+ success = apply_patch(file_path, line_number, error_line, fix + "\n", create_backup=True)
177
+ if success:
178
+ performed = True
179
+ metrics.update("autofix.patch_applied", True)
180
+ metrics.update("autofix.files_modified", 1)
181
+ ui.patch_applied()
182
+ # Run validation to ensure fix works.
183
+ with ui.spinner("Validating the fix…"):
184
+ validation_ok = run_validation(command, cwd=Path.cwd())
185
+ if validation_ok:
186
+ metrics.update("validation.passed", True)
187
+ ui.validation_passed()
188
+ else:
189
+ ui.validation_failed()
190
+ else:
191
+ ui.patch_not_applied()
192
+ except Exception as exc:
193
+ ui.patch_error(exc)
194
+ else:
195
+ ui.auto_fix_declined()
196
+
197
+ # Record final metrics.
198
+ metrics.update("diagnosis.error_line_found", bool(error_line))
199
+ metrics.update("diagnosis.fix_generated", bool(fix))
200
+ metrics.write()
201
+
202
+ ui.summary(fix_applied=performed, returncode=returncode)
203
+
204
+ # Exit with the original return code if no successful fix was applied.
205
+ if performed:
206
+ raise typer.Exit(code=0)
207
+ else:
208
+ raise typer.Exit(code=returncode)
209
+
210
+
211
+ @app.command()
212
+ def analyze(
213
+ command: List[str] = typer.Argument(..., help="Command to analyze without applying a fix"),
214
+ ) -> None:
215
+ """Run *command* and display LLM analysis but do not prompt for auto‑fix."""
216
+ # Reuse ``run`` logic but force ``yes=False`` and skip patch step.
217
+ run(command=command, yes=False)
218
+
219
+
220
+ @app.command()
221
+ def version() -> None:
222
+ """Print the installed BugBee version."""
223
+ ui.version(__version__)
224
+
225
+
226
+ @app.command()
227
+ def cache_clear() -> None:
228
+ """Remove the local cache file (``bugbee.json``)."""
229
+ get_cache().clear()
230
+ ui.cache_cleared()
231
+
232
+
233
+ @app.command()
234
+ def config_show() -> None:
235
+ """Display the effective configuration (environment variables, .env, defaults)."""
236
+ ui.config_table(settings.dict())
237
+
238
+
239
+ @app.command()
240
+ def doctor() -> None:
241
+ """Run a quick health‑check of required dependencies and connectivity."""
242
+ ui.doctor_start()
243
+ missing = []
244
+ try:
245
+ import chromadb # noqa: F401
246
+ except Exception:
247
+ missing.append("chromadb")
248
+ try:
249
+ import langchain # noqa: F401
250
+ except Exception:
251
+ missing.append("langchain")
252
+ ui.doctor_result(missing)
253
+ if missing:
254
+ raise typer.Exit(code=1)
255
+
256
+ def jayaho() -> None:
257
+ """Displays a menu of machine learning topics and prints the corresponding code."""
258
+ print("Select an option:")
259
+ print("1. LinR")
260
+ print("2. Log")
261
+ print("3. KNNc")
262
+ print("4. KNNr")
263
+ print("5. SVM")
264
+ print("6. DTree.")
265
+ print("7. RForest.")
266
+ print("8. Naive")
267
+ print("9. K-means")
268
+ print("10. Hierarchical.")
269
+ print("11. CNN")
270
+ print("12. RNN")
271
+ print("13. LSTM")
272
+ print("14. ObjD")
273
+ print("15. Face")
274
+
275
+ choice = input("Enter your choice (1-15): ").strip()
276
+
277
+ if choice == "1":
278
+ print('''# Linear Regression
279
+ import numpy as np
280
+ import matplotlib.pyplot as plt
281
+ from sklearn.linear_model import LinearRegression
282
+ from sklearn.model_selection import train_test_split
283
+ from sklearn.metrics import mean_squared_error, r2_score
284
+
285
+ # Fixed dataset: area (sq. ft.) and price (lakhs)
286
+ X = np.array([500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400]).reshape(-1, 1)
287
+ y = np.array([15, 18, 21, 24, 27, 30, 33.9, 35.7, 39.6, 47])
288
+
289
+ X_train, X_test, y_train, y_test = train_test_split(
290
+ X, y, test_size=0.2, random_state=42
291
+ )
292
+
293
+ model = LinearRegression()
294
+ model.fit(X_train, y_train)
295
+
296
+ pred = model.predict(X_test)
297
+ print("R2 Score:", r2_score(y_test, pred))
298
+ print("MSE:", mean_squared_error(y_test, pred))
299
+
300
+ plt.scatter(X, y, label="Actual data")
301
+ plt.plot(X, model.predict(X), color="red", label="Regression line")
302
+ plt.xlabel("Area (sq. ft.)")
303
+ plt.ylabel("Price (lakhs)")
304
+ plt.legend()
305
+ plt.show()
306
+
307
+ print("Price for 1500 sq.ft.:", model.predict([[1500]])[0])''')
308
+ elif choice == "2":
309
+ print('''# Logistic Regression
310
+ from sklearn.datasets import load_iris
311
+ from sklearn.model_selection import train_test_split
312
+ from sklearn.linear_model import LogisticRegression
313
+ from sklearn.metrics import accuracy_score, classification_report
314
+
315
+ data = load_iris()
316
+ X, y = data.data, data.target
317
+
318
+ X_train, X_test, y_train, y_test = train_test_split(
319
+ X, y, test_size=0.2, random_state=42, stratify=y
320
+ )
321
+
322
+ model = LogisticRegression(max_iter=200)
323
+ model.fit(X_train, y_train)
324
+
325
+ pred = model.predict(X_test)
326
+ print("Accuracy:", accuracy_score(y_test, pred))
327
+ print(classification_report(y_test, pred, target_names=data.target_names))
328
+
329
+ # Predict a new flower
330
+ sample = [[5.1, 3.5, 1.4, 0.2]]
331
+ print("Predicted flower:", data.target_names[model.predict(sample)[0]])''')
332
+ elif choice == "3":
333
+ print('''# KNN Classification
334
+ from sklearn.datasets import load_iris
335
+ from sklearn.model_selection import train_test_split
336
+ from sklearn.neighbors import KNeighborsClassifier
337
+ from sklearn.metrics import accuracy_score, classification_report
338
+ from sklearn.preprocessing import StandardScaler
339
+ from sklearn.pipeline import make_pipeline
340
+
341
+ data = load_iris()
342
+ X, y = data.data, data.target
343
+
344
+ X_train, X_test, y_train, y_test = train_test_split(
345
+ X, y, test_size=0.2, random_state=42, stratify=y
346
+ )
347
+
348
+ model = KNeighborsClassifier(n_neighbors=3)
349
+ model.fit(X_train, y_train)
350
+
351
+ pred = model.predict(X_test)
352
+ print("Accuracy:", accuracy_score(y_test, pred))
353
+ print(classification_report(y_test, pred, target_names=data.target_names))
354
+
355
+ print("New flower:", data.target_names[
356
+ model.predict([[5.1, 3.5, 1.4, 0.2]])[0]
357
+ ])''')
358
+ elif choice == "4":
359
+ print('''# KNN Regression
360
+ import numpy as np
361
+ import matplotlib.pyplot as plt
362
+ from sklearn.neighbors import KNeighborsRegressor
363
+ from sklearn.model_selection import train_test_split
364
+ from sklearn.metrics import mean_squared_error, r2_score
365
+
366
+ X = np.array([500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400]).reshape(-1, 1)
367
+ y = np.array([15, 18, 21, 24, 27, 30, 33, 36, 39, 42])
368
+
369
+ X_train, X_test, y_train, y_test = train_test_split(
370
+ X, y, test_size=0.5, random_state=42
371
+ )
372
+
373
+ model = KNeighborsRegressor(n_neighbors=3)
374
+ model.fit(X_train, y_train)
375
+
376
+ pred = model.predict(X_test)
377
+ print("R2 Score:", r2_score(y_test, pred))
378
+ print("MSE:", mean_squared_error(y_test, pred))
379
+ print("Price for 950 sq.ft.:", model.predict([[950]])[0])''')
380
+ elif choice == "5":
381
+ print('''# SVM
382
+ from sklearn.datasets import load_iris
383
+ from sklearn.model_selection import train_test_split
384
+ from sklearn.svm import SVC
385
+ from sklearn.metrics import accuracy_score, classification_report
386
+ from sklearn.pipeline import make_pipeline
387
+ from sklearn.preprocessing import StandardScaler
388
+
389
+ data = load_iris()
390
+ X, y = data.data, data.target
391
+
392
+ X_train, X_test, y_train, y_test = train_test_split(
393
+ X, y, test_size=0.2, random_state=42, stratify=y
394
+ )
395
+
396
+ model = SVC(kernel="linear")
397
+ model.fit(X_train, y_train)
398
+
399
+ pred = model.predict(X_test)
400
+ print("Accuracy:", accuracy_score(y_test, pred))
401
+ print(classification_report(y_test, pred, target_names=data.target_names))
402
+
403
+ print("New flower:", data.target_names[
404
+ model.predict([[5.2, 3.5, 1.4, 0.2]])[0]
405
+ ])''')
406
+ elif choice == "6":
407
+ print('''# Decision Tree
408
+ from sklearn.datasets import load_iris
409
+ from sklearn.model_selection import train_test_split
410
+ from sklearn.tree import DecisionTreeClassifier
411
+ from sklearn.metrics import accuracy_score, classification_report
412
+
413
+ data = load_iris()
414
+ X, y = data.data, data.target
415
+
416
+ X_train, X_test, y_train, y_test = train_test_split(
417
+ X, y, test_size=0.2, random_state=42, stratify=y
418
+ )
419
+
420
+ model = DecisionTreeClassifier(max_depth=3, random_state=42)
421
+ model.fit(X_train, y_train)
422
+
423
+ pred = model.predict(X_test)
424
+ print("Accuracy:", accuracy_score(y_test, pred))
425
+ print(classification_report(y_test, pred, target_names=data.target_names))
426
+
427
+ print("New flower:", data.target_names[
428
+ model.predict([[5.1, 3.5, 1.4, 0.2]])[0]
429
+ ])''')
430
+ elif choice == "7":
431
+ print('''# Random Forest
432
+ from sklearn.datasets import load_iris
433
+ from sklearn.model_selection import train_test_split
434
+ from sklearn.ensemble import RandomForestClassifier
435
+ from sklearn.metrics import accuracy_score, classification_report
436
+
437
+ data = load_iris()
438
+ X, y = data.data, data.target
439
+
440
+ X_train, X_test, y_train, y_test = train_test_split(
441
+ X, y, test_size=0.2, random_state=42, stratify=y
442
+ )
443
+
444
+ model = RandomForestClassifier(n_estimators=100, random_state=42)
445
+ model.fit(X_train, y_train)
446
+
447
+ pred = model.predict(X_test)
448
+ print("Accuracy:", accuracy_score(y_test, pred))
449
+ print(classification_report(y_test, pred, target_names=data.target_names))
450
+
451
+ print("New flower:", data.target_names[
452
+ model.predict([[5.1, 3.5, 1.4, 0.2]])[0]
453
+ ])''')
454
+ elif choice == "8":
455
+ print('''# Naive Bayes
456
+ from sklearn.datasets import load_iris
457
+ from sklearn.model_selection import train_test_split
458
+ from sklearn.naive_bayes import GaussianNB
459
+ from sklearn.metrics import accuracy_score, classification_report
460
+
461
+ data = load_iris()
462
+ X, y = data.data, data.target
463
+
464
+ X_train, X_test, y_train, y_test = train_test_split(
465
+ X, y, test_size=0.2, random_state=42, stratify=y
466
+ )
467
+
468
+ model = GaussianNB()
469
+ model.fit(X_train, y_train)
470
+
471
+ pred = model.predict(X_test)
472
+ print("Accuracy:", accuracy_score(y_test, pred))
473
+ print(classification_report(y_test, pred, target_names=data.target_names))
474
+
475
+ print("New flower:", data.target_names[
476
+ model.predict([[5.1, 3.5, 1.4, 0.2]])[0]
477
+ ])''')
478
+ elif choice == "9":
479
+ print('''# K-Means
480
+ import matplotlib.pyplot as plt
481
+ from sklearn.datasets import load_iris
482
+ from sklearn.cluster import KMeans
483
+ from sklearn.preprocessing import StandardScaler
484
+ from sklearn.metrics import silhouette_score
485
+
486
+ data = load_iris()
487
+ X = data.data[:, :2] # First two features for easy plotting
488
+ X = StandardScaler().fit_transform(X)
489
+
490
+ # K-Means
491
+ km = KMeans(n_clusters=3, random_state=42, n_init=10)
492
+ labels_km = km.fit_predict(X)
493
+
494
+ print("K-Means Silhouette:", silhouette_score(X, labels_km))
495
+
496
+ plt.scatter(X[:, 0], X[:, 1], c=labels_km)
497
+ plt.title("K-Means Clustering")
498
+ plt.xlabel("Sepal length (scaled)")
499
+ plt.ylabel("Sepal width (scaled)")
500
+ plt.show()''')
501
+ elif choice == "10":
502
+ print('''# Hierarchical Clustering
503
+ import matplotlib.pyplot as plt
504
+ from sklearn.datasets import load_iris
505
+ from sklearn.cluster import AgglomerativeClustering
506
+ from sklearn.preprocessing import StandardScaler
507
+ from sklearn.metrics import silhouette_score
508
+
509
+ data = load_iris()
510
+ X = data.data[:, :2] # First two features for easy plotting
511
+ X = StandardScaler().fit_transform(X)
512
+
513
+ # Hierarchical clustering
514
+ hc = AgglomerativeClustering(n_clusters=3, linkage="ward")
515
+ labels_hc = hc.fit_predict(X)
516
+
517
+ print("Hierarchical Silhouette:", silhouette_score(X, labels_hc))
518
+
519
+ plt.scatter(X[:, 0], X[:, 1], c=labels_hc)
520
+ plt.title("Hierarchical Clustering")
521
+ plt.xlabel("Sepal length (scaled)")
522
+ plt.ylabel("Sepal width (scaled)")
523
+ plt.show()''')
524
+ elif choice == "11":
525
+ print('''# CNN
526
+ import numpy as np
527
+ import matplotlib.pyplot as plt
528
+ import tensorflow as tf
529
+ from tensorflow.keras import layers, models
530
+
531
+ # Load fixed built-in dataset
532
+ (X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()
533
+
534
+ # Normalize pixels and add channel dimension
535
+ X_train = X_train.astype("float32") / 255.0
536
+ X_test = X_test.astype("float32") / 255.0
537
+ X_train = X_train[..., np.newaxis]
538
+ X_test = X_test[..., np.newaxis]
539
+
540
+ # CNN model
541
+ model = models.Sequential([
542
+ layers.Input(shape=(28, 28, 1)),
543
+ layers.Conv2D(16, (3, 3), activation="relu"),
544
+ layers.MaxPooling2D((2, 2)),
545
+ layers.Flatten(),
546
+ layers.Dense(64, activation="relu"),
547
+ layers.Dense(10, activation="softmax")
548
+ ])
549
+
550
+ model.compile(optimizer="adam",
551
+ loss="sparse_categorical_crossentropy",
552
+ metrics=["accuracy"])
553
+
554
+ model.fit(X_train, y_train, epochs=3, batch_size=64, validation_split=0.1, verbose=1)
555
+
556
+ loss, acc = model.evaluate(X_test, y_test, verbose=0)
557
+ print("Test accuracy:", acc)
558
+
559
+ # Give an input by choosing a test-image index
560
+ i = int(input("Enter test image index (0-9999): "))
561
+ i = max(0, min(i, len(X_test) - 1))
562
+ prediction = model.predict(X_test[i:i+1], verbose=0)
563
+ print("Predicted digit:", np.argmax(prediction))
564
+ print("Actual digit:", y_test[i])
565
+
566
+ plt.imshow(X_test[i].squeeze(), cmap="gray")
567
+ plt.title("Predicted: " + str(np.argmax(prediction)))
568
+ plt.axis("off")
569
+ plt.show()''')
570
+ elif choice in ["12", "13"]:
571
+ print('''# RNN and LSTM
572
+ import numpy as np
573
+ import matplotlib.pyplot as plt
574
+ import tensorflow as tf
575
+ from tensorflow.keras import Sequential
576
+ from tensorflow.keras.layers import SimpleRNN, LSTM, Dense
577
+
578
+ # Fixed time-series data
579
+ data = np.array([
580
+ 10, 12, 13, 15, 14, 16, 18, 17, 20, 21,
581
+ 23, 22, 25, 27, 26, 29, 30, 32, 31, 34,
582
+ 36, 35, 38, 40, 39, 42, 44, 43, 46, 48
583
+ ], dtype="float32")
584
+
585
+ # Create sequences of 3 values to predict the next value
586
+ X, y = [], []
587
+ for i in range(len(data) - 3):
588
+ X.append(data[i:i+3])
589
+ y.append(data[i+3])
590
+ X = np.array(X).reshape(-1, 3, 1)
591
+ y = np.array(y)
592
+
593
+ # Chronological split: first 80% train, last 20% test
594
+ split = int(0.8 * len(X))
595
+ X_train, X_test = X[:split], X[split:]
596
+ y_train, y_test = y[:split], y[split:]
597
+
598
+ # RNN
599
+ rnn = Sequential([
600
+ tf.keras.Input(shape=(3, 1)),
601
+ SimpleRNN(16, activation="tanh"),
602
+ Dense(1)
603
+ ])
604
+ rnn.compile(optimizer="adam", loss="mse")
605
+ rnn.fit(X_train, y_train, epochs=100, verbose=0)
606
+
607
+ # LSTM
608
+ lstm = Sequential([
609
+ tf.keras.Input(shape=(3, 1)),
610
+ LSTM(16),
611
+ Dense(1)
612
+ ])
613
+ lstm.compile(optimizer="adam", loss="mse")
614
+ lstm.fit(X_train, y_train, epochs=100, verbose=0)
615
+
616
+ # Predictions
617
+ rnn_pred = rnn.predict(X_test, verbose=0).flatten()
618
+ lstm_pred = lstm.predict(X_test, verbose=0).flatten()
619
+
620
+ print("RNN Test MSE:", np.mean((y_test - rnn_pred) ** 2))
621
+ print("LSTM Test MSE:", np.mean((y_test - lstm_pred) ** 2))
622
+
623
+ plt.plot(y_test, marker="o", label="Actual")
624
+ plt.plot(rnn_pred, marker="x", label="RNN")
625
+ plt.plot(lstm_pred, marker="s", label="LSTM")
626
+ plt.title("Time-Series Prediction")
627
+ plt.xlabel("Test time step")
628
+ plt.ylabel("Value")
629
+ plt.legend()
630
+ plt.show()
631
+
632
+ # Predict the next value from your own three values
633
+ sample = np.array([40, 42, 44], dtype="float32").reshape(1, 3, 1)
634
+ print("RNN next value:", rnn.predict(sample, verbose=0)[0][0])
635
+ print("LSTM next value:", lstm.predict(sample, verbose=0)[0][0])''')
636
+ elif choice in ["14", "15"]:
637
+ pass # Code not available in pdf
638
+
639
+
640
+ if __name__ == "__main__":
641
+ app()
@@ -117,17 +117,17 @@ class UI:
117
117
  def location_found(self, file_path: str, line_number: int) -> None:
118
118
  _log.debug(f"Extracted location: {file_path}:{line_number}")
119
119
  self.console.print(
120
- f"[bugbee.info]\U0001F4CD Located error at[/] [bugbee.path]{file_path}:{line_number}[/]"
120
+ f"[bugbee.info]Located error at[/] [bugbee.path]{file_path}:{line_number}[/]"
121
121
  )
122
122
 
123
123
  def location_not_found(self) -> None:
124
124
  _log.debug("Could not determine source location from stderr.")
125
- self.console.print("[bugbee.warning]\u26A0 Could not determine the source location.[/]")
125
+ self.console.print("[bugbee.warning]Could not determine the source location.[/]")
126
126
 
127
127
  def cache_hit(self) -> None:
128
128
  _log.info("Cache hit – using previously generated response.")
129
129
  self.console.print(
130
- "[bugbee.info]\u26A1 Cache hit[/] [bugbee.muted]— reusing a previous analysis.[/]"
130
+ "[bugbee.info]Cache hit[/] [bugbee.muted]— reusing a previous analysis.[/]"
131
131
  )
132
132
 
133
133
  def cache_miss(self) -> None:
@@ -136,7 +136,7 @@ class UI:
136
136
 
137
137
  def cache_cleared(self) -> None:
138
138
  _log.info("Cache cleared.")
139
- self.console.print("[bugbee.success]\u2713 Cache cleared.[/]")
139
+ self.console.print("[bugbee.success]Cache cleared.[/]")
140
140
 
141
141
  def retrieval_stats(self, top_score: float, avg_score: float) -> None:
142
142
  _log.debug(f"Retrieval scores — top: {top_score}, avg: {avg_score}")
@@ -146,7 +146,7 @@ class UI:
146
146
  # ------------------------------------------------------------------
147
147
  def show_analysis(self, content: str) -> None:
148
148
  self.console.print(
149
- Panel(content, title="\U0001F916 AI Analysis", border_style="cyan", expand=True)
149
+ Panel(content, title="AI Analysis", border_style="cyan", expand=True)
150
150
  )
151
151
 
152
152
  # ------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bugbee
3
- Version: 0.1.0
3
+ Version: 0.1.3
4
4
  Summary: AI-powered CLI debugging and auto-repair tool with RAG, automatic patch generation, and validation.
5
5
  Author-email: Pranav Kurmadasu <kurmadasu.pranav@example.com>
6
6
  License-Expression: Apache-2.0
@@ -46,7 +46,7 @@ Requires-Dist: build>=1.2.1; extra == "dev"
46
46
  Requires-Dist: twine>=5.1.1; extra == "dev"
47
47
  Dynamic: license-file
48
48
 
49
- # 🐝 BugBee
49
+ # BugBee
50
50
 
51
51
  > **AI-powered CLI debugging assistant that automatically analyzes runtime errors, retrieves relevant documentation using RAG, and suggests or applies intelligent fixes.**
52
52
 
@@ -54,19 +54,19 @@ BugBee wraps your terminal commands, intercepts runtime failures, analyzes the t
54
54
 
55
55
  ---
56
56
 
57
- ## ✨ Features
58
-
59
- * 🤖 AI-powered debugging using **DeepSeek-R1**
60
- * 📚 Retrieval-Augmented Generation (RAG) using **ChromaDB**
61
- * 🔍 Automatic traceback parsing
62
- * 🧠 Context-aware code analysis
63
- * 📖 Retrieval from official framework documentation
64
- * ⚡ SHA256-based intelligent response caching
65
- * 🩹 Automatic code patch generation
66
- * ✅ Validation after applying fixes
67
- * 📊 Metrics collection
68
- * 💻 Cross-platform support (Windows, Linux, macOS)
69
- * 🚀 Packaged for PyPI
57
+ ## Features
58
+
59
+ * AI-powered debugging using **DeepSeek-R1**
60
+ * Retrieval-Augmented Generation (RAG) using **ChromaDB**
61
+ * Automatic traceback parsing
62
+ * Context-aware code analysis
63
+ * Retrieval from official framework documentation
64
+ * SHA256-based intelligent response caching
65
+ * Automatic code patch generation
66
+ * Validation after applying fixes
67
+ * Metrics collection
68
+ * Cross-platform support (Windows, Linux, macOS)
69
+ * Packaged for PyPI
70
70
 
71
71
  ---
72
72
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "bugbee"
7
- version = "0.1.0"
7
+ version = "0.1.3"
8
8
  description = "AI-powered CLI debugging and auto-repair tool with RAG, automatic patch generation, and validation."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11,<3.13"
@@ -1,258 +0,0 @@
1
- """CLI entry point for the BugBee package.
2
-
3
- The original monolithic implementation lived in ``cli-wrapper/src/cli_wrapper/main.py``.
4
- This version keeps the ``run`` command as a clean pipeline built on top of:
5
-
6
- * ``bugbee.parser.error_parser`` – stack‑trace extraction, sanitisation, hashing
7
- * ``bugbee.cache`` – JSON cache wrapper
8
- * ``bugbee.retrieval`` – ChromaDB document retrieval
9
- * ``bugbee.llm`` – LLM provider abstraction
10
- * ``bugbee.patcher`` – safe patch application with backup
11
- * ``bugbee.validator`` – post‑patch validation runner
12
- * ``bugbee.metrics`` – metrics collection and JSONL persistence
13
- * ``bugbee.ui`` – ALL logging and terminal presentation (spinners, colored
14
- status lines, panels, tables, confirmation prompts) lives here. This file
15
- contains no direct ``log.*`` or ``typer.echo`` calls — every user-facing
16
- message is routed through the ``ui`` singleton so the pipeline below reads
17
- as pure business logic.
18
-
19
- The command line mimics the historic behaviour while being fully testable,
20
- type‑safe, and pleasant to actually use.
21
- """
22
-
23
- from __future__ import annotations
24
-
25
- import re
26
- import sys
27
- from pathlib import Path
28
- from typing import List, Tuple
29
-
30
- import typer
31
-
32
- from bugbee import __version__
33
- from bugbee.config.settings import settings
34
- from bugbee.parser.error_parser import (
35
- extract_location,
36
- sanitize_error,
37
- error_hash,
38
- )
39
- from bugbee.cache import get_cache
40
- from bugbee.lazy import lazy_import
41
- from bugbee.ui import ui
42
- from bugbee.utils.logging import enable_console_logging
43
- # Heavy imports are lazy‑loaded inside the command handlers.
44
- # from bugbee.retrieval import get_related_docs
45
- # from bugbee.llm import default_provider
46
- # from bugbee.metrics import MetricsCollector
47
- # from bugbee.patcher import apply_patch
48
- # from bugbee.validator import run_validation
49
-
50
- app = typer.Typer(
51
- help="BugBee – AI‑powered CLI debugger and auto‑repair tool",
52
- rich_markup_mode="rich",
53
- )
54
-
55
-
56
- @app.callback()
57
- def main(
58
- verbose: bool = typer.Option(
59
- False,
60
- "--verbose",
61
- "-v",
62
- help="Also print raw, timestamped log lines to the console (in addition to the normal UI).",
63
- ),
64
- ) -> None:
65
- """BugBee – AI‑powered CLI debugger and auto‑repair tool."""
66
- if verbose:
67
- enable_console_logging()
68
-
69
-
70
- def _run_subprocess(command: List[str]) -> Tuple[int, str, str]:
71
- """Execute *command* and return ``(returncode, stdout, stderr)``.
72
-
73
- The function deliberately forwards stdout directly to the console (as the
74
- original tool did) and captures stderr for analysis.
75
- """
76
- import subprocess
77
-
78
- ui.running_command(command)
79
- proc = subprocess.Popen(
80
- command,
81
- stdout=subprocess.PIPE,
82
- stderr=subprocess.PIPE,
83
- text=True,
84
- )
85
- out, err = proc.communicate()
86
- # Echo stdout to the console to preserve original behaviour.
87
- if out:
88
- sys.stdout.write(out)
89
- return proc.returncode, out, err
90
-
91
-
92
- def _parse_llm_output(output: str) -> Tuple[str, str, str]:
93
- """Extract ``error_line`` and ``fix`` from the LLM ``output``.
94
-
95
- The original script expected XML‑like tags <ERROR_LINE> and <FIX>. We keep
96
- that contract for backward compatibility.
97
- """
98
- error_line_match = re.search(r"<ERROR_LINE>(.*?)</ERROR_LINE>", output, re.DOTALL)
99
- fix_match = re.search(r"<FIX>(.*?)</FIX>", output, re.DOTALL)
100
- error_line = error_line_match.group(1).strip() if error_line_match else ""
101
- fix = fix_match.group(1).strip() if fix_match else ""
102
- # The full content without the tags is also useful for reporting.
103
- content = output.strip()
104
- return content, error_line, fix
105
-
106
-
107
- @app.command()
108
- def run(
109
- command: List[str] = typer.Argument(..., help="Command to execute and debug"),
110
- yes: bool = typer.Option(False, "--yes", "-y", help="Auto‑accept fix without prompting"),
111
- ) -> None:
112
- """Execute *command*, analyse failures and optionally apply an auto‑fix."""
113
- # Lazy imports of heavy modules – they are only loaded when this command runs.
114
- MetricsCollector = lazy_import('bugbee.metrics').MetricsCollector
115
- get_related_docs = lazy_import('bugbee.retrieval').get_related_docs
116
- default_provider = lazy_import('bugbee.llm').default_provider
117
- apply_patch = lazy_import('bugbee.patcher').apply_patch
118
- run_validation = lazy_import('bugbee.validator').run_validation
119
-
120
- metrics = MetricsCollector()
121
- metrics.update("command", " ".join(command))
122
-
123
- returncode, _, stderr = _run_subprocess(command)
124
- if returncode == 0:
125
- ui.command_succeeded()
126
- metrics.write()
127
- raise typer.Exit(code=0)
128
-
129
- ui.command_failed()
130
-
131
- # 1️⃣ Extract location info (file + line) – may be ``None``.
132
- file_path, line_number = extract_location(stderr, command)
133
- if file_path and line_number:
134
- ui.location_found(file_path, line_number)
135
- else:
136
- ui.location_not_found()
137
-
138
- # 2️⃣ Sanitize error and compute hash for caching.
139
- cleaned = sanitize_error(stderr)
140
- err_hash = error_hash(cleaned)
141
- cache_obj = get_cache()
142
- cached = cache_obj.get(err_hash)
143
- if cached:
144
- ui.cache_hit()
145
- llm_content, error_line, fix = cached
146
- else:
147
- ui.cache_miss()
148
- # Retrieve relevant docs.
149
- with ui.spinner("Retrieving relevant documentation…"):
150
- docs, top_score, avg_score = get_related_docs(stderr)
151
- metrics.update("retrieval.top_score", top_score)
152
- metrics.update("retrieval.avg_score", avg_score)
153
- ui.retrieval_stats(top_score, avg_score)
154
-
155
- # Very simple prompt composition.
156
- prompt = (
157
- f"Error message:\n{stderr}\n\n"
158
- f"Relevant documentation:\n{docs}\n\n"
159
- "Provide an XML-like response containing <ERROR_LINE> and <FIX>."
160
- )
161
- with ui.spinner("Consulting the model for a fix…"):
162
- llm_resp = default_provider().generate(prompt)
163
-
164
- llm_content, error_line, fix = _parse_llm_output(llm_resp)
165
- cache_obj.set(err_hash, (llm_content, error_line, fix))
166
-
167
- # Show the LLM analysis to the user.
168
- ui.show_analysis(llm_content)
169
-
170
- # Auto‑fix handling.
171
- performed = False
172
- accept = ui.confirm_fix(yes)
173
- if accept and file_path:
174
- ui.applying_patch(file_path, line_number)
175
- try:
176
- success = apply_patch(file_path, line_number, error_line, fix + "\n", create_backup=True)
177
- if success:
178
- performed = True
179
- metrics.update("autofix.patch_applied", True)
180
- metrics.update("autofix.files_modified", 1)
181
- ui.patch_applied()
182
- # Run validation to ensure fix works.
183
- with ui.spinner("Validating the fix…"):
184
- validation_ok = run_validation(command, cwd=Path.cwd())
185
- if validation_ok:
186
- metrics.update("validation.passed", True)
187
- ui.validation_passed()
188
- else:
189
- ui.validation_failed()
190
- else:
191
- ui.patch_not_applied()
192
- except Exception as exc:
193
- ui.patch_error(exc)
194
- else:
195
- ui.auto_fix_declined()
196
-
197
- # Record final metrics.
198
- metrics.update("diagnosis.error_line_found", bool(error_line))
199
- metrics.update("diagnosis.fix_generated", bool(fix))
200
- metrics.write()
201
-
202
- ui.summary(fix_applied=performed, returncode=returncode)
203
-
204
- # Exit with the original return code if no successful fix was applied.
205
- if performed:
206
- raise typer.Exit(code=0)
207
- else:
208
- raise typer.Exit(code=returncode)
209
-
210
-
211
- @app.command()
212
- def analyze(
213
- command: List[str] = typer.Argument(..., help="Command to analyze without applying a fix"),
214
- ) -> None:
215
- """Run *command* and display LLM analysis but do not prompt for auto‑fix."""
216
- # Reuse ``run`` logic but force ``yes=False`` and skip patch step.
217
- run(command=command, yes=False)
218
-
219
-
220
- @app.command()
221
- def version() -> None:
222
- """Print the installed BugBee version."""
223
- ui.version(__version__)
224
-
225
-
226
- @app.command()
227
- def cache_clear() -> None:
228
- """Remove the local cache file (``bugbee.json``)."""
229
- get_cache().clear()
230
- ui.cache_cleared()
231
-
232
-
233
- @app.command()
234
- def config_show() -> None:
235
- """Display the effective configuration (environment variables, .env, defaults)."""
236
- ui.config_table(settings.dict())
237
-
238
-
239
- @app.command()
240
- def doctor() -> None:
241
- """Run a quick health‑check of required dependencies and connectivity."""
242
- ui.doctor_start()
243
- missing = []
244
- try:
245
- import chromadb # noqa: F401
246
- except Exception:
247
- missing.append("chromadb")
248
- try:
249
- import langchain # noqa: F401
250
- except Exception:
251
- missing.append("langchain")
252
- ui.doctor_result(missing)
253
- if missing:
254
- raise typer.Exit(code=1)
255
-
256
-
257
- if __name__ == "__main__":
258
- app()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes