|
4 | 4 | import re |
5 | 5 | import shutil |
6 | 6 | import subprocess |
| 7 | +import sys |
7 | 8 | import difflib |
8 | 9 | import tempfile |
9 | 10 | from pathlib import Path |
@@ -56,6 +57,68 @@ def _verbose(msg: str) -> None: |
56 | 57 | if _IS_VERBOSE: |
57 | 58 | console.print(msg) |
58 | 59 |
|
| 60 | + |
| 61 | +def _detect_suspicious_files(cwd: Path, context: str = "") -> List[Path]: |
| 62 | + """ |
| 63 | + Detect suspicious single-character files (like C, E, T) in a directory. |
| 64 | +
|
| 65 | + This is a diagnostic function to help identify when/where these files are created. |
| 66 | + Issue #186: Empty files named C, E, T (first letters of Code, Example, Test) |
| 67 | + have been appearing during agentic operations. |
| 68 | +
|
| 69 | + Args: |
| 70 | + cwd: Directory to scan |
| 71 | + context: Description of what operation just ran (for logging) |
| 72 | +
|
| 73 | + Returns: |
| 74 | + List of suspicious file paths found |
| 75 | + """ |
| 76 | + suspicious: List[Path] = [] |
| 77 | + try: |
| 78 | + for f in cwd.iterdir(): |
| 79 | + if f.is_file() and len(f.name) <= 2 and not f.name.startswith('.'): |
| 80 | + suspicious.append(f) |
| 81 | + |
| 82 | + if suspicious: |
| 83 | + import datetime |
| 84 | + timestamp = datetime.datetime.now().isoformat() |
| 85 | + _always(f"[bold red]⚠️ SUSPICIOUS FILES DETECTED (Issue #186)[/bold red]") |
| 86 | + _always(f"[red]Timestamp: {timestamp}[/red]") |
| 87 | + _always(f"[red]Context: {context}[/red]") |
| 88 | + _always(f"[red]Directory: {cwd}[/red]") |
| 89 | + for sf in suspicious: |
| 90 | + try: |
| 91 | + size = sf.stat().st_size |
| 92 | + _always(f"[red] - {sf.name} (size: {size} bytes)[/red]") |
| 93 | + except Exception: |
| 94 | + _always(f"[red] - {sf.name} (could not stat)[/red]") |
| 95 | + |
| 96 | + # Also log to a file for persistence |
| 97 | + log_file = Path.home() / ".pdd" / "suspicious_files.log" |
| 98 | + log_file.parent.mkdir(parents=True, exist_ok=True) |
| 99 | + with open(log_file, "a") as lf: |
| 100 | + lf.write(f"\n{'='*60}\n") |
| 101 | + lf.write(f"Timestamp: {timestamp}\n") |
| 102 | + lf.write(f"Context: {context}\n") |
| 103 | + lf.write(f"Directory: {cwd}\n") |
| 104 | + lf.write(f"CWD at detection: {Path.cwd()}\n") |
| 105 | + for sf in suspicious: |
| 106 | + try: |
| 107 | + size = sf.stat().st_size |
| 108 | + lf.write(f" - {sf.name} (size: {size} bytes)\n") |
| 109 | + except Exception as e: |
| 110 | + lf.write(f" - {sf.name} (error: {e})\n") |
| 111 | + # Log stack trace to help identify caller |
| 112 | + import traceback |
| 113 | + lf.write("Stack trace:\n") |
| 114 | + lf.write(traceback.format_stack()[-10:][0] if traceback.format_stack() else "N/A") |
| 115 | + lf.write("\n") |
| 116 | + except Exception as e: |
| 117 | + _verbose(f"[yellow]Could not scan for suspicious files: {e}[/yellow]") |
| 118 | + |
| 119 | + return suspicious |
| 120 | + |
| 121 | + |
59 | 122 | def _begin_marker(path: Path) -> str: |
60 | 123 | """Marker that must wrap the BEGIN of a corrected file block emitted by the agent.""" |
61 | 124 | return f"<<<BEGIN_FILE:{path}>>>" |
@@ -435,6 +498,12 @@ def _run_anthropic_variants(prompt_text: str, cwd: Path, total_timeout: int, lab |
435 | 498 | return last |
436 | 499 | finally: |
437 | 500 | prompt_file.unlink(missing_ok=True) |
| 501 | + # Issue #186: Scan for suspicious files after Anthropic agent runs |
| 502 | + _detect_suspicious_files(cwd, f"After _run_anthropic_variants ({label})") |
| 503 | + # Also scan project root in case agent created files there |
| 504 | + project_root = Path.cwd() |
| 505 | + if project_root != cwd: |
| 506 | + _detect_suspicious_files(project_root, f"After _run_anthropic_variants ({label}) - project root") |
438 | 507 |
|
439 | 508 | def _run_cli_args_google(args: List[str], cwd: Path, timeout: int) -> subprocess.CompletedProcess: |
440 | 509 | """Subprocess runner for Google commands with common sanitized env.""" |
@@ -494,6 +563,12 @@ def _run_google_variants(prompt_text: str, cwd: Path, total_timeout: int, label: |
494 | 563 | return last |
495 | 564 | finally: |
496 | 565 | prompt_file.unlink(missing_ok=True) |
| 566 | + # Issue #186: Scan for suspicious files after Google agent runs |
| 567 | + _detect_suspicious_files(cwd, f"After _run_google_variants ({label})") |
| 568 | + # Also scan project root in case agent created files there |
| 569 | + project_root = Path.cwd() |
| 570 | + if project_root != cwd: |
| 571 | + _detect_suspicious_files(project_root, f"After _run_google_variants ({label}) - project root") |
497 | 572 |
|
498 | 573 | def _run_testcmd(cmd: str, cwd: Path) -> bool: |
499 | 574 | """ |
@@ -532,7 +607,7 @@ def _verify_and_log(unit_test_file: str, cwd: Path, *, verify_cmd: Optional[str] |
532 | 607 | return _run_testcmd(run_cmd, cwd) |
533 | 608 | # Fallback: try running with Python if no run command found |
534 | 609 | verify = subprocess.run( |
535 | | - [os.sys.executable, str(Path(unit_test_file).resolve())], |
| 610 | + [sys.executable, str(Path(unit_test_file).resolve())], |
536 | 611 | capture_output=True, |
537 | 612 | text=True, |
538 | 613 | check=False, |
@@ -800,7 +875,7 @@ def _try_harvest_then_verify( |
800 | 875 | newest = code_path.read_text(encoding="utf-8") |
801 | 876 | _print_diff(code_snapshot, newest, code_path) |
802 | 877 | ok = _post_apply_verify_or_testcmd( |
803 | | - provider, unit_test_file, working_dir, |
| 878 | + provider, unit_test_file, cwd, |
804 | 879 | verify_cmd=verify_cmd, verify_enabled=verify_enabled, |
805 | 880 | stdout=res.stdout or "", stderr=res.stderr or "" |
806 | 881 | ) |
@@ -992,7 +1067,7 @@ def _is_useless_error_content(content: str) -> bool: |
992 | 1067 | else: |
993 | 1068 | # Fallback: run directly with Python interpreter |
994 | 1069 | pre = subprocess.run( |
995 | | - [os.sys.executable, str(Path(unit_test_file).resolve())], |
| 1070 | + [sys.executable, str(Path(unit_test_file).resolve())], |
996 | 1071 | capture_output=True, |
997 | 1072 | text=True, |
998 | 1073 | check=False, |
|
0 commit comments