Skip to content

Commit dbfb66e

Browse files
VicVic
authored andcommitted
Fixed-IgnoreDir/IgnoreFiles Issue
1 parent 2a11d8d commit dbfb66e

File tree

7 files changed

+114
-45
lines changed

7 files changed

+114
-45
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ SYMBOLS.json
1818
ANALYSIS.md
1919
build_env
2020
*.dmg
21-
NoClaw.spec
21+
*.spec

autoreviewer.py

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -836,31 +836,37 @@ def implement_feature(self, feature_content: str) -> bool:
836836
if not match:
837837
logger.error("Could not determine target file from FEATURE.md formatting.")
838838
return False
839+
839840
rel_path = match.group(1)
840841
target_path = os.path.join(self.target_dir, rel_path)
841842
lang_name, lang_tag = self.get_language_info(target_path)
842-
with open(target_path, "r", encoding="utf-8") as f:
843-
source_code = f.read()
844843

845-
# --- NEW: ARCHITECTURAL SPLIT SUPPORT ---
846-
# Look for a <CREATE_FILE> tag in the AI proposal
847-
new_file_match = re.search(
844+
with open(target_path, "r", encoding="utf-8") as f_handle:
845+
source_code = f_handle.read()
846+
847+
# --- TRANSACTIONAL STORAGE ---
848+
created_files: list[str] = []
849+
850+
# Look for <CREATE_FILE> tags in the AI proposal
851+
new_file_matches = re.finditer(
848852
r'<CREATE_FILE path="(.*?)">(.*?)</CREATE_FILE>', feature_content, re.DOTALL
849853
)
850-
if new_file_match:
851-
new_path_rel = new_file_match.group(1)
852-
new_code_payload = new_file_match.group(2).strip()
854+
855+
for file_match in new_file_matches:
856+
new_path_rel = file_match.group(1)
857+
new_code_payload = file_match.group(2).strip()
853858
new_path_abs = os.path.join(self.target_dir, new_path_rel)
859+
854860
if not os.path.exists(new_path_abs):
855-
logger.warning(
856-
f"🏗️ ARCHITECTURAL SPLIT: Spawning new module `{new_path_rel}`"
857-
)
858-
with open(new_path_abs, "w", encoding="utf-8") as f:
859-
f.write(new_code_payload)
860-
self.session_context.append(
861-
f"Created new architectural module: `{new_path_rel}`"
862-
)
863-
# ----------------------------------------
861+
try:
862+
logger.warning(
863+
f"🏗️ ARCHITECTURAL SPLIT: Spawning new module `{new_path_rel}`"
864+
)
865+
with open(new_path_abs, "w", encoding="utf-8") as f_new:
866+
f_new.write(new_code_payload)
867+
created_files.append(new_path_abs)
868+
except Exception as e:
869+
logger.error(f"Failed to create new module {new_path_rel}: {e}")
864870

865871
exp_match = re.search(
866872
r"\*\*Explanation:\*\*(.*?)(?:###|---|>)",
@@ -872,6 +878,7 @@ def implement_feature(self, feature_content: str) -> bool:
872878
if exp_match
873879
else f"Implemented a new structural feature for: [{rel_path}]..."
874880
)
881+
875882
logger.info(
876883
f"Implementing approved feature seamlessly directly into {rel_path}..."
877884
)
@@ -885,30 +892,58 @@ def implement_feature(self, feature_content: str) -> bool:
885892
source_code=source_code,
886893
rel_path=rel_path,
887894
)
895+
888896
new_code, _, _ = self.get_valid_edit(
889897
prompt, source_code, require_edit=True, target_filepath=target_path
890898
)
899+
891900
if new_code == source_code:
892-
logger.error("Implementation failed. LLM did not apply valid changes.")
901+
logger.error("Implementation failed. Rolling back created modules.")
902+
# FIXED: Used 'file_path' instead of 'f' to avoid Mypy TextIOWrapper collision
903+
for file_path in created_files:
904+
if os.path.exists(file_path):
905+
os.remove(file_path)
893906
return False
907+
894908
if lang_tag == "python":
895909
new_code = self.ensure_imports_retained(source_code, new_code, target_path)
896-
with open(target_path, "w", encoding="utf-8") as f:
897-
f.write(new_code)
910+
911+
with open(target_path, "w", encoding="utf-8") as f_out:
912+
f_out.write(new_code)
913+
898914
if lang_tag == "python":
915+
# If verification fails, clean up the newly spawned files
899916
if not self.run_linter_fix_loop(
900917
context_of_change=feature_content
901918
) or not self.run_and_verify_app(context_of_change=feature_content):
919+
# FIXED: Used 'file_path' instead of 'f'
920+
for file_path in created_files:
921+
if os.path.exists(file_path):
922+
os.remove(file_path)
902923
return False
924+
903925
if not self.check_downstream_breakages(target_path, rel_path):
904-
logger.error(
905-
"❌ Feature implementation failed downstream type checks (Mypy)."
906-
)
926+
# FIXED: Used 'file_path' instead of 'f'
927+
for file_path in created_files:
928+
if os.path.exists(file_path):
929+
os.remove(file_path)
907930
return False
931+
908932
logger.info(f"✅ Successfully implemented feature directly into {rel_path}.")
933+
909934
self.session_context.append(
910-
f"Successfully implemented feature directly into `{rel_path}` -> {feature_explanation}"
935+
f"SUCCESSFUL CHANGE in `{rel_path}`: {feature_explanation}"
911936
)
937+
938+
if created_files:
939+
# FIXED: Used 'file_path' in list comprehension
940+
self.session_context.append(
941+
"Created new modules: "
942+
+ ", ".join(
943+
[os.path.basename(file_path) for file_path in created_files]
944+
)
945+
)
946+
912947
if os.path.exists(self.feature_file):
913948
os.remove(self.feature_file)
914949
return True
@@ -1064,6 +1099,10 @@ def run_pipeline(self, current_iteration: int):
10641099
if not success:
10651100
self.restore_workspace(backup_state)
10661101
logger.warning("🔄 Rollback performed due to unfixable errors.")
1102+
self.session_context.append(
1103+
"CRITICAL: The last refactor/feature attempt FAILED and was ROLLED BACK. "
1104+
"The files on disk have NOT changed. Check FAILED_FEATURE.md for error logs."
1105+
)
10671106

10681107
failure_report = f"\n\n### ❌ FAILURE ATTEMPT LOGS ({time.strftime('%Y-%m-%d %H:%M:%S')})\n"
10691108
failure_report += "\n".join(self.session_context[-3:])

build_pyinstaller_multiOS.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def main():
99
project_root = Path(__file__).parent.absolute()
1010

1111
# --- Configuration ---
12-
VERSION = "0.2.0"
12+
VERSION = "0.2.1-beta1"
1313
APP_NAME = "PyOuroBoros"
1414

1515
print(f"🚀 Forging {APP_NAME} v{VERSION} for {os_name}...")
@@ -54,7 +54,7 @@ def main():
5454
+ [
5555
"--onefile",
5656
"--console",
57-
"--icon=no-claw.ico",
57+
"--icon=pyob.ico", # convert pyob.png or pyob.icns to .ico
5858
]
5959
)
6060
dist_output = project_root / "dist" / f"{APP_NAME}.exe"

core_utils.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,14 @@ def _edit_prompt_with_external_editor(self, initial_prompt: str) -> str:
233233
def backup_workspace(self) -> dict[str, str]:
234234
state: dict[str, str] = {}
235235
for root, dirs, files in os.walk(self.target_dir):
236+
# Prune directories using the global set
236237
dirs[:] = [d for d in dirs if d not in IGNORE_DIRS]
238+
237239
for file in files:
240+
# Skip ignored files
241+
if file in IGNORE_FILES:
242+
continue
243+
238244
if any(file.endswith(ext) for ext in SUPPORTED_EXTENSIONS):
239245
path = os.path.join(root, file)
240246
try:
@@ -605,7 +611,7 @@ def normalize(t):
605611
return new_code, explanation, all_edits_succeeded
606612

607613
def _find_entry_file(self) -> str | None:
608-
FORBIDDEN = {"venv", ".venv", "autovenv", "__pycache__", "node_modules", ".git"}
614+
# PRIORITY SEARCH
609615
priority_files = [
610616
"entrance.py",
611617
"main.py",
@@ -617,24 +623,32 @@ def _find_entry_file(self) -> str | None:
617623
target = os.path.join(self.target_dir, f_name)
618624
if os.path.exists(target):
619625
try:
620-
with open(target, "r", encoding="utf-8") as f:
626+
with open(target, "r", encoding="utf-8", errors="ignore") as f:
621627
if 'if __name__ == "__main__":' in f.read():
622628
return target
623629
except Exception:
624630
continue
631+
632+
# DEEP SEARCH
625633
for root, dirs, files in os.walk(self.target_dir):
626-
dirs[:] = [d for d in dirs if d not in FORBIDDEN and not d.startswith(".")]
634+
# --- FIXED: Use the GLOBAL IGNORE_DIRS here ---
635+
dirs[:] = [
636+
d for d in dirs if d not in IGNORE_DIRS and not d.startswith(".")
637+
]
638+
627639
for file in files:
628-
if file in [
629-
"autoreviewer.py",
630-
"core_utils.py",
631-
"prompts_and_memory.py",
632-
] or not file.endswith(".py"):
640+
# --- FIXED: Use the GLOBAL IGNORE_FILES here ---
641+
if file in IGNORE_FILES or not file.endswith(".py"):
642+
continue
643+
644+
# Double-check that we aren't inside an ignore dir (backup safety)
645+
if any(x in root for x in IGNORE_DIRS):
633646
continue
647+
634648
file_path = os.path.join(root, file)
635649
try:
636-
with open(file_path, "r", encoding="utf-8") as f:
637-
if 'if __name__ == "__main__":' in f.read():
650+
with open(file_path, "r", encoding="utf-8") as f_obj:
651+
if 'if __name__ == "__main__":' in f_obj.read():
638652
return file_path
639653
except Exception:
640654
continue

entrance.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,7 @@ def execute_targeted_iteration(self, iteration: int):
224224
if any(f in target_rel_path for f in engine_files):
225225
timestamp = time.strftime("%Y%m%d_%H%M%S")
226226
project_name = os.path.basename(self.target_dir)
227-
base_backup_path = (
228-
Path.home() / "Documents" / "PYOB_Backups" / project_name
229-
)
227+
base_backup_path = Path.home() / "Documents" / "PYOB_Backups" / project_name
230228
pod_path = base_backup_path / f"safety_pod_v{iteration}_{timestamp}"
231229

232230
try:
@@ -501,21 +499,41 @@ def update_analysis_for_single_file(self, target_abs_path: str, rel_path: str):
501499

502500
def update_ledger_for_file(self, rel_path: str, code: str):
503501
ext = os.path.splitext(rel_path)[1]
502+
# Clear existing definitions for this file before re-populating
503+
definitions_to_remove = [
504+
name
505+
for name, path in self.ledger["definitions"].items()
506+
if path == rel_path
507+
]
508+
for name in definitions_to_remove:
509+
del self.ledger["definitions"][name]
510+
504511
if ext == ".py":
505512
try:
506513
tree = ast.parse(code)
507514
for n in ast.walk(tree):
508515
if isinstance(n, (ast.FunctionDef, ast.ClassDef)):
509516
self.ledger["definitions"][n.name] = rel_path
517+
elif isinstance(n, ast.Assign):
518+
# Add top-level global variables and constants as definitions.
519+
# This aligns with the stated bug fix and the logic in _parse_python.
520+
for target in n.targets:
521+
if isinstance(target, ast.Name) and target.id.isupper():
522+
self.ledger["definitions"][target.id] = rel_path
510523
except Exception as e:
511524
logger.warning(f"Failed to parse Python AST for {rel_path}: {e}")
512525
elif ext in [".js", ".ts"]:
513526
defs = re.findall(
514527
r"(?:function|class|const|var|let)\s+([a-zA-Z0-9_$]+)", code
515528
)
516529
for d in defs:
517-
if len(d) > 3:
530+
if len(d) > 3: # Minimum length for a symbol
518531
self.ledger["definitions"][d] = rel_path
532+
533+
# Clear existing references for this file before re-populating
534+
if rel_path in self.ledger["references"]:
535+
del self.ledger["references"][rel_path]
536+
519537
potential_refs = re.findall(r"([a-zA-Z0-9_$]{4,})(?=\s*\(|\s*\.)", code)
520538
self.ledger["references"][rel_path] = list(set(potential_refs))
521539
self.save_ledger()

prompts_and_memory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class PromptsAndMemoryMixin:
1414

1515
def _ensure_prompt_files(self) -> None:
1616
templates = {
17-
"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. CRITICAL: Keep the ENTIRE document under 250 words. Be ruthless. Delete old, irrelevant details.\n3. Keep lists strictly to bullet points. No long paragraphs.\n4. Respond EXCLUSIVELY with the raw markdown for MEMORY.md. Do not use ```markdown fences or <THOUGHT> blocks.",
17+
"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.",
1818
"RM.md": "You are the PYOB Memory Manager. The current MEMORY.md is too bloated and is breaking the AI context window.\n\n### Bloated Memory:\n{current_memory}\n\n### INSTRUCTIONS:\n1. AGGRESSIVELY COMPRESS this memory. \n2. Delete duplicate information, repetitive logs, and obvious statements.\n3. Keep ONLY the core architectural rules and crucial file dependencies.\n4. The final output MUST BE UNDER 150 WORDS.\n5. Respond EXCLUSIVELY with the raw markdown. No fences, no thoughts.",
1919
"PP.md": "You are an elite PYOB Software Engineer. Analyze the code for bugs or architectural gaps.\n\n{memory_section}{ruff_section}{mypy_section}{custom_issues_section}### Source Code:\n```{lang_tag}\n{content}\n```\n\n### CRITICAL RULES:\n1. **SURGICAL FIXES**: Every <SEARCH> block must be exactly 2-5 lines.\n2. **NO HALLUCINATIONS**: Do not invent bugs. If the code is functional, state 'The code looks good.'\n3. **ARCHITECTURAL BLOAT**: If 'Code Quality Issues' flags bloat (>800 lines), your Evaluation MUST prioritize identifying which classes or methods can be moved to a new file to restore modularity.\n4. **MISSING IMPORTS**: If you use a new module/type, you MUST add an <EDIT> block to import it at the top.\n5. **INDENTATION IS SYNTAX**: Your <REPLACE> blocks must have perfect, absolute indentation.\n6. **DEFEATING MYPY**: If fixing a `[union-attr]` error, use `assert variable is not None` or `# type: ignore`.\n\n### HOW TO RESPOND (CHOOSE ONE):\n\n**SCENARIO A: Code has bugs/bloat and needs edits:**\n<THOUGHT>\nSummary: ...\nEvaluation: [Address bloat here if flagged]\nImports Required: ...\nAction: I will fix X by doing Y.\n</THOUGHT>\n<EDIT>\n<SEARCH>\nExact lines to replace\n</SEARCH>\n<REPLACE>\nNew lines\n</REPLACE>\n</EDIT>\n\n**SCENARIO B: Code is perfect. NO EDITS NEEDED:**\n<THOUGHT>\nSummary: ...\nEvaluation: ...\nAction: The code looks good. No fixes needed.\n</THOUGHT>",
2020
"ALF.md": "You are an elite developer fixing syntax errors.\nThe file `{rel_path}` failed validation with these exact errors:\n{err_text}\n\n### Current Code:\n```\n{code}\n```\n\n### Instructions:\n1. Fix the syntax errors (like stray brackets, unexpected tokens, or indentation) using surgical XML edits.\n2. Respond EXCLUSIVELY with a <THOUGHT> block followed by ONE OR MORE <EDIT> blocks.\n3. Ensure your edits perfectly align with the surrounding brackets.",

pyob_launcher.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ def load_config():
2626

2727
# 2. Models with Warning
2828
print("\n🤖 Step 2: Model Configuration")
29-
print(
30-
"⚠️ WARNING: PYOB is optimized for 'gemini-2.5-flash' and 'qwen3-coder:30b'."
31-
)
29+
print("⚠️ WARNING: PYOB is optimized for 'gemini-2.5-flash' and 'qwen3-coder:30b'.")
3230
print(" Changing these may result in parsing errors or logic loops.")
3331

3432
g_model = input("\nEnter Gemini Model [default: gemini-2.5-flash]: ").strip()

0 commit comments

Comments
 (0)