Skip to content

Commit aa252b8

Browse files
authored
Merge pull request #267 FOLLOW DIRECTIVES.md
Update core_utils.py
2 parents ca9ef4a + fa8fc39 commit aa252b8

File tree

3 files changed

+102
-197
lines changed

3 files changed

+102
-197
lines changed

src/pyob/core_utils.py

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

6061
IGNORE_FILES = {
@@ -351,14 +352,18 @@ def load_memory(self) -> str:
351352
with open(directives_path, "r", encoding="utf-8") as f:
352353
human_orders = f.read().strip()
353354
if human_orders:
355+
logger.info(
356+
f"🎯 BEACON ACTIVE: Injected {len(human_orders.splitlines())} lines from DIRECTIVES.md"
357+
)
358+
354359
memory_content = (
355-
f"# HUMAN DIRECTIVES (PRIORITY)\n"
356-
f"{human_orders}\n\n"
357-
f"---\n\n"
358-
f"{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}"
359364
)
360365
except Exception as e:
361-
logger.warning(f"Librarian could not read DIRECTIVES.md: {e}")
366+
logger.warning(f"Could not read DIRECTIVES.md: {e}")
362367

363368
return memory_content
364369

src/pyob/entrance_mixins.py

Lines changed: 84 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -1,176 +1,79 @@
11
import difflib
2-
import json
32
import logging
43
import os
54
import shutil
6-
import threading
75
import time
8-
import urllib.parse
9-
from http.server import HTTPServer
106
from 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

179
logger = logging.getLogger(__name__)
1810

1911

20-
# [TargetedReviewer class moved to targeted_reviewer.py]
21-
22-
2312
class 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

Comments
 (0)