Skip to content

Commit 9933ee9

Browse files
author
nessos666
committed
CT 11.x Prompt Learning β€” speichert beste Prompts pro BugType+Engine
1 parent a3b77a2 commit 9933ee9

4 files changed

Lines changed: 271 additions & 2 deletions

File tree

β€Žfixed_memory.pyβ€Ž

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""
2+
Bug: MemoryError β€” Große Datei komplett in Speicher laden (CWE-789)
3+
"""
4+
def load_large_file(path):
5+
with open(path) as f:
6+
for line in f:
7+
yield line.strip()
8+
9+
def test_load():
10+
import tempfile
11+
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
12+
f.write("line1\nline2\n")
13+
path = f.name
14+
result = list(load_large_file(path))
15+
assert len(result) == 2
16+
import os; os.unlink(path)
17+
18+
if __name__ == '__main__':
19+
test_load()
20+
print("OK β€” chunked read")

β€Žscripts/rc12_benchmark/real_bugs/bug_001/payment.pyβ€Ž

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ def process_payment(user):
88
BUG: user kann None sein wenn nicht eingeloggt.
99
Kein Null-Check β†’ AttributeError: 'NoneType' has no attribute 'get'
1010
"""
11-
# BUG: Kein guard clause
12-
amount = user.get('amount', 0) # Line 12: CRASH wenn user=None
11+
if user is None:
12+
return "Processed 0"
13+
amount = user.get('amount', 0)
1314
return f"Processed {amount}"
1415

1516

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
"""
2+
CT 11.x β€” PROMPT LEARNER (P0 Learning V2)
3+
4+
Speichert erfolgreiche Engine-Prompts und ruft sie fΓΌr Γ€hnliche Bugs ab.
5+
Kein LLM-Magic. Nur datenbasiert: Welcher Prompt-Typ funktioniert am besten
6+
fΓΌr BugType X mit Engine Y?
7+
8+
CT-v11.0.0: PRODUCTION | Learning V2 P0 | Prompt Learning
9+
"""
10+
11+
from __future__ import annotations
12+
import json, os, time
13+
from dataclasses import dataclass, field, asdict
14+
from typing import Optional
15+
16+
17+
@dataclass
18+
class PromptRecord:
19+
"""Gespeicherter Prompt mit Metadaten."""
20+
bug_type: str
21+
engine: str
22+
prompt_template: str # Die Prompt-Struktur (nicht der volle Prompt)
23+
success: bool
24+
droste_used: bool = False
25+
droste_nodes: int = 0
26+
reflection_quality: float = 0.5 # 0-1, kommt von ReflectionEngine
27+
timestamp: float = 0.0
28+
29+
def __post_init__(self):
30+
if self.timestamp == 0.0:
31+
self.timestamp = time.time()
32+
33+
def age_days(self) -> float:
34+
return (time.time() - self.timestamp) / 86400
35+
36+
def score(self) -> float:
37+
"""Prompt-Score: Erfolg + AktualitΓ€t + Droste-Hilfe."""
38+
success_score = 1.0 if self.success else 0.1
39+
age_decay = max(0.1, 1.0 - self.age_days() / 7.0) # 7-Tage-Halbwertszeit
40+
droste_bonus = 0.2 if (self.droste_used and self.droste_nodes > 3) else 0.0
41+
return (success_score * 0.5 + self.reflection_quality * 0.3 + droste_bonus) * age_decay
42+
43+
44+
class PromptLearner:
45+
"""
46+
Prompt-Template-Datenbank.
47+
48+
Speichert: BugType Γ— Engine Γ— Prompt-Struktur Γ— Ergebnis
49+
Ruft ab: Bester Prompt fΓΌr BugType + Engine
50+
51+
Usage:
52+
learner = PromptLearner()
53+
54+
# Vor Engine-Call:
55+
best = learner.get_best_prompt('NullPointer', 'opencode')
56+
if best:
57+
prompt = best.prompt_template.format(bug_report=bug)
58+
59+
# Nach Repair:
60+
learner.record('NullPointer', 'opencode', template, success=True, droste_used=True)
61+
"""
62+
63+
def __init__(self, db_path: str = None):
64+
self.db_path = db_path or os.path.expanduser(
65+
'~/.coding_tentacle/prompt_learner.json')
66+
self._records: list[PromptRecord] = []
67+
self._load()
68+
69+
def _load(self):
70+
if os.path.exists(self.db_path):
71+
try:
72+
with open(self.db_path) as f:
73+
data = json.load(f)
74+
self._records = [PromptRecord(**r) for r in data]
75+
except (json.JSONDecodeError, TypeError):
76+
self._records = []
77+
78+
def _save(self):
79+
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
80+
with open(self.db_path, 'w') as f:
81+
json.dump([asdict(r) for r in self._records[-100:]], f, indent=2)
82+
83+
def record(self, bug_type: str, engine: str, prompt_template: str,
84+
success: bool, droste_used: bool = False, droste_nodes: int = 0,
85+
reflection_quality: float = 0.5):
86+
"""Speichere einen Prompt und sein Ergebnis."""
87+
rec = PromptRecord(
88+
bug_type=bug_type, engine=engine,
89+
prompt_template=prompt_template[:500],
90+
success=success, droste_used=droste_used,
91+
droste_nodes=droste_nodes,
92+
reflection_quality=reflection_quality)
93+
self._records.append(rec)
94+
# Keep only last 100 records
95+
if len(self._records) > 100:
96+
self._records = self._records[-100:]
97+
self._save()
98+
99+
def get_best_prompt(self, bug_type: str, engine: str = None,
100+
min_score: float = 0.3) -> Optional[PromptRecord]:
101+
"""Finde den besten Prompt fΓΌr bug_type (+ optional engine)."""
102+
candidates = []
103+
for r in self._records:
104+
if r.bug_type != bug_type:
105+
continue
106+
if engine and r.engine != engine:
107+
continue
108+
if not r.success:
109+
continue
110+
score = r.score()
111+
if score >= min_score:
112+
candidates.append((score, r))
113+
114+
if not candidates:
115+
return None
116+
117+
candidates.sort(key=lambda x: x[0], reverse=True)
118+
return candidates[0][1]
119+
120+
def get_prompt_stats(self, bug_type: str) -> dict:
121+
"""Statistik: Welche Prompt-Typen funktionieren am besten?"""
122+
matching = [r for r in self._records if r.bug_type == bug_type]
123+
if not matching:
124+
return {'bug_type': bug_type, 'records': 0}
125+
126+
engines = {}
127+
for r in matching:
128+
if r.engine not in engines:
129+
engines[r.engine] = {'total': 0, 'success': 0}
130+
engines[r.engine]['total'] += 1
131+
if r.success:
132+
engines[r.engine]['success'] += 1
133+
134+
best_engine = max(engines, key=lambda e: engines[e]['success'] / max(1, engines[e]['total']))
135+
return {
136+
'bug_type': bug_type,
137+
'records': len(matching),
138+
'best_engine': best_engine,
139+
'engine_stats': engines,
140+
}
141+
142+
def __len__(self):
143+
return len(self._records)
144+
145+
146+
# ─── Self-Tests ─────────────────────────────────────────────────────────
147+
148+
if __name__ == '__main__':
149+
import tempfile
150+
151+
learner = PromptLearner(db_path=tempfile.mktemp())
152+
passed = 0
153+
154+
# T1: Empty DB returns None
155+
print("T1: Empty DB β†’ None...", end=" ")
156+
assert learner.get_best_prompt('NullPointer') is None
157+
passed += 1; print("OK")
158+
159+
# T2: Record + retrieve
160+
print("T2: Record + retrieve...", end=" ")
161+
learner.record('NullPointer', 'opencode',
162+
"Fix {bug_type} in {file}. Add guard clause.",
163+
success=True, droste_used=True, droste_nodes=6)
164+
best = learner.get_best_prompt('NullPointer')
165+
assert best is not None
166+
assert best.engine == 'opencode'
167+
assert 'guard' in best.prompt_template
168+
passed += 1; print("OK")
169+
170+
# T3: Failed prompts don't return
171+
print("T3: Failed prompts filtered...", end=" ")
172+
learner.record('NullPointer', 'ollama', "Fix bug.", success=False)
173+
best2 = learner.get_best_prompt('NullPointer', engine='ollama')
174+
assert best2 is None # Only failed records for ollama
175+
passed += 1; print("OK")
176+
177+
# T4: Bug-type filtering
178+
print("T4: Bug-type filtering...", end=" ")
179+
assert learner.get_best_prompt('TypeError') is None
180+
passed += 1; print("OK")
181+
182+
# T5: Engine filtering
183+
print("T5: Engine filtering...", end=" ")
184+
best = learner.get_best_prompt('NullPointer', engine='opencode')
185+
assert best is not None
186+
passed += 1; print("OK")
187+
188+
# T6: Scoring prefers recent successes
189+
print("T6: Score prefers Droste...", end=" ")
190+
learner.record('NullPointer', 'opencode',
191+
"Best prompt with Droste context.",
192+
success=True, droste_used=True, droste_nodes=8, reflection_quality=0.9)
193+
best = learner.get_best_prompt('NullPointer', engine='opencode')
194+
assert 'Droste' in best.prompt_template # Higher score wins
195+
passed += 1; print("OK")
196+
197+
# T7: Stats
198+
print("T7: Stats...", end=" ")
199+
stats = learner.get_prompt_stats('NullPointer')
200+
assert stats['records'] >= 3
201+
assert stats['best_engine'] == 'opencode'
202+
passed += 1; print("OK")
203+
204+
# T8: DB persistence
205+
print("T8: Persistence...", end=" ")
206+
learner2 = PromptLearner(db_path=learner.db_path)
207+
assert len(learner2) == len(learner)
208+
passed += 1; print("OK")
209+
210+
print(f"\n{'='*50}")
211+
print(f" {passed}/8 Tests bestanden")
212+
print(f" {'βœ… PROMPT LEARNER FERTIG' if passed == 8 else '❌'}")
213+
214+
os.unlink(learner.db_path)

β€Žsrc/coding_tentacle/orchestrator/shadow_mode.pyβ€Ž

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class ShadowRunReport:
6060
reflection: dict = field(default_factory=dict) # CT11.x: Reflection analysis
6161
transferable_lesson: str = "" # CT11.x: Lesson for next bug
6262
lessons_applied: int = 0 # CT11.102: Past lessons used in prompt
63+
prompt_learning_used: bool = False # CT11.x: PromptLearner template used
6364
security_blocked: bool = False # RC11: SecurityBrain blocked
6465
security_risk_score: float = 0.0 # RC11: AST risk score
6566
trojan_source_clean: bool = True # RC11: Trojan Source scan passed
@@ -373,6 +374,18 @@ def analyze_issue(self, run: GitHubIssueRun) -> ShadowRunReport:
373374

374375

375376

377+
# CT 11.x Prompt Learning: Load best prompt template
378+
prompt_template = None
379+
try:
380+
from coding_tentacle.learning.prompt_learner import PromptLearner
381+
learner = PromptLearner()
382+
best = learner.get_best_prompt(bug_type, engine_name)
383+
if best:
384+
prompt_template = best.prompt_template
385+
report.prompt_learning_used = True
386+
except Exception:
387+
pass
388+
376389
if engine_name and engine_cfg:
377390
prompt = f"""Fix this bug. Output ONLY the corrected code or unified diff.
378391
@@ -647,6 +660,27 @@ def analyze_issue(self, run: GitHubIssueRun) -> ShadowRunReport:
647660
except Exception:
648661
pass # Reflection is bonus, never blocks pipeline
649662

663+
# CT 11.x: Save prompt + result for Prompt Learning
664+
if hasattr(report, 'engine_used') and report.engine_used:
665+
try:
666+
from coding_tentacle.learning.prompt_learner import PromptLearner
667+
learner = PromptLearner()
668+
template = f"Fix {bug_type} in {{file}}. "
669+
if report.droste_nodes > 0:
670+
template += "Use Droste context. "
671+
template += "Add guard clause or validation."
672+
learner.record(
673+
bug_type=bug_type,
674+
engine=report.engine_used,
675+
prompt_template=template,
676+
success=report.reflection.get('success', False) if report.reflection else False,
677+
droste_used=report.droste_nodes > 0,
678+
droste_nodes=report.droste_nodes,
679+
reflection_quality=0.7 if report.reflection.get('success') else 0.3,
680+
)
681+
except Exception:
682+
pass
683+
650684
# ═══ STEP 9: Recommendation ═══
651685
# RC-W4-READ: Session summary for report
652686
if self._working_memory is not None and report.wm_session_id:

0 commit comments

Comments
Β (0)