bugbee 0.1.2__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.
- {bugbee-0.1.2 → bugbee-0.1.3}/PKG-INFO +1 -1
- bugbee-0.1.3/bugbee/cli/main.py +641 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee.egg-info/PKG-INFO +1 -1
- {bugbee-0.1.2 → bugbee-0.1.3}/pyproject.toml +1 -1
- bugbee-0.1.2/bugbee/cli/main.py +0 -258
- {bugbee-0.1.2 → bugbee-0.1.3}/LICENSE +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/README.md +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/__init__.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/cache/__init__.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/cli/__init__.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/config/settings.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/lazy.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/legacy.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/llm/__init__.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/metrics/__init__.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/parser/__init__.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/parser/error_parser.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/patcher/__init__.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/retrieval/__init__.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/ui.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/utils/logging.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee/validator/__init__.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee.egg-info/SOURCES.txt +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee.egg-info/dependency_links.txt +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee.egg-info/entry_points.txt +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee.egg-info/requires.txt +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/bugbee.egg-info/top_level.txt +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/setup.cfg +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/tests/test.py +0 -0
- {bugbee-0.1.2 → bugbee-0.1.3}/tests/test2.py +0 -0
- {bugbee-0.1.2 → 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.
|
|
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
|
|
@@ -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()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bugbee
|
|
3
|
-
Version: 0.1.
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "bugbee"
|
|
7
|
-
version = "0.1.
|
|
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"
|
bugbee-0.1.2/bugbee/cli/main.py
DELETED
|
@@ -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
|
|
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
|