11import difflib
2- import json
32import logging
43import os
54import shutil
6- import threading
75import time
8- import urllib .parse
9- from http .server import HTTPServer
106from pathlib import Path
11- from typing import Any
12-
13- from .dashboard_html import OBSERVER_HTML
14- from .pyob_dashboard import ObserverHandler
15- from .targeted_reviewer import TargetedReviewer
7+ from typing import Any , Callable , Optional
168
179logger = logging .getLogger (__name__ )
1810
1911
20- # [TargetedReviewer class moved to targeted_reviewer.py]
21-
22-
2312class EntranceMixin :
24- def start_dashboard (self : Any ):
25- # 1. Save to the internal .pyob folder
26- obs_path = os .path .join (self .pyob_dir , "observer.html" )
27-
28- # Dynamically modify OBSERVER_HTML string to inject manual target UI and logic
29- modified_html_content = OBSERVER_HTML
30-
31- # Insert HTML block for manual target selection
32- html_to_insert = """
33- <!-- NEW INTERACTIVE FEATURE START -->
34- <div class="control-section" style="background-color: #333333; padding: 15px; border-radius: 6px; margin-top: 20px;">
35- <h3>Manual Target Selection</h3>
36- <form id="set-target-form" action="/set_target" method="POST">
37- <input type="text" id="target-file-path" name="file_path" placeholder="e.g., src/my_module/my_file.py" style="width: 70%; padding: 8px; margin-right: 10px; border: 1px solid #555; background-color: #1c1c1c; color: #d4d4d4; border-radius: 4px;">
38- <button type="submit" style="padding: 8px 15px; background-color: #6a9955; color: white; border: none; border-radius: 4px; cursor: pointer;">Set Next Target</button>
39- </form>
40- <p id="target-message" style="margin-top: 10px; color: #dcdcaa;"></p>
41- </div>
42- <!-- NEW INTERACTIVE FEATURE END -->
43- """
44- # Assuming OBSERVER_HTML has a structure like: ... </div>\n\n <h2>Live Log</h2>
45- insertion_marker_html = " </div>\n \n <h2>Live Log</h2>"
46- if insertion_marker_html in modified_html_content :
47- modified_html_content = modified_html_content .replace (
48- insertion_marker_html , html_to_insert + "\n " + insertion_marker_html
49- )
50- else :
51- logger .warning (
52- "HTML insertion marker not found in OBSERVER_HTML. Manual target UI may not be visible."
53- )
54-
55- # Insert JavaScript for manual target form submission handling
56- js_to_insert = """
57- // Handle manual target form submission
58- document.getElementById('set-target-form').addEventListener('submit', function(event) {
59- event.preventDefault(); // Prevent default form submission
60-
61- const filePathInput = document.getElementById('target-file-path');
62- const filePath = filePathInput.value;
63- const targetMessage = document.getElementById('target-message');
64-
65- if (!filePath) {
66- targetMessage.textContent = 'Please enter a file path.';
67- targetMessage.style.color = 'red';
68- return;
69- }
70-
71- fetch('/set_target', {
72- method: 'POST',
73- headers: {
74- 'Content-Type': 'application/x-www-form-urlencoded',
75- },
76- body: `file_path=${encodeURIComponent(filePath)}`
77- })
78- .then(response => {
79- const isOk = response.ok; // Capture response.ok status
80- return response.json().then(data => ({ data, isOk })); // Pass data and status
81- })
82- .then(({ data, isOk }) => { // Destructure to get both
83- targetMessage.textContent = data.message;
84- // Check response.ok from the original fetch response, not data
85- targetMessage.style.color = isOk ? '#6a9955' : 'red';
86- if (isOk) {
87- filePathInput.value = ''; // Clear input on success
88- }
89- })
90- .catch(error => {
91- console.error('Error setting manual target:', error);
92- targetMessage.textContent = 'An error occurred while setting target.';
93- targetMessage.style.color = 'red';
94- });
95- });
96- """
97- # Assuming OBSERVER_HTML has a script tag ending with ' </script>'
98- insertion_marker_js = " </script>"
99- if insertion_marker_js in modified_html_content :
100- modified_html_content = modified_html_content .replace (
101- insertion_marker_js , js_to_insert + "\n " + insertion_marker_js
102- )
103- else :
104- logger .warning (
105- "JavaScript insertion marker not found in OBSERVER_HTML. Manual target logic may not be functional."
106- )
107-
108- with open (obs_path , "w" , encoding = "utf-8" ) as f :
109- f .write (modified_html_content )
110-
111- # 2. Initialize and Start the Live Server
112-
113- # Dynamically add do_POST method for manual target handling
114- def _dynamic_do_POST_method (handler_instance : ObserverHandler ):
115- if handler_instance .path == "/set_target" :
116- try :
117- content_length = int (
118- handler_instance .headers .get ("Content-Length" , 0 )
119- )
120- except (ValueError , TypeError ):
121- content_length = 0 # Default to 0 if header is malformed
122-
123- if content_length > 0 :
124- post_data = handler_instance .rfile .read (content_length ).decode (
125- "utf-8"
126- )
127- parsed_data = urllib .parse .parse_qs (post_data )
128- file_path = parsed_data .get ("file_path" , ["" ])[0 ]
129- else :
130- file_path = "" # No content, no file_path
131-
132- if file_path and handler_instance .controller :
133- handler_instance .controller .set_manual_target_file (file_path )
134- message = f"Manual target set to: { file_path } "
135- status_code = 200
136- else :
137- message = (
138- "Error: No file path provided or controller not available."
139- )
140- status_code = 400
141-
142- handler_instance .send_response (status_code )
143- handler_instance .send_header ("Content-type" , "application/json" )
144- handler_instance .end_headers ()
145- handler_instance .wfile .write (
146- json .dumps ({"message" : message }).encode ("utf-8" )
147- )
148- else :
149- handler_instance .send_error (404 )
150-
151- # Fix: Use setattr to bypass Mypy [method-assign] error
152- setattr (ObserverHandler , "do_POST" , _dynamic_do_POST_method )
153-
154- ObserverHandler .controller = self
155-
156- def run_server ():
157- try :
158- server = HTTPServer (("localhost" , 5000 ), ObserverHandler )
159- server .serve_forever ()
160- except Exception as e :
161- logger .error (f"Dashboard failed to start: { e } " )
162-
163- threading .Thread (target = run_server , daemon = True ).start ()
164-
165- print ("\n " + "=" * 60 )
166- print ("PyOuroBoros (PyOB) OBSERVER IS LIVE" )
167- print ("URL: http://localhost:5000" )
168- print (f"FILE: { obs_path } " )
169- print ("=" * 60 + "\n " )
170-
171- def execute_targeted_iteration (self : Any , iteration : int ):
13+ """
14+ Mixin providing core iteration logic.
15+ Attributes are declared here to satisfy strict Mypy checks.
16+ """
17+
18+ target_dir : str
19+ pyob_dir : str
20+ ENGINE_FILES : list [str ]
21+ llm_engine : Any
22+ code_parser : Any
23+ cascade_queue : list [str ]
24+ cascade_diffs : dict [str , str ]
25+ session_pr_count : int
26+ self_evolved_flag : bool
27+ memory_path : str
28+ history_path : str
29+ analysis_path : str
30+ symbols_path : str
31+ manual_target_file : Optional [str ]
32+ key_cooldowns : dict [str , float ]
33+
34+ def pick_target_file (self ) -> str :
35+ return ""
36+
37+ def _read_file (self , path : str ) -> str :
38+ return ""
39+
40+ def _extract_path_from_llm_response (self , text : str ) -> str :
41+ return ""
42+
43+ def get_valid_llm_response (
44+ self , p : str , v : Callable [[str ], bool ], context : str
45+ ) -> str :
46+ return ""
47+
48+ def update_analysis_for_single_file (self , abs_p : str , rel_p : str ):
49+ pass
50+
51+ def update_ledger_for_file (self , rel_p : str , code : str ):
52+ pass
53+
54+ def detect_symbolic_ripples (self , o : str , n : str , p : str ) -> list [str ]:
55+ return []
56+
57+ def _run_final_verification_and_heal (self , b : dict ) -> bool :
58+ return False
59+
60+ def handle_git_librarian (self , p : str , i : int ):
61+ pass
62+
63+ def append_to_history (self , p : str , o : str , n : str ):
64+ pass
65+
66+ def wrap_up_evolution_session (self ):
67+ pass
68+
69+ def generate_pr_summary (self , rel_path : str , diff_text : str ) -> dict :
70+ return {}
71+
72+ def execute_targeted_iteration (self , iteration : int ):
73+ """Orchestrates a single targeted evolution step."""
17274 backup_state = self .llm_engine .backup_workspace ()
17375 target_diff = ""
76+
17477 if self .cascade_queue :
17578 target_rel_path = self .cascade_queue .pop (0 )
17679 target_diff = self .cascade_diffs .get (target_rel_path , "" )
@@ -192,35 +95,40 @@ def execute_targeted_iteration(self: Any, iteration: int):
19295 project_name = os .path .basename (self .target_dir )
19396 base_backup_path = Path .home () / "Documents" / "PYOB_Backups" / project_name
19497 pod_path = base_backup_path / f"safety_pod_v{ iteration } _{ timestamp } "
195-
19698 try :
19799 pod_path .mkdir (parents = True , exist_ok = True )
198- logger .warning (
199- f"SELF-EVOLUTION: Sheltering engine source EXTERNALLY in { pod_path } "
200- )
201100 for f_name in self .ENGINE_FILES :
202101 src = os .path .join (self .target_dir , "src" , "pyob" , f_name )
203102 if os .path .exists (src ):
204103 shutil .copy (src , str (pod_path ))
205104 except Exception as e :
206- logger .error (f"Failed to create external safety pod: { e } " )
105+ logger .error (f"Failed to create safety pod: { e } " )
207106
208107 target_abs_path = os .path .join (self .target_dir , target_rel_path )
209108 self .llm_engine .session_context = []
210109 if is_cascade and target_diff :
211- msg = f"CRITICAL SYMBOLIC RIPPLE: This file depends on code that was just modified. Ensure this file is updated to support these changes: \n \n ### DEPDENDENCY CHANGE DIFF:\n { target_diff } "
110+ msg = f"CRITICAL SYMBOLIC RIPPLE: This file depends on code that was just modified.\n ### CHANGE DIFF:\n { target_diff } "
212111 self .llm_engine .session_context .append (msg )
213112
214113 old_content = ""
215114 if os .path .exists (target_abs_path ):
216115 with open (target_abs_path , "r" , encoding = "utf-8" , errors = "ignore" ) as f :
217116 old_content = f .read ()
218117
118+ from pyob .targeted_reviewer import TargetedReviewer
119+
219120 reviewer = TargetedReviewer (self .target_dir , target_abs_path )
220121 reviewer .session_context = self .llm_engine .session_context [:]
122+ if hasattr (self , "key_cooldowns" ):
123+ reviewer .key_cooldowns = self .key_cooldowns
124+ if hasattr (self , "session_pr_count" ):
125+ reviewer .session_pr_count = self .session_pr_count
126+
221127 reviewer .run_pipeline (iteration )
222128
223129 self .llm_engine .session_context = reviewer .session_context [:]
130+ if hasattr (reviewer , "session_pr_count" ):
131+ self .session_pr_count = reviewer .session_pr_count
224132
225133 new_content = ""
226134 if os .path .exists (target_abs_path ):
@@ -232,43 +140,35 @@ def execute_targeted_iteration(self: Any, iteration: int):
232140 self .update_ledger_for_file (target_rel_path , new_content )
233141
234142 if old_content != new_content :
235- logger .info (
236- f"Edit successful. Checking ripples and running final verification for { target_rel_path } ..."
237- )
143+ logger .info (f"Edit successful. Verifying { target_rel_path } ..." )
238144 self .append_to_history (target_rel_path , old_content , new_content )
239-
240145 current_diff = "" .join (
241146 difflib .unified_diff (
242147 old_content .splitlines (keepends = True ),
243148 new_content .splitlines (keepends = True ),
244149 )
245150 )
246-
247151 ripples = self .detect_symbolic_ripples (
248152 old_content , new_content , target_rel_path
249153 )
250154 if ripples :
251- logger .warning (
252- f"CROSS-FILE DEPTH TRIGGERED! Queuing { len (ripples )} files."
253- )
254155 for r in ripples :
255156 if r not in self .cascade_queue :
256157 self .cascade_queue .append (r )
257158 self .cascade_diffs [r ] = current_diff
258159
259- logger .info ("\n " + "=" * 20 + " FINAL VERIFICATION " + "=" * 20 )
260160 if not self ._run_final_verification_and_heal (backup_state ):
261- logger .error (
262- "Final verification failed and could not be auto-repaired. Iteration changes have been rolled back."
263- )
161+ logger .error ("Final verification failed. Changes rolled back." )
264162 else :
265- logger .info ("Final verification successful. Application is stable." )
266163 self .handle_git_librarian (target_rel_path , iteration )
267-
268164 if is_engine_file :
269- logger .warning (
270- f"SELF-EVOLUTION: `{ target_rel_path } ` was successfully updated."
271- )
272165 self .self_evolved_flag = True
273166
274- logger .info ("=" * 60 + "\n " )
167+ # --- THE FINAL WRAP-UP GATE ---
168+ if getattr (self , "session_pr_count" , 0 ) >= 8 and not getattr (
169+ self , "cascade_queue" , []
170+ ):
171+ logger .info (
172+ f"🏆 MISSION ACCOMPLISHED: { self .session_pr_count } PRs achieved."
173+ )
174+ self .wrap_up_evolution_session ()
0 commit comments