Skip to content
Merged
19 changes: 19 additions & 0 deletions PR_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# PR_SUMMARY.md

## Session Overview
This session focused on hardening the PyOB core infrastructure, improving code manipulation precision, and formalizing system communication protocols. We successfully executed 8 targeted pull requests that transitioned the codebase toward stricter type safety, more robust file-patching logic, and standardized prompt management.

## Technical Milestones
* **Precision Patching:** Implemented an indentation-aware replacement mechanism in `xml_mixin.py`, ensuring that automated code injections maintain structural integrity.
* **Type Safety Enforcement:** Applied explicit type hinting across `core_utils.py`, `pyob_code_parser.py`, and `entrance_mixins.py` to reduce runtime ambiguity and improve IDE static analysis.
* **Robust Server Lifecycle:** Refactored dynamic method injection and server execution in `entrance_mixins.py` to ensure consistent signature handling and cleaner thread management.
* **Prompt Centralization:** Formalized the `prompts.py` module, introducing strict validation and a structured template for the `MEMORY.md` system, ensuring the AI agent maintains a concise and accurate transactional history.
* **Parser Optimization:** Enhanced regex handling in `pyob_code_parser.py` to improve the reliability of asset extraction from source code.

## Architectural Impact
The codebase is now significantly more resilient and maintainable:
* **Predictable State:** By enforcing strict formatting and length constraints in the `MEMORY.md` generation prompts, we have eliminated "memory bloat" and ensured the system remains focused on successful transactional outcomes.
* **Reduced Fragility:** The transition to explicit type annotations and improved indentation logic in the XML/code patching engine minimizes the risk of syntax errors during automated refactoring.
* **Improved Debuggability:** Standardizing the server-side handlers and utility functions has created a more predictable execution environment, making it easier to trace logic flow during complex symbolic ripple analysis.

The system is now better equipped to handle long-term autonomous operations with higher reliability and clearer self-documentation.
4 changes: 2 additions & 2 deletions src/pyob/core_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import time
from typing import Callable, Optional

from .models import (
from pyob.models import (
OPENROUTER_KEY,
get_valid_llm_response_engine,
raw_gemini_keys,
Expand Down Expand Up @@ -257,7 +257,7 @@ def _get_input_with_timeout(self, timeout: int) -> str:
def _win32_input(self, start_time: float, timeout: int) -> str:
import msvcrt # type: ignore

input_str = ""
input_str: str = ""
prev_line_len = 0
while True:
remaining = int(timeout - (time.time() - start_time))
Expand Down
8 changes: 6 additions & 2 deletions src/pyob/entrance.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,8 @@ def _run_git_command(self, cmd: list[str]) -> bool:
def detect_symbolic_ripples(
self, old: str, new: str, source_file: str
) -> list[str]:
diff = list(difflib.unified_diff(old.splitlines(), new.splitlines()))
"""Detects files impacted by symbolic changes."""
diff: list[str] = list(difflib.unified_diff(old.splitlines(), new.splitlines()))
changed_text = "\n".join(
[line for line in diff if line.startswith("+") or line.startswith("-")]
)
Expand All @@ -466,7 +467,10 @@ def detect_symbolic_ripples(
impacted_files.append(target_file)
return list(set(impacted_files))

def update_analysis_for_single_file(self, target_abs_path: str, rel_path: str):
def update_analysis_for_single_file(
self, target_abs_path: str, rel_path: str
) -> None:
"""Updates the analysis markdown for a specific file."""
if not os.path.exists(self.analysis_path):
return
with open(target_abs_path, "r", encoding="utf-8", errors="ignore") as f:
Expand Down
4 changes: 2 additions & 2 deletions src/pyob/entrance_mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def start_dashboard(self):
# 2. Initialize and Start the Live Server

# Dynamically add do_POST method for manual target handling
def _dynamic_do_POST_method(handler_instance: ObserverHandler):
def _dynamic_do_POST_method(handler_instance: ObserverHandler) -> None:
if handler_instance.path == "/set_target":
try:
content_length = int(
Expand Down Expand Up @@ -152,7 +152,7 @@ def _dynamic_do_POST_method(handler_instance: ObserverHandler):

ObserverHandler.controller = self

def run_server():
def run_server() -> None:
try:
server = HTTPServer(("localhost", 5000), ObserverHandler)
server.serve_forever()
Expand Down
24 changes: 24 additions & 0 deletions src/pyob/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,30 @@
These templates are used to generate the `.pyob/*.md` files.
"""

from typing import Dict, List


class PromptValidator:
@staticmethod
def validate_prompts(prompts: Dict[str, str]) -> None:
required_keys: List[str] = [
"UM.md",
"RM.md",
"PP.md",
"ALF.md",
"FRE.md",
"PF.md",
"IF.md",
"PCF.md",
"PIR.md",
]
for key in required_keys:
if key not in prompts:
raise ValueError(f"Missing required prompt key: {key}")
if not prompts[key].strip():
raise ValueError(f"Prompt key {key} is empty")


SYSTEM_PROMPTS = {
"UM.md": "You are the PyOB Memory Manager. Your job is to update MEMORY.md.\n\n### Current Memory:\n{current_memory}\n\n### Recent Actions:\n{session_summary}\n\n### INSTRUCTIONS:\n1. Update the memory with the Recent Actions.\n2. TRANSACTIONAL RECORDING: Only record changes as 'Implemented' if the actions specifically state 'SUCCESSFUL CHANGE'. If you see 'CRITICAL: FAILED' or 'ROLLED BACK', record this as a 'Failed Attempt' with the reason, so the engine knows to try a different approach next time.\n3. BREVITY: Keep the ENTIRE document under 200 words. Be ruthless. Delete old, irrelevant details.\n4. FORMAT: Keep lists strictly to bullet points. No long paragraphs.\n5. Respond EXCLUSIVELY with the raw markdown for MEMORY.md. Do not use ```markdown fences or <THOUGHT> blocks.",
"RM.md": "You are the PYOB Memory Manager. The current memory and context are becoming too bloated.\n\n### Current Logic Memory:\n{current_memory}\n\n### Recent History:\n{history}\n\n### High-Level Analysis:\n{analysis}\n\n### INSTRUCTIONS:\n1. AGGRESSIVELY COMPRESS this memory. \n2. Maintain a dense rolling summary combining the history, analysis, and prior memory.\n3. Delete duplicate information, repetitive logs, and obvious statements.\n4. Keep ONLY the core architectural rules, crucial file dependencies, and key facts/decisions.\n5. The final output MUST BE UNDER 150 WORDS.\n6. Respond EXCLUSIVELY with the raw markdown for MEMORY.md. No fences, no thoughts.",
Expand Down
6 changes: 4 additions & 2 deletions src/pyob/pyob_code_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ def _parse_python_regex_fallback(self, code: str) -> str:
return self._format_dropdowns(imports, classes, functions, consts)

def _parse_javascript(self, code: str) -> str:
imports = re.findall(r"(?:import|from|require)\s+['\"].*?['\"]", code)
imports: list[str] = re.findall(
r"(?:import|from|require)\s+['\"].*?['\"]", code
)
classes = re.findall(r"(?:class|interface)\s+([a-zA-Z0-9_$]+)", code)
types = re.findall(r"type\s+([a-zA-Z0-9_$]+)\s*=", code)
classes.extend([f"type {t}" for t in types])
Expand Down Expand Up @@ -113,7 +115,7 @@ def _parse_javascript(self, code: str) -> str:
)

def _parse_html(self, code: str) -> str:
scripts = re.findall(r"<script.*?src=['\"](.*?)['\"]", code)
scripts: list[str] = re.findall(r"<script.*?src=['\"](.*?)['\"]", code)
styles = re.findall(r"<link.*?href=['\"](.*?)['\"]", code)
ids = re.findall(r"id=['\"](.*?)['\"]", code)
return self._format_dropdowns(
Expand Down
9 changes: 8 additions & 1 deletion src/pyob/xml_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,15 @@ def _attempt_line_by_line_match(
match = False
break
if match:
# Apply indentation fix to replacement lines
fixed_replace = self._fix_replace_indentation(
"\n".join(code_lines[i : i + len(search_lines_stripped)]),
"\n".join(replace_lines),
)
new_code_lines = (
code_lines[:i] + replace_lines + code_lines[i + len(search_lines) :]
code_lines[:i]
+ fixed_replace.splitlines()
+ code_lines[i + len(search_lines_stripped) :]
)
new_code = "\n".join(new_code_lines)
if not new_code.endswith("\n") and source.endswith("\n"):
Expand Down
Loading