Skip to content

Commit c77f590

Browse files
authored
Merge pull request #269 from vicsanity623/new
New
2 parents f464524 + 5ccaeb9 commit c77f590

4 files changed

Lines changed: 198 additions & 102 deletions

File tree

src/pyob/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

src/pyob/core_utils.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
"node_modules",
5656
".vscode",
5757
".idea",
58-
"tests",
5958
}
6059

6160
IGNORE_FILES = {
@@ -352,18 +351,14 @@ def load_memory(self) -> str:
352351
with open(directives_path, "r", encoding="utf-8") as f:
353352
human_orders = f.read().strip()
354353
if human_orders:
355-
logger.info(
356-
f"🎯 BEACON ACTIVE: Injected {len(human_orders.splitlines())} lines from DIRECTIVES.md"
357-
)
358-
359354
memory_content = (
360-
f"# CRITICAL HUMAN DIRECTIVES - PRIORITY 1\n"
361-
f"{human_orders}\n"
362-
f"\n# END CRITICAL DIRECTIVES\n"
363-
f"---\n{memory_content}"
355+
f"# HUMAN DIRECTIVES (PRIORITY)\n"
356+
f"{human_orders}\n\n"
357+
f"---\n\n"
358+
f"{memory_content}"
364359
)
365360
except Exception as e:
366-
logger.warning(f"Could not read DIRECTIVES.md: {e}")
361+
logger.warning(f"Librarian could not read DIRECTIVES.md: {e}")
367362

368363
return memory_content
369364

src/pyob/entrance_mixins.py

Lines changed: 184 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,176 @@
11
import difflib
2+
import json
23
import logging
34
import os
45
import shutil
6+
import threading
57
import time
8+
import urllib.parse
9+
from http.server import HTTPServer
610
from pathlib import Path
7-
from typing import Any, Callable, Optional
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
816

917
logger = logging.getLogger(__name__)
1018

1119

20+
# [TargetedReviewer class moved to targeted_reviewer.py]
21+
22+
1223
class EntranceMixin:
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."""
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):
74172
backup_state = self.llm_engine.backup_workspace()
75173
target_diff = ""
76-
77174
if self.cascade_queue:
78175
target_rel_path = self.cascade_queue.pop(0)
79176
target_diff = self.cascade_diffs.get(target_rel_path, "")
@@ -95,40 +192,35 @@ def execute_targeted_iteration(self, iteration: int):
95192
project_name = os.path.basename(self.target_dir)
96193
base_backup_path = Path.home() / "Documents" / "PYOB_Backups" / project_name
97194
pod_path = base_backup_path / f"safety_pod_v{iteration}_{timestamp}"
195+
98196
try:
99197
pod_path.mkdir(parents=True, exist_ok=True)
198+
logger.warning(
199+
f"SELF-EVOLUTION: Sheltering engine source EXTERNALLY in {pod_path}"
200+
)
100201
for f_name in self.ENGINE_FILES:
101202
src = os.path.join(self.target_dir, "src", "pyob", f_name)
102203
if os.path.exists(src):
103204
shutil.copy(src, str(pod_path))
104205
except Exception as e:
105-
logger.error(f"Failed to create safety pod: {e}")
206+
logger.error(f"Failed to create external safety pod: {e}")
106207

107208
target_abs_path = os.path.join(self.target_dir, target_rel_path)
108209
self.llm_engine.session_context = []
109210
if is_cascade and target_diff:
110-
msg = f"CRITICAL SYMBOLIC RIPPLE: This file depends on code that was just modified.\n### CHANGE DIFF:\n{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}"
111212
self.llm_engine.session_context.append(msg)
112213

113214
old_content = ""
114215
if os.path.exists(target_abs_path):
115216
with open(target_abs_path, "r", encoding="utf-8", errors="ignore") as f:
116217
old_content = f.read()
117218

118-
from pyob.targeted_reviewer import TargetedReviewer
119-
120219
reviewer = TargetedReviewer(self.target_dir, target_abs_path)
121220
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-
127221
reviewer.run_pipeline(iteration)
128222

129223
self.llm_engine.session_context = reviewer.session_context[:]
130-
if hasattr(reviewer, "session_pr_count"):
131-
self.session_pr_count = reviewer.session_pr_count
132224

133225
new_content = ""
134226
if os.path.exists(target_abs_path):
@@ -140,35 +232,43 @@ def execute_targeted_iteration(self, iteration: int):
140232
self.update_ledger_for_file(target_rel_path, new_content)
141233

142234
if old_content != new_content:
143-
logger.info(f"Edit successful. Verifying {target_rel_path}...")
235+
logger.info(
236+
f"Edit successful. Checking ripples and running final verification for {target_rel_path}..."
237+
)
144238
self.append_to_history(target_rel_path, old_content, new_content)
239+
145240
current_diff = "".join(
146241
difflib.unified_diff(
147242
old_content.splitlines(keepends=True),
148243
new_content.splitlines(keepends=True),
149244
)
150245
)
246+
151247
ripples = self.detect_symbolic_ripples(
152248
old_content, new_content, target_rel_path
153249
)
154250
if ripples:
251+
logger.warning(
252+
f"CROSS-FILE DEPTH TRIGGERED! Queuing {len(ripples)} files."
253+
)
155254
for r in ripples:
156255
if r not in self.cascade_queue:
157256
self.cascade_queue.append(r)
158257
self.cascade_diffs[r] = current_diff
159258

259+
logger.info("\n" + "=" * 20 + " FINAL VERIFICATION " + "=" * 20)
160260
if not self._run_final_verification_and_heal(backup_state):
161-
logger.error("Final verification failed. Changes rolled back.")
261+
logger.error(
262+
"Final verification failed and could not be auto-repaired. Iteration changes have been rolled back."
263+
)
162264
else:
265+
logger.info("Final verification successful. Application is stable.")
163266
self.handle_git_librarian(target_rel_path, iteration)
267+
164268
if is_engine_file:
269+
logger.warning(
270+
f"SELF-EVOLUTION: `{target_rel_path}` was successfully updated."
271+
)
165272
self.self_evolved_flag = True
166273

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()
274+
logger.info("=" * 60 + "\n")

0 commit comments

Comments
 (0)