centicodehelp 0.2.0__py3-none-any.whl
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.
- centicodehelp/__init__.py +3 -0
- centicodehelp/ai/llm_client.py +150 -0
- centicodehelp/ai/prompts.py +63 -0
- centicodehelp/ai/providers.py +195 -0
- centicodehelp/ai/reviewer.py +139 -0
- centicodehelp/analysis/binary_analyzer.py +124 -0
- centicodehelp/analysis/detector.py +80 -0
- centicodehelp/analysis/parser.py +41 -0
- centicodehelp/analysis/static_analyzer.py +194 -0
- centicodehelp/cli/banner.py +60 -0
- centicodehelp/cli/commands/ai.py +128 -0
- centicodehelp/cli/commands/analyze.py +389 -0
- centicodehelp/cli/commands/check.py +91 -0
- centicodehelp/cli/commands/config.py +67 -0
- centicodehelp/cli/commands/fix.py +170 -0
- centicodehelp/cli/commands/providers.py +81 -0
- centicodehelp/cli/commands/scan.py +129 -0
- centicodehelp/cli/commands/setup.py +93 -0
- centicodehelp/cli/commands/update.py +51 -0
- centicodehelp/cli/main.py +141 -0
- centicodehelp/cli/theme.py +50 -0
- centicodehelp/core/config.py +58 -0
- centicodehelp/core/models.py +31 -0
- centicodehelp/fixer/applier.py +23 -0
- centicodehelp/fixer/patcher.py +42 -0
- centicodehelp-0.2.0.dist-info/METADATA +79 -0
- centicodehelp-0.2.0.dist-info/RECORD +30 -0
- centicodehelp-0.2.0.dist-info/WHEEL +5 -0
- centicodehelp-0.2.0.dist-info/entry_points.txt +4 -0
- centicodehelp-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
import click
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn
|
|
9
|
+
from rich.syntax import Syntax
|
|
10
|
+
|
|
11
|
+
from centicodehelp.core.models import Finding, Severity, AnalysisResult
|
|
12
|
+
from centicodehelp.core.config import load_config
|
|
13
|
+
from centicodehelp.analysis.detector import detect_language, is_binary_file
|
|
14
|
+
from centicodehelp.analysis.static_analyzer import StaticAnalyzer
|
|
15
|
+
from centicodehelp.analysis.binary_analyzer import BinaryAnalyzer
|
|
16
|
+
from centicodehelp.ai.llm_client import LLMClient
|
|
17
|
+
from centicodehelp.ai.reviewer import AIReviewer, AIReviewResult
|
|
18
|
+
from centicodehelp.ai.prompts import FIX_PROMPT_TEMPLATE
|
|
19
|
+
from centicodehelp.fixer.patcher import PatchGenerator
|
|
20
|
+
from centicodehelp.fixer.applier import PatchApplier
|
|
21
|
+
from centicodehelp.cli.theme import (
|
|
22
|
+
console,
|
|
23
|
+
COLOR_PURPLE,
|
|
24
|
+
COLOR_BLUE,
|
|
25
|
+
COLOR_INDIGO,
|
|
26
|
+
COLOR_CRITICAL,
|
|
27
|
+
COLOR_HIGH,
|
|
28
|
+
COLOR_MEDIUM,
|
|
29
|
+
COLOR_LOW,
|
|
30
|
+
COLOR_INFO,
|
|
31
|
+
COLOR_SUCCESS,
|
|
32
|
+
COLOR_ERROR,
|
|
33
|
+
COLOR_TEXT,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Dark Theme Severity Styles
|
|
37
|
+
SEVERITY_STYLES = {
|
|
38
|
+
Severity.CRITICAL: (COLOR_CRITICAL, "🔴 CRITICAL"),
|
|
39
|
+
Severity.HIGH: (f"bold {COLOR_HIGH}", "🟠HIGH"),
|
|
40
|
+
Severity.MEDIUM: (f"bold {COLOR_MEDIUM}", "🟡 MEDIUM"),
|
|
41
|
+
Severity.LOW: (f"{COLOR_LOW}", "🔵 LOW"),
|
|
42
|
+
Severity.INFO: (f"{COLOR_INFO}", "⚪ INFO"),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@click.command(name="analyze", help="Quellcode oder Binärdateien mit statischen Regeln und KI analysieren.")
|
|
46
|
+
@click.argument("target", type=click.Path(exists=False), required=False)
|
|
47
|
+
@click.option("-f", "--file", "file_opt", type=click.Path(exists=False), help="Zu analysierende Datei.")
|
|
48
|
+
@click.option("-d", "--dir", "dir_opt", type=click.Path(exists=False), help="Zu analysierendes Verzeichnis.")
|
|
49
|
+
@click.option("--lang", type=str, help="Programmiersprache erzwingen (z. B. python, cpp, csharp, java).")
|
|
50
|
+
@click.option("--ai", is_flag=True, default=False, help="KI-gestützte Tiefenanalyse aktivieren.")
|
|
51
|
+
@click.option("--fix", is_flag=True, default=False, help="Gefundene Probleme automatisch mit KI beheben.")
|
|
52
|
+
@click.option("--provider", type=str, help="KI-Anbieter (openai, anthropic, ollama, deepseek, groq, etc.).")
|
|
53
|
+
@click.option("--model", type=str, help="Modellname (z. B. gpt-4o, claude-3-5-sonnet, llama3.2).")
|
|
54
|
+
@click.option("--output", type=click.Choice(["terminal", "json", "diff"], case_sensitive=False), default="terminal", help="Ausgabeformat.")
|
|
55
|
+
@click.option("--yes", "-y", is_flag=True, default=False, help="Korrekturen ohne manuelle Bestätigung anwenden.")
|
|
56
|
+
@click.option("--debug", is_flag=True, default=False, help="Debug-Informationen (Roh-Antwort der KI, Tokens, Latenz) anzeigen.")
|
|
57
|
+
def analyze_cmd(
|
|
58
|
+
target: Optional[str],
|
|
59
|
+
file_opt: Optional[str],
|
|
60
|
+
dir_opt: Optional[str],
|
|
61
|
+
lang: Optional[str],
|
|
62
|
+
ai: bool,
|
|
63
|
+
fix: bool,
|
|
64
|
+
provider: Optional[str],
|
|
65
|
+
model: Optional[str],
|
|
66
|
+
output: str,
|
|
67
|
+
yes: bool,
|
|
68
|
+
debug: bool,
|
|
69
|
+
):
|
|
70
|
+
"""Hauptbefehl zur Code- und Binäranalyse mit modernem Centi09-Dark-Theme."""
|
|
71
|
+
target_str = file_opt or dir_opt or target or "."
|
|
72
|
+
target_path = Path(target_str)
|
|
73
|
+
|
|
74
|
+
if not target_path.exists():
|
|
75
|
+
console.print(f"\n[bold {COLOR_ERROR}]⌠Ziel nicht gefunden:[/bold {COLOR_ERROR}] [white]{target_path}[/white]\n")
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
# Sammle alle Dateien
|
|
79
|
+
files_to_analyze: List[Path] = []
|
|
80
|
+
if target_path.is_file():
|
|
81
|
+
files_to_analyze.append(target_path)
|
|
82
|
+
else:
|
|
83
|
+
for root, dirs, files in os.walk(target_path):
|
|
84
|
+
dirs[:] = [
|
|
85
|
+
d for d in dirs
|
|
86
|
+
if not d.startswith(".")
|
|
87
|
+
and d not in ("node_modules", "target", "build", "dist", "__pycache__", "venv", ".venv", "env")
|
|
88
|
+
]
|
|
89
|
+
for f in files:
|
|
90
|
+
files_to_analyze.append(Path(root) / f)
|
|
91
|
+
|
|
92
|
+
if not files_to_analyze:
|
|
93
|
+
console.print(f"\n[bold {COLOR_MEDIUM}]âš ï¸ Keine analysierbaren Dateien im Verzeichnis gefunden.[/bold {COLOR_MEDIUM}]\n")
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
config = load_config()
|
|
97
|
+
selected_provider = provider or config.default_provider
|
|
98
|
+
selected_model = model or config.default_model
|
|
99
|
+
|
|
100
|
+
# Info Header
|
|
101
|
+
mode_text = "[bold #38BDF8]Statische Analyse[/bold #38BDF8]"
|
|
102
|
+
if ai and fix:
|
|
103
|
+
mode_text = f"[bold {COLOR_PURPLE}]Statisch + KI-Audit & Auto-Fix[/bold {COLOR_PURPLE}] ({selected_provider}/{selected_model})"
|
|
104
|
+
elif ai:
|
|
105
|
+
mode_text = f"[bold {COLOR_PURPLE}]Statisch + KI-Tiefenanalyse[/bold {COLOR_PURPLE}] ({selected_provider}/{selected_model})"
|
|
106
|
+
elif fix:
|
|
107
|
+
mode_text = f"[bold {COLOR_PURPLE}]Statisch + Auto-Fix[/bold {COLOR_PURPLE}] ({selected_provider}/{selected_model})"
|
|
108
|
+
|
|
109
|
+
header_panel = Panel(
|
|
110
|
+
f"[bold]Ziel:[/bold] [yellow]{target_path}[/yellow] ({len(files_to_analyze)} Datei(en))\n"
|
|
111
|
+
f"[bold]Modus:[/bold] {mode_text}",
|
|
112
|
+
title=f"[bold {COLOR_PURPLE}]âš¡ centicodehelp ANALYSE[/bold {COLOR_PURPLE}]",
|
|
113
|
+
border_style=COLOR_INDIGO,
|
|
114
|
+
expand=False,
|
|
115
|
+
)
|
|
116
|
+
console.print()
|
|
117
|
+
console.print(header_panel)
|
|
118
|
+
console.print()
|
|
119
|
+
|
|
120
|
+
static_analyzer = StaticAnalyzer()
|
|
121
|
+
binary_analyzer = BinaryAnalyzer()
|
|
122
|
+
llm_client = None
|
|
123
|
+
ai_reviewer = None
|
|
124
|
+
|
|
125
|
+
if ai or fix:
|
|
126
|
+
try:
|
|
127
|
+
llm_client = LLMClient(provider=selected_provider, model=selected_model)
|
|
128
|
+
ai_reviewer = AIReviewer(llm_client)
|
|
129
|
+
except Exception as e:
|
|
130
|
+
console.print(f"[bold {COLOR_ERROR}]⌠KI-Initialisierungsfehler:[/bold {COLOR_ERROR}] {e}")
|
|
131
|
+
if ai:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
total_findings: List[Finding] = []
|
|
135
|
+
|
|
136
|
+
# Fortschrittsanzeige bei mehreren Dateien
|
|
137
|
+
if len(files_to_analyze) > 1:
|
|
138
|
+
with Progress(
|
|
139
|
+
SpinnerColumn(spinner_name="dots", style=COLOR_PURPLE),
|
|
140
|
+
TextColumn("[bold #818CF8]{task.description}"),
|
|
141
|
+
BarColumn(complete_style=COLOR_BLUE, finished_style=COLOR_SUCCESS),
|
|
142
|
+
TextColumn("[dim]{task.completed}/{task.total} Dateien[/dim]"),
|
|
143
|
+
console=console,
|
|
144
|
+
) as progress:
|
|
145
|
+
scan_task = progress.add_task("Dateien scannen...", total=len(files_to_analyze))
|
|
146
|
+
for f_path in files_to_analyze:
|
|
147
|
+
progress.update(scan_task, description=f"Analysiere [cyan]{f_path.name}[/cyan]...")
|
|
148
|
+
file_lang = lang or detect_language(f_path)
|
|
149
|
+
if file_lang:
|
|
150
|
+
if file_lang == "binary":
|
|
151
|
+
findings = binary_analyzer.analyze_binary(f_path)
|
|
152
|
+
else:
|
|
153
|
+
findings = static_analyzer.analyze_file(f_path, file_lang)
|
|
154
|
+
total_findings.extend(findings)
|
|
155
|
+
progress.advance(scan_task)
|
|
156
|
+
else:
|
|
157
|
+
# Einzelne Datei
|
|
158
|
+
f_path = files_to_analyze[0]
|
|
159
|
+
file_lang = lang or detect_language(f_path)
|
|
160
|
+
if file_lang:
|
|
161
|
+
if file_lang == "binary":
|
|
162
|
+
findings = binary_analyzer.analyze_binary(f_path)
|
|
163
|
+
else:
|
|
164
|
+
findings = static_analyzer.analyze_file(f_path, file_lang)
|
|
165
|
+
total_findings.extend(findings)
|
|
166
|
+
|
|
167
|
+
# 1. Statische Findings anzeigen
|
|
168
|
+
_render_results(total_findings, output, title_prefix="STATISCHE CODE-ANALYSE")
|
|
169
|
+
|
|
170
|
+
# 2. KI-Tiefenanalyse (falls aktiviert)
|
|
171
|
+
ai_review_results: List[AIReviewResult] = []
|
|
172
|
+
if ai and ai_reviewer:
|
|
173
|
+
target_files = files_to_analyze[:3] # KI-Review für Zieldateien
|
|
174
|
+
for target_file in target_files:
|
|
175
|
+
f_lang = lang or detect_language(target_file)
|
|
176
|
+
if not f_lang or f_lang == "binary":
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
with console.status(
|
|
180
|
+
f"[bold {COLOR_PURPLE}]🤖 Analysiere {target_file.name} mit {selected_provider}/{selected_model}...[/bold {COLOR_PURPLE}]",
|
|
181
|
+
spinner="dots12",
|
|
182
|
+
):
|
|
183
|
+
review_res = ai_reviewer.review_with_details(target_file, f_lang, total_findings)
|
|
184
|
+
ai_review_results.append(review_res)
|
|
185
|
+
|
|
186
|
+
# Rendere KI-Tiefenanalyse-Abschnitt
|
|
187
|
+
_render_ai_section(ai_review_results, selected_provider, selected_model, debug=debug)
|
|
188
|
+
|
|
189
|
+
# Neue KI-Findings mit Gesamtergebnis verschmelzen
|
|
190
|
+
for r in ai_review_results:
|
|
191
|
+
if r.findings:
|
|
192
|
+
total_findings = ai_reviewer._merge_findings(total_findings, r.findings)
|
|
193
|
+
|
|
194
|
+
# Automatischer Fix falls angefordert
|
|
195
|
+
if fix and total_findings and llm_client:
|
|
196
|
+
for f_path in files_to_analyze:
|
|
197
|
+
f_lang = lang or detect_language(f_path)
|
|
198
|
+
if f_lang and f_lang != "binary":
|
|
199
|
+
file_findings = [f for f in total_findings if Path(f.file_path).name == f_path.name]
|
|
200
|
+
if file_findings:
|
|
201
|
+
_handle_fix(f_path, f_lang, file_findings, llm_client, yes, selected_provider, selected_model)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _render_results(findings: List[Finding], output_format: str, title_prefix: str = "GEFUNDENE PROBLEME"):
|
|
205
|
+
if output_format == "json":
|
|
206
|
+
import json
|
|
207
|
+
out_list = [f.model_dump() for f in findings]
|
|
208
|
+
console.print(json.dumps(out_list, indent=2))
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
if not findings:
|
|
212
|
+
clean_panel = Panel(
|
|
213
|
+
f"[bold {COLOR_SUCCESS}]✅ Keine statischen Probleme gefunden![/bold {COLOR_SUCCESS}]\n"
|
|
214
|
+
f"[dim]Der Code entspricht allen statischen Sicherheits- und Qualitätsregeln.[/dim]",
|
|
215
|
+
border_style=COLOR_SUCCESS,
|
|
216
|
+
expand=False,
|
|
217
|
+
)
|
|
218
|
+
console.print(clean_panel)
|
|
219
|
+
console.print()
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
table = Table(
|
|
223
|
+
title=f"\n[bold {COLOR_PURPLE}]🔠{title_prefix}[/bold {COLOR_PURPLE}] ({len(findings)} Finding(s))",
|
|
224
|
+
show_header=True,
|
|
225
|
+
header_style=f"bold {COLOR_BLUE}",
|
|
226
|
+
border_style=COLOR_INDIGO,
|
|
227
|
+
expand=True,
|
|
228
|
+
)
|
|
229
|
+
table.add_column("Schweregrad", width=14, justify="left")
|
|
230
|
+
table.add_column("Datei:Zeile", style="cyan", width=26)
|
|
231
|
+
table.add_column("Regel", style="bold #818CF8", width=14)
|
|
232
|
+
table.add_column("Beschreibung & Fix-Empfehlung", style=COLOR_TEXT)
|
|
233
|
+
|
|
234
|
+
for f in findings:
|
|
235
|
+
style_color, badge = SEVERITY_STYLES.get(f.severity, ("white", str(f.severity.value)))
|
|
236
|
+
loc = f"{Path(f.file_path).name}:{f.line if f.line is not None else '-'}"
|
|
237
|
+
desc_text = f"[{style_color}]{f.description}[/{style_color}]"
|
|
238
|
+
if f.suggested_fix:
|
|
239
|
+
desc_text += f"\n[dim italic]💡 Fix: {f.suggested_fix}[/dim italic]"
|
|
240
|
+
|
|
241
|
+
table.add_row(
|
|
242
|
+
f"[{style_color}]{badge}[/{style_color}]",
|
|
243
|
+
loc,
|
|
244
|
+
f.rule_id,
|
|
245
|
+
desc_text,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
console.print(table)
|
|
249
|
+
console.print()
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _render_ai_section(
|
|
253
|
+
ai_results: List[AIReviewResult],
|
|
254
|
+
provider: str,
|
|
255
|
+
model: str,
|
|
256
|
+
debug: bool = False,
|
|
257
|
+
):
|
|
258
|
+
"""Rendert den sichtbaren Abschnitt für die KI-Tiefenanalyse."""
|
|
259
|
+
console.print()
|
|
260
|
+
section_title = f"🤖 KI-TIEFENANALYSE ({provider}/{model})"
|
|
261
|
+
|
|
262
|
+
if not ai_results:
|
|
263
|
+
console.print(
|
|
264
|
+
Panel(
|
|
265
|
+
f"[bold {COLOR_ERROR}]⌠Keine Antwort von der KI-Engine ({provider}/{model}) erhalten.[/bold {COLOR_ERROR}]",
|
|
266
|
+
title=f"[bold {COLOR_PURPLE}]{section_title}[/bold {COLOR_PURPLE}]",
|
|
267
|
+
border_style=COLOR_ERROR,
|
|
268
|
+
expand=False,
|
|
269
|
+
)
|
|
270
|
+
)
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
for res in ai_results:
|
|
274
|
+
telemetry = (
|
|
275
|
+
f"[dim]Dauer: [bold cyan]{res.duration_seconds}s[/bold cyan] • "
|
|
276
|
+
f"Tokens: [bold cyan]{res.total_tokens}[/bold cyan] (Prompt: {res.prompt_tokens}, Completion: {res.completion_tokens}) • "
|
|
277
|
+
f"Modell: [bold cyan]{res.model}[/bold cyan][/dim]"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
content_lines = []
|
|
281
|
+
if res.error:
|
|
282
|
+
content_lines.append(f"[bold {COLOR_ERROR}]⌠KI-Fehler:[/bold {COLOR_ERROR}] {res.error}")
|
|
283
|
+
else:
|
|
284
|
+
if res.summary:
|
|
285
|
+
content_lines.append(f"[bold white]Zusammenfassung:[/bold white] {res.summary}")
|
|
286
|
+
|
|
287
|
+
if res.findings:
|
|
288
|
+
content_lines.append(f"\n[bold {COLOR_PURPLE}]Gefundene KI-Hinweise & Empfehlungen ({len(res.findings)}):[/bold {COLOR_PURPLE}]")
|
|
289
|
+
for idx, f in enumerate(res.findings, 1):
|
|
290
|
+
style_color, badge = SEVERITY_STYLES.get(f.severity, ("white", f.severity.value))
|
|
291
|
+
line_str = f"Zeile {f.line}" if f.line else "Allgemein"
|
|
292
|
+
content_lines.append(f" {idx}. [{style_color}]{badge}[/{style_color}] [cyan]({line_str})[/cyan]: {f.description}")
|
|
293
|
+
if f.suggested_fix:
|
|
294
|
+
content_lines.append(f" [dim italic]💡 Empfehlung: {f.suggested_fix}[/dim italic]")
|
|
295
|
+
else:
|
|
296
|
+
content_lines.append(f"\n[bold {COLOR_SUCCESS}]✅ Keine zusätzlichen Probleme durch KI gefunden.[/bold {COLOR_SUCCESS}]")
|
|
297
|
+
|
|
298
|
+
content_lines.append(f"\n{telemetry}")
|
|
299
|
+
|
|
300
|
+
console.print(
|
|
301
|
+
Panel(
|
|
302
|
+
"\n".join(content_lines),
|
|
303
|
+
title=f"[bold {COLOR_PURPLE}]{section_title}[/bold {COLOR_PURPLE}]",
|
|
304
|
+
border_style=COLOR_INDIGO,
|
|
305
|
+
expand=False,
|
|
306
|
+
)
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# Debug Panel (falls --debug aktiviert oder Rohantwort gewünscht)
|
|
310
|
+
if debug and res.raw_response:
|
|
311
|
+
syntax_raw = Syntax(res.raw_response, "json", theme="monokai", line_numbers=True)
|
|
312
|
+
console.print(
|
|
313
|
+
Panel(
|
|
314
|
+
syntax_raw,
|
|
315
|
+
title=f"[bold #fb923c]ðŸ› ï¸ DEBUG: Rohtext-Antwort von {provider}/{model}[/bold #fb923c]",
|
|
316
|
+
border_style="#fb923c",
|
|
317
|
+
expand=False,
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
console.print()
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _handle_fix(
|
|
325
|
+
file_path: Path,
|
|
326
|
+
language: str,
|
|
327
|
+
findings: List[Finding],
|
|
328
|
+
llm_client: LLMClient,
|
|
329
|
+
auto_confirm: bool,
|
|
330
|
+
provider: str,
|
|
331
|
+
model: str,
|
|
332
|
+
):
|
|
333
|
+
try:
|
|
334
|
+
source_code = file_path.read_text(encoding="utf-8", errors="replace")
|
|
335
|
+
except Exception as e:
|
|
336
|
+
console.print(f"[bold {COLOR_ERROR}]⌠Fehler beim Lesen von {file_path.name}:[/bold {COLOR_ERROR}] {e}")
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
findings_text = "\n".join(
|
|
340
|
+
[f"- Line {f.line or '?'}: {f.description} (Suggested fix: {f.suggested_fix or 'N/A'})" for f in findings]
|
|
341
|
+
)
|
|
342
|
+
fix_prompt = FIX_PROMPT_TEMPLATE.format(
|
|
343
|
+
file_path=str(file_path),
|
|
344
|
+
language=language,
|
|
345
|
+
findings_text=findings_text,
|
|
346
|
+
source_code=source_code,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
clean_code = ""
|
|
350
|
+
with console.status(
|
|
351
|
+
f"[bold {COLOR_PURPLE}]Generiere KI-Korrektur mit {provider}/{model}...[/bold {COLOR_PURPLE}]",
|
|
352
|
+
spinner="dots12",
|
|
353
|
+
):
|
|
354
|
+
try:
|
|
355
|
+
raw_fixed = llm_client.complete(
|
|
356
|
+
fix_prompt,
|
|
357
|
+
system="Du bist ein Sicherheitsexperte. Ersetze eval() IMMER durch ast.literal_eval(). Entferne alle print() Debug-Meldungen. Gib NUR den korrigierten Code zurück."
|
|
358
|
+
)
|
|
359
|
+
clean_code = PatchGenerator.extract_code_from_markdown(raw_fixed)
|
|
360
|
+
except Exception as e:
|
|
361
|
+
console.print(f"\n[bold {COLOR_ERROR}]⌠KI-Fix Fehler:[/bold {COLOR_ERROR}] {e}\n")
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
diff = PatchGenerator.generate_diff(source_code, clean_code, file_path=file_path.name)
|
|
365
|
+
if not diff:
|
|
366
|
+
console.print(f"[dim]Keine Diff-Änderung von der KI für {file_path.name} vorgeschlagen.[/dim]")
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
syntax_diff = Syntax(diff, "diff", theme="monokai", line_numbers=True)
|
|
370
|
+
console.print(
|
|
371
|
+
Panel(
|
|
372
|
+
syntax_diff,
|
|
373
|
+
title=f"[bold {COLOR_SUCCESS}]Vorgeschlagener Patch für {file_path.name}[/bold {COLOR_SUCCESS}]",
|
|
374
|
+
border_style=COLOR_INDIGO,
|
|
375
|
+
expand=False,
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
should_apply = auto_confirm
|
|
380
|
+
if not auto_confirm:
|
|
381
|
+
should_apply = click.confirm(f"Diesen Patch auf {file_path.name} anwenden?", default=True)
|
|
382
|
+
|
|
383
|
+
if should_apply:
|
|
384
|
+
applier = PatchApplier(backup=True)
|
|
385
|
+
if applier.apply_fix(file_path, clean_code):
|
|
386
|
+
console.print(f"\n[bold {COLOR_SUCCESS}]✅ Patch erfolgreich angewendet![/bold {COLOR_SUCCESS}] [dim](Backup: {file_path.name}.bak)[/dim]\n")
|
|
387
|
+
else:
|
|
388
|
+
console.print(f"\n[bold {COLOR_ERROR}]⌠Fehler beim Schreiben des Patches auf {file_path.name}[/bold {COLOR_ERROR}]\n")
|
|
389
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import click
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
|
|
6
|
+
from centicodehelp.analysis.detector import detect_language
|
|
7
|
+
from centicodehelp.analysis.static_analyzer import StaticAnalyzer
|
|
8
|
+
from centicodehelp.analysis.binary_analyzer import BinaryAnalyzer
|
|
9
|
+
from centicodehelp.core.models import Finding, Severity
|
|
10
|
+
from centicodehelp.cli.theme import (
|
|
11
|
+
console,
|
|
12
|
+
COLOR_PURPLE,
|
|
13
|
+
COLOR_BLUE,
|
|
14
|
+
COLOR_INDIGO,
|
|
15
|
+
COLOR_SUCCESS,
|
|
16
|
+
COLOR_ERROR,
|
|
17
|
+
COLOR_CRITICAL,
|
|
18
|
+
COLOR_HIGH,
|
|
19
|
+
COLOR_MEDIUM,
|
|
20
|
+
COLOR_LOW,
|
|
21
|
+
COLOR_INFO,
|
|
22
|
+
COLOR_TEXT,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
SEVERITY_STYLES = {
|
|
26
|
+
Severity.CRITICAL: (COLOR_CRITICAL, "🔴 CRITICAL"),
|
|
27
|
+
Severity.HIGH: (f"bold {COLOR_HIGH}", "🟠HIGH"),
|
|
28
|
+
Severity.MEDIUM: (f"bold {COLOR_MEDIUM}", "🟡 MEDIUM"),
|
|
29
|
+
Severity.LOW: (f"{COLOR_LOW}", "🔵 LOW"),
|
|
30
|
+
Severity.INFO: (f"{COLOR_INFO}", "⚪ INFO"),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@click.command(name="check", help="Schnellcheck einer einzelnen Datei (statisch).")
|
|
34
|
+
@click.argument("file_path", type=click.Path(exists=False), required=True)
|
|
35
|
+
def check_cmd(file_path: str):
|
|
36
|
+
"""
|
|
37
|
+
ct check <datei> - Schnelle, kinderleichte Dateiüberprüfung.
|
|
38
|
+
"""
|
|
39
|
+
path = Path(file_path)
|
|
40
|
+
if not path.exists() or not path.is_file():
|
|
41
|
+
console.print(f"\n[bold {COLOR_ERROR}]⌠Datei nicht gefunden:[/bold {COLOR_ERROR}] [white]{file_path}[/white]\n")
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
lang = detect_language(path)
|
|
45
|
+
if not lang:
|
|
46
|
+
console.print(f"\n[bold {COLOR_MEDIUM}]âš ï¸ Dateityp von {path.name} wird nicht unterstützt.[/bold {COLOR_MEDIUM}]\n")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
static_analyzer = StaticAnalyzer()
|
|
50
|
+
binary_analyzer = BinaryAnalyzer()
|
|
51
|
+
|
|
52
|
+
with console.status(f"[bold {COLOR_PURPLE}]Prüfe {path.name}...[/bold {COLOR_PURPLE}]", spinner="dots"):
|
|
53
|
+
if lang == "binary":
|
|
54
|
+
findings = binary_analyzer.analyze_binary(path)
|
|
55
|
+
else:
|
|
56
|
+
findings = static_analyzer.analyze_file(path, lang)
|
|
57
|
+
|
|
58
|
+
console.print()
|
|
59
|
+
if not findings:
|
|
60
|
+
console.print(
|
|
61
|
+
Panel(
|
|
62
|
+
f"[bold {COLOR_SUCCESS}]✅ Keine Probleme gefunden![/bold {COLOR_SUCCESS}]\n"
|
|
63
|
+
f"[white]Die Datei [bold cyan]{path.name}[/bold cyan] ({lang}) ist sauber.[/white]",
|
|
64
|
+
title=f"[bold {COLOR_PURPLE}]âš¡ centicodehelp CHECK[/bold {COLOR_PURPLE}]",
|
|
65
|
+
border_style=COLOR_SUCCESS,
|
|
66
|
+
expand=False,
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
console.print()
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
table = Table(
|
|
73
|
+
title=f"[bold {COLOR_PURPLE}]âš¡ DATEICHECK:[/bold {COLOR_PURPLE}] [yellow]{path.name}[/yellow] ({len(findings)} Problem(e))",
|
|
74
|
+
show_header=True,
|
|
75
|
+
header_style=f"bold {COLOR_BLUE}",
|
|
76
|
+
border_style=COLOR_INDIGO,
|
|
77
|
+
)
|
|
78
|
+
table.add_column("Schweregrad", width=14)
|
|
79
|
+
table.add_column("Zeile", width=8, justify="right")
|
|
80
|
+
table.add_column("Was ist falsch?", style=COLOR_TEXT)
|
|
81
|
+
table.add_column("Tipp / Lösung", style="italic dim cyan")
|
|
82
|
+
|
|
83
|
+
for f in findings:
|
|
84
|
+
color, badge = SEVERITY_STYLES.get(f.severity, ("white", f.severity.value))
|
|
85
|
+
line_str = str(f.line) if f.line is not None else "-"
|
|
86
|
+
fix_str = f.suggested_fix or "Prüfen und korrigieren"
|
|
87
|
+
table.add_row(f"[{color}]{badge}[/{color}]", line_str, f.description, fix_str)
|
|
88
|
+
|
|
89
|
+
console.print(table)
|
|
90
|
+
console.print(f"[dim]Tipp: Repariere die Datei automatisch mit [bold {COLOR_BLUE}]ct fix {path}[/bold {COLOR_BLUE}][/dim]\n")
|
|
91
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from rich.table import Table
|
|
3
|
+
from rich.panel import Panel
|
|
4
|
+
|
|
5
|
+
from centicodehelp.core.config import load_config, save_config, get_config_path
|
|
6
|
+
from centicodehelp.cli.theme import (
|
|
7
|
+
console,
|
|
8
|
+
COLOR_PURPLE,
|
|
9
|
+
COLOR_BLUE,
|
|
10
|
+
COLOR_INDIGO,
|
|
11
|
+
COLOR_SUCCESS,
|
|
12
|
+
COLOR_ERROR,
|
|
13
|
+
COLOR_TEXT,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
@click.group(name="config", help="centicodehelp Einstellungen und API-Schlüssel verwalten.")
|
|
17
|
+
def config_cmd():
|
|
18
|
+
"""Konfiguration für centicodehelp."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
@config_cmd.command(name="show", help="Aktuelle Konfiguration übersichtlich anzeigen.")
|
|
22
|
+
def show_config():
|
|
23
|
+
config = load_config()
|
|
24
|
+
cfg_path = get_config_path()
|
|
25
|
+
|
|
26
|
+
table = Table(
|
|
27
|
+
title=f"\n[bold {COLOR_PURPLE}]âš™ï¸ centicodehelp KONFIGURATION[/bold {COLOR_PURPLE}]",
|
|
28
|
+
caption=f"[dim]Speicherort: {cfg_path}[/dim]",
|
|
29
|
+
show_header=True,
|
|
30
|
+
header_style=f"bold {COLOR_BLUE}",
|
|
31
|
+
border_style=COLOR_INDIGO,
|
|
32
|
+
expand=True,
|
|
33
|
+
)
|
|
34
|
+
table.add_column("Einstellung", style=f"bold {COLOR_PURPLE}", width=24)
|
|
35
|
+
table.add_column("Wert", style=COLOR_TEXT)
|
|
36
|
+
|
|
37
|
+
table.add_row("Standard-Anbieter", f"[bold {COLOR_BLUE}]{config.default_provider}[/bold {COLOR_BLUE}]")
|
|
38
|
+
table.add_row("Standard-Modell", f"[cyan]{config.default_model}[/cyan]")
|
|
39
|
+
table.add_row("Ollama API URL", config.ollama_base_url)
|
|
40
|
+
table.add_row("Automatisches Backup (.bak)", f"[{COLOR_SUCCESS}]Aktiviert[/{COLOR_SUCCESS}]" if config.auto_backup else "[dim]Deaktiviert[/dim]")
|
|
41
|
+
|
|
42
|
+
if config.api_keys:
|
|
43
|
+
keys_summary = ", ".join([f"[bold {COLOR_BLUE}]{k}[/bold {COLOR_BLUE}]: [dim]••••{v[-4:] if len(v)>4 else '••••'}[/dim]" for k, v in config.api_keys.items()])
|
|
44
|
+
table.add_row("Konfigurierte API-Keys", keys_summary)
|
|
45
|
+
else:
|
|
46
|
+
table.add_row("Konfigurierte API-Keys", "[dim italic]Keine (Verwende Umgebungsvariablen wie OPENAI_API_KEY)[/dim italic]")
|
|
47
|
+
|
|
48
|
+
console.print(table)
|
|
49
|
+
console.print()
|
|
50
|
+
|
|
51
|
+
@config_cmd.command(name="set-key", help="API-Schlüssel für einen Anbieter hinterlegen.")
|
|
52
|
+
@click.argument("provider")
|
|
53
|
+
@click.argument("api_key")
|
|
54
|
+
def set_key(provider: str, api_key: str):
|
|
55
|
+
config = load_config()
|
|
56
|
+
config.api_keys[provider.lower()] = api_key
|
|
57
|
+
save_config(config)
|
|
58
|
+
console.print(f"\n[bold {COLOR_SUCCESS}]✅ API-Schlüssel für[/bold {COLOR_SUCCESS}] [bold {COLOR_PURPLE}]{provider}[/bold {COLOR_PURPLE}] [bold {COLOR_SUCCESS}]gespeichert![/bold {COLOR_SUCCESS}]\n")
|
|
59
|
+
|
|
60
|
+
@config_cmd.command(name="set-default-model", help="Standardmodell festlegen.")
|
|
61
|
+
@click.argument("model")
|
|
62
|
+
def set_default_model(model: str):
|
|
63
|
+
config = load_config()
|
|
64
|
+
config.default_model = model
|
|
65
|
+
save_config(config)
|
|
66
|
+
console.print(f"\n[bold {COLOR_SUCCESS}]✅ Standardmodell geändert auf:[/bold {COLOR_SUCCESS}] [bold {COLOR_BLUE}]{model}[/bold {COLOR_BLUE}]\n")
|
|
67
|
+
|