autoforge-ai 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/templates/coding_prompt.template.md +4 -2
- package/.claude/templates/testing_prompt.template.md +4 -4
- package/client.py +3 -0
- package/package.json +1 -1
- package/parallel_orchestrator.py +21 -4
- package/server/main.py +11 -0
- package/server/services/process_manager.py +1 -0
- package/temp_cleanup.py +50 -1
|
@@ -90,13 +90,13 @@ Use browser automation tools:
|
|
|
90
90
|
|
|
91
91
|
- Navigate to the app in a real browser
|
|
92
92
|
- Interact like a human user (click, type, scroll)
|
|
93
|
-
- Take screenshots at each step
|
|
93
|
+
- Take screenshots at each step (use inline screenshots only -- do NOT save screenshot files to disk)
|
|
94
94
|
- Verify both functionality AND visual appearance
|
|
95
95
|
|
|
96
96
|
**DO:**
|
|
97
97
|
|
|
98
98
|
- Test through the UI with clicks and keyboard input
|
|
99
|
-
- Take screenshots to verify visual appearance
|
|
99
|
+
- Take screenshots to verify visual appearance (inline only, never save to disk)
|
|
100
100
|
- Check for console errors in browser
|
|
101
101
|
- Verify complete user workflows end-to-end
|
|
102
102
|
|
|
@@ -194,6 +194,8 @@ Before context fills up:
|
|
|
194
194
|
|
|
195
195
|
Use Playwright MCP tools (`browser_*`) for UI verification. Key tools: `navigate`, `click`, `type`, `fill_form`, `take_screenshot`, `console_messages`, `network_requests`. All tools have auto-wait built in.
|
|
196
196
|
|
|
197
|
+
**Screenshot rule:** Always use inline mode (base64). NEVER save screenshots as files to disk.
|
|
198
|
+
|
|
197
199
|
Test like a human user with mouse and keyboard. Use `browser_console_messages` to detect errors. Don't bypass UI with JavaScript evaluation.
|
|
198
200
|
|
|
199
201
|
---
|
|
@@ -31,14 +31,14 @@ For the feature returned:
|
|
|
31
31
|
1. Read and understand the feature's verification steps
|
|
32
32
|
2. Navigate to the relevant part of the application
|
|
33
33
|
3. Execute each verification step using browser automation
|
|
34
|
-
4. Take screenshots to document the verification
|
|
34
|
+
4. Take screenshots to document the verification (inline only -- do NOT save to disk)
|
|
35
35
|
5. Check for console errors
|
|
36
36
|
|
|
37
37
|
Use browser automation tools:
|
|
38
38
|
|
|
39
39
|
**Navigation & Screenshots:**
|
|
40
40
|
- browser_navigate - Navigate to a URL
|
|
41
|
-
- browser_take_screenshot - Capture screenshot (
|
|
41
|
+
- browser_take_screenshot - Capture screenshot (inline mode only -- never save to disk)
|
|
42
42
|
- browser_snapshot - Get accessibility tree snapshot
|
|
43
43
|
|
|
44
44
|
**Element Interaction:**
|
|
@@ -79,7 +79,7 @@ A regression has been introduced. You MUST fix it:
|
|
|
79
79
|
|
|
80
80
|
4. **Verify the fix:**
|
|
81
81
|
- Run through all verification steps again
|
|
82
|
-
- Take screenshots confirming the fix
|
|
82
|
+
- Take screenshots confirming the fix (inline only, never save to disk)
|
|
83
83
|
|
|
84
84
|
5. **Mark as passing after fix:**
|
|
85
85
|
```
|
|
@@ -110,7 +110,7 @@ A regression has been introduced. You MUST fix it:
|
|
|
110
110
|
All interaction tools have **built-in auto-wait** -- no manual timeouts needed.
|
|
111
111
|
|
|
112
112
|
- `browser_navigate` - Navigate to URL
|
|
113
|
-
- `browser_take_screenshot` - Capture screenshot
|
|
113
|
+
- `browser_take_screenshot` - Capture screenshot (inline only, never save to disk)
|
|
114
114
|
- `browser_snapshot` - Get accessibility tree
|
|
115
115
|
- `browser_click` - Click elements
|
|
116
116
|
- `browser_type` - Type text
|
package/client.py
CHANGED
|
@@ -446,6 +446,9 @@ def create_client(
|
|
|
446
446
|
mcp_servers["playwright"] = {
|
|
447
447
|
"command": "npx",
|
|
448
448
|
"args": playwright_args,
|
|
449
|
+
"env": {
|
|
450
|
+
"NODE_COMPILE_CACHE": "", # Disable V8 compile caching to prevent .node file accumulation in %TEMP%
|
|
451
|
+
},
|
|
449
452
|
}
|
|
450
453
|
|
|
451
454
|
# Build environment overrides for API endpoint configuration
|
package/package.json
CHANGED
package/parallel_orchestrator.py
CHANGED
|
@@ -846,7 +846,7 @@ class ParallelOrchestrator:
|
|
|
846
846
|
"encoding": "utf-8",
|
|
847
847
|
"errors": "replace",
|
|
848
848
|
"cwd": str(self.project_dir), # Run from project dir so CLI creates .claude/ in project
|
|
849
|
-
"env": {**os.environ, "PYTHONUNBUFFERED": "1"},
|
|
849
|
+
"env": {**os.environ, "PYTHONUNBUFFERED": "1", "NODE_COMPILE_CACHE": ""},
|
|
850
850
|
}
|
|
851
851
|
if sys.platform == "win32":
|
|
852
852
|
popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
|
|
@@ -909,7 +909,7 @@ class ParallelOrchestrator:
|
|
|
909
909
|
"encoding": "utf-8",
|
|
910
910
|
"errors": "replace",
|
|
911
911
|
"cwd": str(self.project_dir), # Run from project dir so CLI creates .claude/ in project
|
|
912
|
-
"env": {**os.environ, "PYTHONUNBUFFERED": "1"},
|
|
912
|
+
"env": {**os.environ, "PYTHONUNBUFFERED": "1", "NODE_COMPILE_CACHE": ""},
|
|
913
913
|
}
|
|
914
914
|
if sys.platform == "win32":
|
|
915
915
|
popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
|
|
@@ -1013,7 +1013,7 @@ class ParallelOrchestrator:
|
|
|
1013
1013
|
"encoding": "utf-8",
|
|
1014
1014
|
"errors": "replace",
|
|
1015
1015
|
"cwd": str(self.project_dir), # Run from project dir so CLI creates .claude/ in project
|
|
1016
|
-
"env": {**os.environ, "PYTHONUNBUFFERED": "1"},
|
|
1016
|
+
"env": {**os.environ, "PYTHONUNBUFFERED": "1", "NODE_COMPILE_CACHE": ""},
|
|
1017
1017
|
}
|
|
1018
1018
|
if sys.platform == "win32":
|
|
1019
1019
|
popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
|
|
@@ -1074,7 +1074,7 @@ class ParallelOrchestrator:
|
|
|
1074
1074
|
"encoding": "utf-8",
|
|
1075
1075
|
"errors": "replace",
|
|
1076
1076
|
"cwd": str(AUTOFORGE_ROOT),
|
|
1077
|
-
"env": {**os.environ, "PYTHONUNBUFFERED": "1"},
|
|
1077
|
+
"env": {**os.environ, "PYTHONUNBUFFERED": "1", "NODE_COMPILE_CACHE": ""},
|
|
1078
1078
|
}
|
|
1079
1079
|
if sys.platform == "win32":
|
|
1080
1080
|
popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
|
|
@@ -1160,6 +1160,19 @@ class ParallelOrchestrator:
|
|
|
1160
1160
|
debug_log.log("CLEANUP", f"Error killing process tree for {agent_type} agent", error=str(e))
|
|
1161
1161
|
self._on_agent_complete(feature_id, proc.returncode, agent_type, proc)
|
|
1162
1162
|
|
|
1163
|
+
def _run_inter_session_cleanup(self):
|
|
1164
|
+
"""Run lightweight cleanup between agent sessions.
|
|
1165
|
+
|
|
1166
|
+
Removes stale temp files and project screenshots to prevent
|
|
1167
|
+
disk space accumulation during long overnight runs.
|
|
1168
|
+
"""
|
|
1169
|
+
try:
|
|
1170
|
+
from temp_cleanup import cleanup_project_screenshots, cleanup_stale_temp
|
|
1171
|
+
cleanup_stale_temp()
|
|
1172
|
+
cleanup_project_screenshots(self.project_dir)
|
|
1173
|
+
except Exception as e:
|
|
1174
|
+
debug_log.log("CLEANUP", f"Inter-session cleanup failed (non-fatal): {e}")
|
|
1175
|
+
|
|
1163
1176
|
def _signal_agent_completed(self):
|
|
1164
1177
|
"""Signal that an agent has completed, waking the main loop.
|
|
1165
1178
|
|
|
@@ -1235,6 +1248,8 @@ class ParallelOrchestrator:
|
|
|
1235
1248
|
pid=proc.pid,
|
|
1236
1249
|
feature_id=feature_id,
|
|
1237
1250
|
status=status)
|
|
1251
|
+
# Run lightweight cleanup between sessions
|
|
1252
|
+
self._run_inter_session_cleanup()
|
|
1238
1253
|
# Signal main loop that an agent slot is available
|
|
1239
1254
|
self._signal_agent_completed()
|
|
1240
1255
|
return
|
|
@@ -1301,6 +1316,8 @@ class ParallelOrchestrator:
|
|
|
1301
1316
|
else:
|
|
1302
1317
|
print(f"Feature #{feature_id} {status}", flush=True)
|
|
1303
1318
|
|
|
1319
|
+
# Run lightweight cleanup between sessions
|
|
1320
|
+
self._run_inter_session_cleanup()
|
|
1304
1321
|
# Signal main loop that an agent slot is available
|
|
1305
1322
|
self._signal_agent_completed()
|
|
1306
1323
|
|
package/server/main.py
CHANGED
|
@@ -61,6 +61,17 @@ UI_DIST_DIR = ROOT_DIR / "ui" / "dist"
|
|
|
61
61
|
@asynccontextmanager
|
|
62
62
|
async def lifespan(app: FastAPI):
|
|
63
63
|
"""Lifespan context manager for startup and shutdown."""
|
|
64
|
+
# Startup - clean up stale temp files (Playwright profiles, .node cache, etc.)
|
|
65
|
+
try:
|
|
66
|
+
from temp_cleanup import cleanup_stale_temp
|
|
67
|
+
stats = cleanup_stale_temp()
|
|
68
|
+
if stats["dirs_deleted"] > 0 or stats["files_deleted"] > 0:
|
|
69
|
+
mb_freed = stats["bytes_freed"] / (1024 * 1024)
|
|
70
|
+
logger.info("Startup temp cleanup: %d dirs, %d files, %.1f MB freed",
|
|
71
|
+
stats["dirs_deleted"], stats["files_deleted"], mb_freed)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.warning("Startup temp cleanup failed (non-fatal): %s", e)
|
|
74
|
+
|
|
64
75
|
# Startup - clean up orphaned lock files from previous runs
|
|
65
76
|
cleanup_orphaned_locks()
|
|
66
77
|
cleanup_orphaned_devserver_locks()
|
|
@@ -410,6 +410,7 @@ class AgentProcessManager:
|
|
|
410
410
|
**os.environ,
|
|
411
411
|
"PYTHONUNBUFFERED": "1",
|
|
412
412
|
"PLAYWRIGHT_HEADLESS": "true" if playwright_headless else "false",
|
|
413
|
+
"NODE_COMPILE_CACHE": "", # Disable V8 compile caching to prevent .node file accumulation in %TEMP%
|
|
413
414
|
**api_env,
|
|
414
415
|
}
|
|
415
416
|
|
package/temp_cleanup.py
CHANGED
|
@@ -37,11 +37,12 @@ DIR_PATTERNS = [
|
|
|
37
37
|
"mongodb-memory-server*", # MongoDB Memory Server binaries
|
|
38
38
|
"ng-*", # Angular CLI temp directories
|
|
39
39
|
"scoped_dir*", # Chrome/Chromium temp directories
|
|
40
|
+
"node-compile-cache", # Node.js V8 compile cache directory
|
|
40
41
|
]
|
|
41
42
|
|
|
42
43
|
# File patterns to clean up (glob patterns)
|
|
43
44
|
FILE_PATTERNS = [
|
|
44
|
-
".
|
|
45
|
+
".[0-9a-f]*.node", # Node.js/V8 compile cache files (~7MB each, varying hex prefixes)
|
|
45
46
|
"claude-*-cwd", # Claude CLI working directory temp files
|
|
46
47
|
"mat-debug-*.log", # Material/Angular debug logs
|
|
47
48
|
]
|
|
@@ -122,6 +123,54 @@ def cleanup_stale_temp(max_age_seconds: int = MAX_AGE_SECONDS) -> dict:
|
|
|
122
123
|
return stats
|
|
123
124
|
|
|
124
125
|
|
|
126
|
+
def cleanup_project_screenshots(project_dir: Path, max_age_seconds: int = 300) -> dict:
|
|
127
|
+
"""
|
|
128
|
+
Clean up stale screenshot files from the project root.
|
|
129
|
+
|
|
130
|
+
Playwright browser verification can leave .png files in the project
|
|
131
|
+
directory. This removes them after they've aged out (default 5 minutes).
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
project_dir: Path to the project directory.
|
|
135
|
+
max_age_seconds: Maximum age in seconds before a screenshot is deleted.
|
|
136
|
+
Defaults to 5 minutes (300 seconds).
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Dictionary with cleanup statistics (files_deleted, bytes_freed, errors).
|
|
140
|
+
"""
|
|
141
|
+
cutoff_time = time.time() - max_age_seconds
|
|
142
|
+
stats: dict = {"files_deleted": 0, "bytes_freed": 0, "errors": []}
|
|
143
|
+
|
|
144
|
+
screenshot_patterns = [
|
|
145
|
+
"feature*-*.png",
|
|
146
|
+
"screenshot-*.png",
|
|
147
|
+
"step-*.png",
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
for pattern in screenshot_patterns:
|
|
151
|
+
for item in project_dir.glob(pattern):
|
|
152
|
+
if not item.is_file():
|
|
153
|
+
continue
|
|
154
|
+
try:
|
|
155
|
+
mtime = item.stat().st_mtime
|
|
156
|
+
if mtime < cutoff_time:
|
|
157
|
+
size = item.stat().st_size
|
|
158
|
+
item.unlink(missing_ok=True)
|
|
159
|
+
if not item.exists():
|
|
160
|
+
stats["files_deleted"] += 1
|
|
161
|
+
stats["bytes_freed"] += size
|
|
162
|
+
logger.debug(f"Deleted project screenshot: {item}")
|
|
163
|
+
except Exception as e:
|
|
164
|
+
stats["errors"].append(f"Failed to delete {item}: {e}")
|
|
165
|
+
logger.debug(f"Failed to delete screenshot {item}: {e}")
|
|
166
|
+
|
|
167
|
+
if stats["files_deleted"] > 0:
|
|
168
|
+
mb_freed = stats["bytes_freed"] / (1024 * 1024)
|
|
169
|
+
logger.info(f"Screenshot cleanup: {stats['files_deleted']} files, {mb_freed:.1f} MB freed")
|
|
170
|
+
|
|
171
|
+
return stats
|
|
172
|
+
|
|
173
|
+
|
|
125
174
|
def _get_dir_size(path: Path) -> int:
|
|
126
175
|
"""Get total size of a directory in bytes."""
|
|
127
176
|
total = 0
|